From 18fe7aa260fae72511d1612b16eeff79b42f98f3 Mon Sep 17 00:00:00 2001 From: Teemu Suo-Anttila Date: Fri, 18 Mar 2016 12:07:17 +0200 Subject: Build vaadin-client with maven Change-Id: I36c426086a2b957f6f738d17470c499a01ddac3b --- .classpath | 4 +- .gitignore | 1 + client-compiled/ivy.xml | 2 +- client-compiler/ivy.xml | 2 +- client/build.xml | 80 - client/ivy.xml | 34 +- client/pom.xml | 218 + client/src/com/vaadin/DefaultWidgetSet.gwt.xml | 42 - client/src/com/vaadin/Vaadin.gwt.xml | 93 - .../vaadin/VaadinBrowserSpecificOverrides.gwt.xml | 47 - client/src/com/vaadin/client/AnimationUtil.java | 179 - .../vaadin/client/ApplicationConfiguration.java | 876 -- .../com/vaadin/client/ApplicationConnection.java | 1642 ---- client/src/com/vaadin/client/BrowserInfo.java | 511 -- client/src/com/vaadin/client/CSSRule.java | 132 - .../src/com/vaadin/client/ComponentConnector.java | 161 - client/src/com/vaadin/client/ComponentDetail.java | 78 - .../src/com/vaadin/client/ComponentDetailMap.java | 99 - client/src/com/vaadin/client/ComputedStyle.java | 362 - .../client/ConnectorHierarchyChangeEvent.java | 103 - client/src/com/vaadin/client/ConnectorMap.java | 306 - .../vaadin/client/ContainerResizedListener.java | 40 - client/src/com/vaadin/client/DateTimeService.java | 504 -- client/src/com/vaadin/client/DeferredWorker.java | 36 - .../vaadin/client/DirectionalManagedLayout.java | 24 - client/src/com/vaadin/client/EventHelper.java | 147 - client/src/com/vaadin/client/FastStringMap.java | 74 - client/src/com/vaadin/client/FastStringSet.java | 101 - client/src/com/vaadin/client/Focusable.java | 31 - .../client/HasChildMeasurementHintConnector.java | 72 - .../com/vaadin/client/HasComponentsConnector.java | 94 - .../vaadin/client/JavaScriptConnectorHelper.java | 511 -- .../src/com/vaadin/client/JavaScriptExtension.java | 58 - client/src/com/vaadin/client/JsArrayObject.java | 42 - client/src/com/vaadin/client/LayoutManager.java | 1836 ---- client/src/com/vaadin/client/LayoutManagerIE8.java | 115 - .../vaadin/client/LocaleNotLoadedException.java | 25 - client/src/com/vaadin/client/LocaleService.java | 154 - client/src/com/vaadin/client/MeasuredSize.java | 299 - .../vaadin/client/MouseEventDetailsBuilder.java | 96 - client/src/com/vaadin/client/Paintable.java | 31 - client/src/com/vaadin/client/Profiler.java | 719 -- .../src/com/vaadin/client/RenderInformation.java | 165 - client/src/com/vaadin/client/RenderSpace.java | 68 - client/src/com/vaadin/client/ResourceLoader.java | 612 -- client/src/com/vaadin/client/ServerConnector.java | 219 - client/src/com/vaadin/client/SimpleTree.java | 192 - client/src/com/vaadin/client/StyleConstants.java | 47 - client/src/com/vaadin/client/SuperDevMode.java | 275 - client/src/com/vaadin/client/TooltipInfo.java | 87 - client/src/com/vaadin/client/UIDL.java | 563 -- client/src/com/vaadin/client/Util.java | 1273 --- client/src/com/vaadin/client/VCaption.java | 776 -- client/src/com/vaadin/client/VCaptionWrapper.java | 51 - client/src/com/vaadin/client/VConsole.java | 103 - client/src/com/vaadin/client/VErrorMessage.java | 104 - .../src/com/vaadin/client/VLoadingIndicator.java | 253 - client/src/com/vaadin/client/VSchedulerImpl.java | 45 - client/src/com/vaadin/client/VTooltip.java | 786 -- client/src/com/vaadin/client/VUIDLBrowser.java | 368 - client/src/com/vaadin/client/ValueMap.java | 121 - .../src/com/vaadin/client/WidgetInstantiator.java | 23 - client/src/com/vaadin/client/WidgetLoader.java | 35 - client/src/com/vaadin/client/WidgetMap.java | 74 - client/src/com/vaadin/client/WidgetSet.java | 154 - client/src/com/vaadin/client/WidgetUtil.java | 1821 ---- .../vaadin/client/annotations/OnStateChange.java | 50 - .../AbstractServerConnectorEvent.java | 45 - .../communication/AtmospherePushConnection.java | 634 -- .../communication/ConnectionStateHandler.java | 202 - .../client/communication/Date_Serializer.java | 45 - .../DefaultConnectionStateHandler.java | 595 -- .../communication/DefaultReconnectDialog.java | 126 - .../client/communication/DiffJSONSerializer.java | 32 - .../HasJavaScriptConnectorHelper.java | 23 - .../com/vaadin/client/communication/Heartbeat.java | 173 - .../client/communication/JSONSerializer.java | 72 - .../communication/JavaScriptMethodInvocation.java | 34 - .../vaadin/client/communication/JsonDecoder.java | 309 - .../vaadin/client/communication/JsonEncoder.java | 333 - .../client/communication/MessageHandler.java | 1815 ---- .../vaadin/client/communication/MessageSender.java | 412 - .../client/communication/PushConnection.java | 121 - .../client/communication/ReconnectDialog.java | 93 - .../vaadin/client/communication/RpcManager.java | 148 - .../com/vaadin/client/communication/RpcProxy.java | 71 - .../client/communication/ServerRpcQueue.java | 342 - .../client/communication/StateChangeEvent.java | 327 - .../communication/TranslatedURLReference.java | 48 - .../communication/URLReference_Serializer.java | 59 - .../vaadin/client/communication/XhrConnection.java | 278 - .../client/communication/XhrConnectionError.java | 106 - .../client/componentlocator/ComponentLocator.java | 253 - .../componentlocator/LegacyLocatorStrategy.java | 719 -- .../client/componentlocator/LocatorStrategy.java | 122 - .../client/componentlocator/LocatorUtil.java | 105 - .../client/componentlocator/SelectorPredicate.java | 228 - .../VaadinFinderLocatorStrategy.java | 750 -- .../connectors/AbstractRendererConnector.java | 182 - .../AbstractSelectionModelConnector.java | 82 - .../client/connectors/ButtonRendererConnector.java | 44 - .../connectors/ClickableRendererConnector.java | 62 - .../client/connectors/DateRendererConnector.java | 34 - .../DetailComponentManagerConnector.java | 36 - .../vaadin/client/connectors/GridConnector.java | 1294 --- .../client/connectors/ImageRendererConnector.java | 57 - .../connectors/JavaScriptRendererConnector.java | 280 - .../connectors/MultiSelectionModelConnector.java | 382 - .../connectors/NoSelectionModelConnector.java | 45 - .../client/connectors/NumberRendererConnector.java | 35 - .../connectors/ProgressBarRendererConnector.java | 35 - .../client/connectors/RpcDataSourceConnector.java | 253 - .../connectors/SingleSelectionModelConnector.java | 180 - .../client/connectors/TextRendererConnector.java | 34 - .../connectors/UnsafeHtmlRendererConnector.java | 43 - .../client/data/AbstractRemoteDataSource.java | 774 -- .../src/com/vaadin/client/data/CacheStrategy.java | 183 - .../com/vaadin/client/data/DataChangeHandler.java | 82 - client/src/com/vaadin/client/data/DataSource.java | 197 - .../client/debug/internal/AnalyzeLayoutsPanel.java | 269 - .../client/debug/internal/ConnectorInfoPanel.java | 113 - .../vaadin/client/debug/internal/DebugButton.java | 109 - .../debug/internal/ErrorNotificationHandler.java | 89 - .../client/debug/internal/HierarchyPanel.java | 178 - .../client/debug/internal/HierarchySection.java | 404 - .../vaadin/client/debug/internal/Highlight.java | 245 - .../src/com/vaadin/client/debug/internal/Icon.java | 68 - .../vaadin/client/debug/internal/InfoSection.java | 320 - .../vaadin/client/debug/internal/LogSection.java | 361 - .../client/debug/internal/NetworkSection.java | 98 - .../debug/internal/OptimizedWidgetsetPanel.java | 143 - .../client/debug/internal/ProfilerSection.java | 160 - .../com/vaadin/client/debug/internal/Section.java | 76 - .../debug/internal/SelectConnectorListener.java | 37 - .../vaadin/client/debug/internal/SelectorPath.java | 272 - .../client/debug/internal/TestBenchSection.java | 284 - .../vaadin/client/debug/internal/VDebugWindow.java | 1137 --- .../debug/internal/theme/DebugWindowStyles.java | 49 - .../client/debug/internal/theme/debugwindow.css | 322 - .../vaadin/client/debug/internal/theme/font.eot | Bin 7752 -> 0 bytes .../vaadin/client/debug/internal/theme/font.svg | 37 - .../vaadin/client/debug/internal/theme/font.ttf | Bin 7588 -> 0 bytes .../vaadin/client/debug/internal/theme/font.woff | Bin 7664 -> 0 bytes .../vaadin/client/event/PointerCancelEvent.java | 62 - .../vaadin/client/event/PointerCancelHandler.java | 34 - .../com/vaadin/client/event/PointerDownEvent.java | 61 - .../vaadin/client/event/PointerDownHandler.java | 34 - .../src/com/vaadin/client/event/PointerEvent.java | 173 - .../vaadin/client/event/PointerEventSupport.java | 55 - .../client/event/PointerEventSupportImpl.java | 51 - .../client/event/PointerEventSupportImplIE10.java | 34 - .../event/PointerEventSupportImplModernIE.java | 72 - .../com/vaadin/client/event/PointerMoveEvent.java | 61 - .../vaadin/client/event/PointerMoveHandler.java | 34 - .../com/vaadin/client/event/PointerUpEvent.java | 61 - .../com/vaadin/client/event/PointerUpHandler.java | 34 - .../extensions/AbstractExtensionConnector.java | 53 - .../extensions/BrowserWindowOpenerConnector.java | 94 - .../client/extensions/FileDownloaderConnector.java | 85 - .../client/extensions/ResponsiveConnector.java | 449 - .../JavaScriptManagerConnector.java | 144 - .../vaadin/client/metadata/AsyncBundleLoader.java | 103 - .../vaadin/client/metadata/BundleLoadCallback.java | 22 - .../client/metadata/ConnectorBundleLoader.java | 209 - .../vaadin/client/metadata/InvokationHandler.java | 21 - client/src/com/vaadin/client/metadata/Invoker.java | 20 - .../com/vaadin/client/metadata/JsniInvoker.java | 53 - client/src/com/vaadin/client/metadata/Method.java | 117 - .../vaadin/client/metadata/NoDataException.java | 25 - .../client/metadata/OnStateChangeMethod.java | 111 - .../src/com/vaadin/client/metadata/Property.java | 143 - .../com/vaadin/client/metadata/ProxyHandler.java | 23 - client/src/com/vaadin/client/metadata/Type.java | 151 - .../src/com/vaadin/client/metadata/TypeData.java | 31 - .../com/vaadin/client/metadata/TypeDataStore.java | 488 -- .../vaadin/client/renderers/ButtonRenderer.java | 44 - .../vaadin/client/renderers/ClickableRenderer.java | 233 - .../vaadin/client/renderers/ComplexRenderer.java | 157 - .../com/vaadin/client/renderers/DateRenderer.java | 108 - .../com/vaadin/client/renderers/HtmlRenderer.java | 41 - .../com/vaadin/client/renderers/ImageRenderer.java | 49 - .../vaadin/client/renderers/NumberRenderer.java | 71 - .../client/renderers/ProgressBarRenderer.java | 47 - .../src/com/vaadin/client/renderers/Renderer.java | 54 - .../com/vaadin/client/renderers/TextRenderer.java | 32 - .../vaadin/client/renderers/WidgetRenderer.java | 111 - .../client/ui/AbstractClickEventHandler.java | 246 - .../client/ui/AbstractComponentConnector.java | 793 -- .../ui/AbstractComponentContainerConnector.java | 26 - .../com/vaadin/client/ui/AbstractConnector.java | 508 -- .../vaadin/client/ui/AbstractFieldConnector.java | 62 - .../client/ui/AbstractHasComponentsConnector.java | 71 - .../vaadin/client/ui/AbstractLayoutConnector.java | 27 - .../AbstractSingleComponentContainerConnector.java | 61 - client/src/com/vaadin/client/ui/Action.java | 83 - client/src/com/vaadin/client/ui/ActionOwner.java | 31 - client/src/com/vaadin/client/ui/CalendarEntry.java | 140 - .../com/vaadin/client/ui/ClickEventHandler.java | 62 - .../client/ui/ConnectorFocusAndBlurHandler.java | 87 - client/src/com/vaadin/client/ui/Field.java | 28 - .../com/vaadin/client/ui/FocusElementPanel.java | 87 - client/src/com/vaadin/client/ui/FocusUtil.java | 94 - .../com/vaadin/client/ui/FocusableFlexTable.java | 122 - .../com/vaadin/client/ui/FocusableFlowPanel.java | 117 - .../com/vaadin/client/ui/FocusableScrollPanel.java | 206 - client/src/com/vaadin/client/ui/FontIcon.java | 149 - client/src/com/vaadin/client/ui/Icon.java | 59 - client/src/com/vaadin/client/ui/ImageIcon.java | 76 - .../client/ui/JavaScriptComponentConnector.java | 63 - .../src/com/vaadin/client/ui/JavaScriptWidget.java | 37 - .../vaadin/client/ui/JsniMousewheelHandler.java | 80 - .../vaadin/client/ui/LayoutClickEventHandler.java | 52 - .../src/com/vaadin/client/ui/LegacyConnector.java | 35 - client/src/com/vaadin/client/ui/ManagedLayout.java | 22 - .../com/vaadin/client/ui/MediaBaseConnector.java | 92 - .../com/vaadin/client/ui/PostLayoutListener.java | 38 - .../vaadin/client/ui/ShortcutActionHandler.java | 312 - .../com/vaadin/client/ui/SimpleFocusablePanel.java | 91 - .../com/vaadin/client/ui/SimpleManagedLayout.java | 20 - client/src/com/vaadin/client/ui/SubPartAware.java | 60 - .../com/vaadin/client/ui/TouchScrollDelegate.java | 723 -- client/src/com/vaadin/client/ui/TreeAction.java | 68 - .../client/ui/UnknownComponentConnector.java | 47 - .../src/com/vaadin/client/ui/VAbsoluteLayout.java | 507 -- .../com/vaadin/client/ui/VAbstractSplitPanel.java | 867 -- client/src/com/vaadin/client/ui/VAccordion.java | 471 -- client/src/com/vaadin/client/ui/VAudio.java | 33 - client/src/com/vaadin/client/ui/VBrowserFrame.java | 157 - client/src/com/vaadin/client/ui/VButton.java | 482 -- client/src/com/vaadin/client/ui/VCalendar.java | 1500 ---- .../src/com/vaadin/client/ui/VCalendarPanel.java | 2258 ----- client/src/com/vaadin/client/ui/VCheckBox.java | 108 - client/src/com/vaadin/client/ui/VColorPicker.java | 86 - .../src/com/vaadin/client/ui/VColorPickerArea.java | 199 - client/src/com/vaadin/client/ui/VContextMenu.java | 331 - client/src/com/vaadin/client/ui/VCssLayout.java | 68 - .../src/com/vaadin/client/ui/VCustomComponent.java | 30 - client/src/com/vaadin/client/ui/VCustomLayout.java | 447 - client/src/com/vaadin/client/ui/VDateField.java | 229 - .../com/vaadin/client/ui/VDateFieldCalendar.java | 129 - .../com/vaadin/client/ui/VDragAndDropWrapper.java | 730 -- .../vaadin/client/ui/VDragAndDropWrapperIE.java | 95 - client/src/com/vaadin/client/ui/VEmbedded.java | 263 - client/src/com/vaadin/client/ui/VFilterSelect.java | 2351 ------ client/src/com/vaadin/client/ui/VFlash.java | 250 - client/src/com/vaadin/client/ui/VForm.java | 139 - client/src/com/vaadin/client/ui/VFormLayout.java | 392 - client/src/com/vaadin/client/ui/VGridLayout.java | 925 -- .../com/vaadin/client/ui/VHorizontalLayout.java | 42 - client/src/com/vaadin/client/ui/VImage.java | 27 - client/src/com/vaadin/client/ui/VLabel.java | 72 - client/src/com/vaadin/client/ui/VLazyExecutor.java | 64 - client/src/com/vaadin/client/ui/VLink.java | 148 - client/src/com/vaadin/client/ui/VListSelect.java | 154 - client/src/com/vaadin/client/ui/VMediaBase.java | 90 - client/src/com/vaadin/client/ui/VMenuBar.java | 1718 ---- client/src/com/vaadin/client/ui/VNativeButton.java | 162 - client/src/com/vaadin/client/ui/VNativeSelect.java | 154 - client/src/com/vaadin/client/ui/VNotification.java | 683 -- client/src/com/vaadin/client/ui/VOptionGroup.java | 311 - .../src/com/vaadin/client/ui/VOptionGroupBase.java | 218 - client/src/com/vaadin/client/ui/VOverlay.java | 177 - client/src/com/vaadin/client/ui/VPanel.java | 204 - .../src/com/vaadin/client/ui/VPasswordField.java | 33 - .../src/com/vaadin/client/ui/VPopupCalendar.java | 737 -- client/src/com/vaadin/client/ui/VPopupView.java | 466 - client/src/com/vaadin/client/ui/VProgressBar.java | 101 - .../com/vaadin/client/ui/VProgressIndicator.java | 34 - client/src/com/vaadin/client/ui/VRichTextArea.java | 363 - client/src/com/vaadin/client/ui/VScrollTable.java | 8370 ------------------ client/src/com/vaadin/client/ui/VSlider.java | 675 -- .../vaadin/client/ui/VSplitPanelHorizontal.java | 49 - .../com/vaadin/client/ui/VSplitPanelVertical.java | 49 - client/src/com/vaadin/client/ui/VTabsheet.java | 1998 ----- client/src/com/vaadin/client/ui/VTabsheetBase.java | 201 - .../src/com/vaadin/client/ui/VTabsheetPanel.java | 202 - client/src/com/vaadin/client/ui/VTextArea.java | 336 - client/src/com/vaadin/client/ui/VTextField.java | 531 -- client/src/com/vaadin/client/ui/VTextualDate.java | 410 - client/src/com/vaadin/client/ui/VTree.java | 2262 ----- client/src/com/vaadin/client/ui/VTreeTable.java | 904 -- .../src/com/vaadin/client/ui/VTwinColSelect.java | 630 -- client/src/com/vaadin/client/ui/VUI.java | 508 -- .../com/vaadin/client/ui/VUnknownComponent.java | 40 - client/src/com/vaadin/client/ui/VUpload.java | 393 - .../src/com/vaadin/client/ui/VVerticalLayout.java | 42 - client/src/com/vaadin/client/ui/VVideo.java | 70 - client/src/com/vaadin/client/ui/VWindow.java | 1487 ---- .../ui/absolutelayout/AbsoluteLayoutConnector.java | 271 - .../client/ui/accordion/AccordionConnector.java | 144 - .../src/com/vaadin/client/ui/aria/AriaHelper.java | 189 - .../vaadin/client/ui/aria/HandlesAriaCaption.java | 37 - .../vaadin/client/ui/aria/HandlesAriaInvalid.java | 33 - .../vaadin/client/ui/aria/HandlesAriaRequired.java | 32 - .../com/vaadin/client/ui/audio/AudioConnector.java | 78 - .../ui/browserframe/BrowserFrameConnector.java | 49 - .../vaadin/client/ui/button/ButtonConnector.java | 131 - .../client/ui/calendar/CalendarConnector.java | 707 -- .../vaadin/client/ui/calendar/VCalendarAction.java | 138 - .../client/ui/calendar/schedule/CalendarDay.java | 61 - .../client/ui/calendar/schedule/CalendarEvent.java | 319 - .../client/ui/calendar/schedule/DateCell.java | 853 -- .../ui/calendar/schedule/DateCellContainer.java | 117 - .../ui/calendar/schedule/DateCellDayEvent.java | 657 -- .../client/ui/calendar/schedule/DateCellGroup.java | 59 - .../client/ui/calendar/schedule/DateUtil.java | 70 - .../client/ui/calendar/schedule/DayToolbar.java | 179 - .../calendar/schedule/FocusableComplexPanel.java | 122 - .../client/ui/calendar/schedule/FocusableGrid.java | 134 - .../client/ui/calendar/schedule/FocusableHTML.java | 124 - .../client/ui/calendar/schedule/HasTooltipKey.java | 33 - .../ui/calendar/schedule/MonthEventLabel.java | 173 - .../client/ui/calendar/schedule/MonthGrid.java | 215 - .../client/ui/calendar/schedule/SimpleDayCell.java | 734 -- .../ui/calendar/schedule/SimpleDayToolbar.java | 97 - .../ui/calendar/schedule/SimpleWeekToolbar.java | 109 - .../client/ui/calendar/schedule/WeekGrid.java | 688 -- .../calendar/schedule/WeekGridMinuteTimeRange.java | 62 - .../client/ui/calendar/schedule/WeekLabel.java | 51 - .../ui/calendar/schedule/WeeklyLongEvents.java | 189 - .../schedule/WeeklyLongEventsDateCell.java | 67 - .../calendar/schedule/dd/CalendarDropHandler.java | 66 - .../schedule/dd/CalendarMonthDropHandler.java | 171 - .../schedule/dd/CalendarWeekDropHandler.java | 186 - .../client/ui/checkbox/CheckBoxConnector.java | 155 - .../colorpicker/AbstractColorPickerConnector.java | 114 - .../ui/colorpicker/ColorPickerAreaConnector.java | 67 - .../ui/colorpicker/ColorPickerConnector.java | 69 - .../colorpicker/ColorPickerGradientConnector.java | 85 - .../ui/colorpicker/ColorPickerGridConnector.java | 94 - .../ui/colorpicker/VColorPickerGradient.java | 209 - .../client/ui/colorpicker/VColorPickerGrid.java | 147 - .../client/ui/combobox/ComboBoxConnector.java | 350 - .../client/ui/csslayout/CssLayoutConnector.java | 203 - .../customcomponent/CustomComponentConnector.java | 50 - .../ui/customfield/CustomFieldConnector.java | 123 - .../ui/customlayout/CustomLayoutConnector.java | 159 - .../ui/datefield/AbstractDateFieldConnector.java | 125 - .../client/ui/datefield/DateFieldConnector.java | 218 - .../ui/datefield/InlineDateFieldConnector.java | 130 - .../ui/datefield/PopupDateFieldConnector.java | 25 - .../client/ui/datefield/TextualDateConnector.java | 67 - .../vaadin/client/ui/dd/DDEventHandleStrategy.java | 418 - client/src/com/vaadin/client/ui/dd/DDUtil.java | 81 - .../vaadin/client/ui/dd/DragAndDropHandler.java | 257 - client/src/com/vaadin/client/ui/dd/DragHandle.java | 219 - .../com/vaadin/client/ui/dd/DragImageModifier.java | 40 - .../vaadin/client/ui/dd/VAbstractDropHandler.java | 154 - client/src/com/vaadin/client/ui/dd/VAcceptAll.java | 32 - .../com/vaadin/client/ui/dd/VAcceptCallback.java | 29 - .../com/vaadin/client/ui/dd/VAcceptCriteria.java | 34 - .../com/vaadin/client/ui/dd/VAcceptCriterion.java | 57 - .../client/ui/dd/VAcceptCriterionFactory.java | 25 - client/src/com/vaadin/client/ui/dd/VAnd.java | 54 - .../vaadin/client/ui/dd/VContainsDataFlavor.java | 33 - .../vaadin/client/ui/dd/VDragAndDropManager.java | 761 -- client/src/com/vaadin/client/ui/dd/VDragEvent.java | 316 - .../client/ui/dd/VDragEventServerCallback.java | 24 - .../src/com/vaadin/client/ui/dd/VDragSourceIs.java | 54 - .../src/com/vaadin/client/ui/dd/VDropHandler.java | 85 - .../com/vaadin/client/ui/dd/VHasDropHandler.java | 29 - .../com/vaadin/client/ui/dd/VHtml5DragEvent.java | 88 - client/src/com/vaadin/client/ui/dd/VHtml5File.java | 49 - client/src/com/vaadin/client/ui/dd/VIsOverId.java | 54 - client/src/com/vaadin/client/ui/dd/VItemIdIs.java | 50 - .../client/ui/dd/VLazyInitItemIdentifiers.java | 93 - client/src/com/vaadin/client/ui/dd/VNot.java | 76 - client/src/com/vaadin/client/ui/dd/VOr.java | 62 - .../src/com/vaadin/client/ui/dd/VOverTreeNode.java | 31 - .../src/com/vaadin/client/ui/dd/VServerAccept.java | 51 - .../com/vaadin/client/ui/dd/VSourceIsTarget.java | 37 - .../com/vaadin/client/ui/dd/VTargetDetailIs.java | 51 - .../com/vaadin/client/ui/dd/VTargetInSubtree.java | 56 - .../src/com/vaadin/client/ui/dd/VTransferable.java | 81 - .../ui/doc-files/IOrderedLayout_alignment.png | Bin 14779 -> 0 bytes ...OrderedLayout_component_handles_the_caption.png | Bin 5676 -> 0 bytes .../client/ui/doc-files/IOrderedLayout_h150.png | Bin 9802 -> 0 bytes .../ui/doc-files/IOrderedLayout_horizontal.png | Bin 5769 -> 0 bytes .../IOrderedLayout_horizontal_spacing.png | Bin 5862 -> 0 bytes .../client/ui/doc-files/IOrderedLayout_margin.png | Bin 6828 -> 0 bytes .../ui/doc-files/IOrderedLayout_no_caption.png | Bin 2113 -> 0 bytes .../ui/doc-files/IOrderedLayout_normal_caption.png | Bin 3571 -> 0 bytes .../ui/doc-files/IOrderedLayout_special-margin.png | Bin 3763 -> 0 bytes .../ui/doc-files/IOrderedLayout_vertical.png | Bin 6140 -> 0 bytes .../doc-files/IOrderedLayout_vertical_spacing.png | Bin 6652 -> 0 bytes .../client/ui/doc-files/IOrderedLayout_w300.png | Bin 9543 -> 0 bytes .../ui/doc-files/IOrderedLayout_w300_h150.png | Bin 10067 -> 0 bytes .../DragAndDropWrapperConnector.java | 124 - .../client/ui/embedded/EmbeddedConnector.java | 274 - .../com/vaadin/client/ui/flash/FlashConnector.java | 55 - .../com/vaadin/client/ui/form/FormConnector.java | 236 - .../client/ui/formlayout/FormLayoutConnector.java | 294 - .../client/ui/gridlayout/GridLayoutConnector.java | 227 - .../com/vaadin/client/ui/image/ImageConnector.java | 82 - .../com/vaadin/client/ui/label/LabelConnector.java | 82 - .../ui/layout/ComponentConnectorLayoutSlot.java | 111 - .../client/ui/layout/ElementResizeEvent.java | 37 - .../client/ui/layout/ElementResizeListener.java | 21 - .../client/ui/layout/LayoutDependencyTree.java | 763 -- .../src/com/vaadin/client/ui/layout/Margins.java | 98 - .../vaadin/client/ui/layout/MayScrollChildren.java | 22 - .../com/vaadin/client/ui/layout/VLayoutSlot.java | 306 - .../com/vaadin/client/ui/link/LinkConnector.java | 103 - .../client/ui/listselect/ListSelectConnector.java | 31 - .../src/com/vaadin/client/ui/menubar/MenuBar.java | 637 -- .../vaadin/client/ui/menubar/MenuBarConnector.java | 223 - .../src/com/vaadin/client/ui/menubar/MenuItem.java | 205 - .../ui/nativebutton/NativeButtonConnector.java | 98 - .../ui/nativeselect/NativeSelectConnector.java | 38 - .../ui/optiongroup/OptionGroupBaseConnector.java | 117 - .../ui/optiongroup/OptionGroupConnector.java | 87 - .../AbstractOrderedLayoutConnector.java | 710 -- .../client/ui/orderedlayout/CaptionPosition.java | 24 - .../orderedlayout/HorizontalLayoutConnector.java | 48 - .../com/vaadin/client/ui/orderedlayout/Slot.java | 832 -- .../ui/orderedlayout/VAbstractOrderedLayout.java | 742 -- .../ui/orderedlayout/VerticalLayoutConnector.java | 47 - .../com/vaadin/client/ui/panel/PanelConnector.java | 256 - .../ui/passwordfield/PasswordFieldConnector.java | 31 - .../client/ui/popupview/PopupViewConnector.java | 145 - .../client/ui/popupview/VisibilityChangeEvent.java | 50 - .../ui/popupview/VisibilityChangeHandler.java | 23 - .../ui/progressindicator/ProgressBarConnector.java | 56 - .../ProgressIndicatorConnector.java | 75 - .../ui/richtextarea/RichTextAreaConnector.java | 144 - .../VRichTextToolbar$Strings.properties | 35 - .../client/ui/richtextarea/VRichTextToolbar.java | 476 -- .../vaadin/client/ui/richtextarea/backColors.gif | Bin 104 -> 0 bytes .../src/com/vaadin/client/ui/richtextarea/bold.gif | Bin 900 -> 0 bytes .../vaadin/client/ui/richtextarea/createLink.gif | Bin 954 -> 0 bytes .../vaadin/client/ui/richtextarea/fontSizes.gif | Bin 96 -> 0 bytes .../com/vaadin/client/ui/richtextarea/fonts.gif | Bin 147 -> 0 bytes .../vaadin/client/ui/richtextarea/foreColors.gif | Bin 173 -> 0 bytes .../com/vaadin/client/ui/richtextarea/gwtLogo.png | Bin 11454 -> 0 bytes .../src/com/vaadin/client/ui/richtextarea/hr.gif | Bin 853 -> 0 bytes .../com/vaadin/client/ui/richtextarea/indent.gif | Bin 76 -> 0 bytes .../vaadin/client/ui/richtextarea/insertImage.gif | Bin 946 -> 0 bytes .../com/vaadin/client/ui/richtextarea/italic.gif | Bin 190 -> 0 bytes .../client/ui/richtextarea/justifyCenter.gif | Bin 70 -> 0 bytes .../vaadin/client/ui/richtextarea/justifyLeft.gif | Bin 71 -> 0 bytes .../vaadin/client/ui/richtextarea/justifyRight.gif | Bin 70 -> 0 bytes .../src/com/vaadin/client/ui/richtextarea/ol.gif | Bin 204 -> 0 bytes .../com/vaadin/client/ui/richtextarea/outdent.gif | Bin 76 -> 0 bytes .../vaadin/client/ui/richtextarea/removeFormat.gif | Bin 962 -> 0 bytes .../vaadin/client/ui/richtextarea/removeLink.gif | Bin 585 -> 0 bytes .../client/ui/richtextarea/strikeThrough.gif | Bin 915 -> 0 bytes .../vaadin/client/ui/richtextarea/subscript.gif | Bin 933 -> 0 bytes .../vaadin/client/ui/richtextarea/superscript.gif | Bin 232 -> 0 bytes .../src/com/vaadin/client/ui/richtextarea/ul.gif | Bin 133 -> 0 bytes .../vaadin/client/ui/richtextarea/underline.gif | Bin 914 -> 0 bytes .../vaadin/client/ui/slider/SliderConnector.java | 97 - .../ui/splitpanel/AbstractSplitPanelConnector.java | 268 - .../splitpanel/HorizontalSplitPanelConnector.java | 37 - .../ui/splitpanel/VerticalSplitPanelConnector.java | 37 - .../com/vaadin/client/ui/table/TableConnector.java | 556 -- .../client/ui/tabsheet/TabsheetBaseConnector.java | 118 - .../client/ui/tabsheet/TabsheetConnector.java | 192 - .../client/ui/textarea/TextAreaConnector.java | 91 - .../client/ui/textfield/TextFieldConnector.java | 129 - .../com/vaadin/client/ui/tree/TreeConnector.java | 400 - .../client/ui/treetable/TreeTableConnector.java | 151 - .../ui/twincolselect/TwinColSelectConnector.java | 84 - .../src/com/vaadin/client/ui/ui/UIConnector.java | 1126 --- .../vaadin/client/ui/upload/UploadConnector.java | 112 - .../ui/upload/UploadIFrameOnloadStrategy.java | 39 - .../ui/upload/UploadIFrameOnloadStrategyIE.java | 42 - .../com/vaadin/client/ui/video/VideoConnector.java | 50 - .../vaadin/client/ui/window/WindowConnector.java | 512 -- .../vaadin/client/ui/window/WindowMoveEvent.java | 82 - .../vaadin/client/ui/window/WindowMoveHandler.java | 35 - .../com/vaadin/client/widget/escalator/Cell.java | 85 - .../widget/escalator/ColumnConfiguration.java | 196 - .../client/widget/escalator/EscalatorUpdater.java | 155 - .../client/widget/escalator/FlyweightCell.java | 201 - .../client/widget/escalator/FlyweightRow.java | 294 - .../client/widget/escalator/PositionFunction.java | 118 - .../com/vaadin/client/widget/escalator/Row.java | 48 - .../client/widget/escalator/RowContainer.java | 281 - .../widget/escalator/RowVisibilityChangeEvent.java | 99 - .../escalator/RowVisibilityChangeHandler.java | 38 - .../client/widget/escalator/ScrollbarBundle.java | 866 -- .../com/vaadin/client/widget/escalator/Spacer.java | 47 - .../client/widget/escalator/SpacerUpdater.java | 64 - .../vaadin/client/widget/grid/AutoScroller.java | 643 -- .../vaadin/client/widget/grid/CellReference.java | 151 - .../client/widget/grid/CellStyleGenerator.java | 40 - .../client/widget/grid/DataAvailableEvent.java | 55 - .../client/widget/grid/DataAvailableHandler.java | 37 - .../widget/grid/DefaultEditorEventHandler.java | 328 - .../client/widget/grid/DetailsGenerator.java | 46 - .../vaadin/client/widget/grid/EditorHandler.java | 174 - .../client/widget/grid/EventCellReference.java | 128 - .../widget/grid/HeightAwareDetailsGenerator.java | 45 - .../client/widget/grid/RendererCellReference.java | 93 - .../vaadin/client/widget/grid/RowReference.java | 104 - .../client/widget/grid/RowStyleGenerator.java | 40 - .../widget/grid/datasources/ListDataSource.java | 478 -- .../client/widget/grid/datasources/ListSorter.java | 175 - .../grid/events/AbstractGridKeyEventHandler.java | 44 - .../grid/events/AbstractGridMouseEventHandler.java | 39 - .../widget/grid/events/BodyClickHandler.java | 28 - .../widget/grid/events/BodyDoubleClickHandler.java | 29 - .../widget/grid/events/BodyKeyDownHandler.java | 28 - .../widget/grid/events/BodyKeyPressHandler.java | 28 - .../widget/grid/events/BodyKeyUpHandler.java | 28 - .../widget/grid/events/ColumnReorderEvent.java | 51 - .../widget/grid/events/ColumnReorderHandler.java | 40 - .../widget/grid/events/ColumnResizeEvent.java | 67 - .../widget/grid/events/ColumnResizeHandler.java | 40 - .../grid/events/ColumnVisibilityChangeEvent.java | 93 - .../grid/events/ColumnVisibilityChangeHandler.java | 39 - .../widget/grid/events/FooterClickHandler.java | 28 - .../grid/events/FooterDoubleClickHandler.java | 29 - .../widget/grid/events/FooterKeyDownHandler.java | 28 - .../widget/grid/events/FooterKeyPressHandler.java | 28 - .../widget/grid/events/FooterKeyUpHandler.java | 28 - .../client/widget/grid/events/GridClickEvent.java | 50 - .../widget/grid/events/GridDoubleClickEvent.java | 52 - .../widget/grid/events/GridKeyDownEvent.java | 121 - .../widget/grid/events/GridKeyPressEvent.java | 74 - .../client/widget/grid/events/GridKeyUpEvent.java | 121 - .../widget/grid/events/HeaderClickHandler.java | 28 - .../grid/events/HeaderDoubleClickHandler.java | 29 - .../widget/grid/events/HeaderKeyDownHandler.java | 28 - .../widget/grid/events/HeaderKeyPressHandler.java | 28 - .../widget/grid/events/HeaderKeyUpHandler.java | 28 - .../client/widget/grid/events/ScrollEvent.java | 40 - .../client/widget/grid/events/ScrollHandler.java | 35 - .../client/widget/grid/events/SelectAllEvent.java | 59 - .../widget/grid/events/SelectAllHandler.java | 37 - .../selection/AbstractRowHandleSelectionModel.java | 66 - .../widget/grid/selection/ClickSelectHandler.java | 77 - .../grid/selection/HasSelectionHandlers.java | 42 - .../grid/selection/MultiSelectionRenderer.java | 763 -- .../widget/grid/selection/SelectionEvent.java | 178 - .../widget/grid/selection/SelectionHandler.java | 39 - .../widget/grid/selection/SelectionModel.java | 258 - .../widget/grid/selection/SelectionModelMulti.java | 273 - .../widget/grid/selection/SelectionModelNone.java | 73 - .../grid/selection/SelectionModelSingle.java | 175 - .../widget/grid/selection/SpaceSelectHandler.java | 137 - .../com/vaadin/client/widget/grid/sort/Sort.java | 154 - .../vaadin/client/widget/grid/sort/SortEvent.java | 113 - .../client/widget/grid/sort/SortHandler.java | 38 - .../vaadin/client/widget/grid/sort/SortOrder.java | 90 - .../src/com/vaadin/client/widgets/Escalator.java | 6697 --------------- client/src/com/vaadin/client/widgets/Grid.java | 8877 -------------------- client/src/com/vaadin/client/widgets/Overlay.java | 1012 --- .../vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml | 9 - .../main/java/com/vaadin/client/AnimationUtil.java | 179 + .../vaadin/client/ApplicationConfiguration.java | 876 ++ .../com/vaadin/client/ApplicationConnection.java | 1642 ++++ .../main/java/com/vaadin/client/BrowserInfo.java | 511 ++ .../src/main/java/com/vaadin/client/CSSRule.java | 132 + .../java/com/vaadin/client/ComponentConnector.java | 161 + .../java/com/vaadin/client/ComponentDetail.java | 78 + .../java/com/vaadin/client/ComponentDetailMap.java | 99 + .../main/java/com/vaadin/client/ComputedStyle.java | 362 + .../client/ConnectorHierarchyChangeEvent.java | 103 + .../main/java/com/vaadin/client/ConnectorMap.java | 306 + .../vaadin/client/ContainerResizedListener.java | 40 + .../java/com/vaadin/client/DateTimeService.java | 504 ++ .../java/com/vaadin/client/DeferredWorker.java | 36 + .../vaadin/client/DirectionalManagedLayout.java | 24 + .../main/java/com/vaadin/client/EventHelper.java | 147 + .../main/java/com/vaadin/client/FastStringMap.java | 74 + .../main/java/com/vaadin/client/FastStringSet.java | 101 + .../src/main/java/com/vaadin/client/Focusable.java | 31 + .../client/HasChildMeasurementHintConnector.java | 72 + .../com/vaadin/client/HasComponentsConnector.java | 94 + .../vaadin/client/JavaScriptConnectorHelper.java | 511 ++ .../com/vaadin/client/JavaScriptExtension.java | 58 + .../main/java/com/vaadin/client/JsArrayObject.java | 42 + .../main/java/com/vaadin/client/LayoutManager.java | 1836 ++++ .../java/com/vaadin/client/LayoutManagerIE8.java | 115 + .../vaadin/client/LocaleNotLoadedException.java | 25 + .../main/java/com/vaadin/client/LocaleService.java | 154 + .../main/java/com/vaadin/client/MeasuredSize.java | 299 + .../vaadin/client/MouseEventDetailsBuilder.java | 96 + .../src/main/java/com/vaadin/client/Paintable.java | 31 + .../src/main/java/com/vaadin/client/Profiler.java | 719 ++ .../java/com/vaadin/client/RenderInformation.java | 165 + .../main/java/com/vaadin/client/RenderSpace.java | 68 + .../java/com/vaadin/client/ResourceLoader.java | 612 ++ .../java/com/vaadin/client/ServerConnector.java | 219 + .../main/java/com/vaadin/client/SimpleTree.java | 192 + .../java/com/vaadin/client/StyleConstants.java | 47 + .../main/java/com/vaadin/client/SuperDevMode.java | 275 + .../main/java/com/vaadin/client/TooltipInfo.java | 87 + client/src/main/java/com/vaadin/client/UIDL.java | 563 ++ client/src/main/java/com/vaadin/client/Util.java | 1273 +++ .../src/main/java/com/vaadin/client/VCaption.java | 776 ++ .../java/com/vaadin/client/VCaptionWrapper.java | 51 + .../src/main/java/com/vaadin/client/VConsole.java | 103 + .../main/java/com/vaadin/client/VErrorMessage.java | 104 + .../java/com/vaadin/client/VLoadingIndicator.java | 253 + .../java/com/vaadin/client/VSchedulerImpl.java | 45 + .../src/main/java/com/vaadin/client/VTooltip.java | 786 ++ .../main/java/com/vaadin/client/VUIDLBrowser.java | 368 + .../src/main/java/com/vaadin/client/ValueMap.java | 121 + .../java/com/vaadin/client/WidgetInstantiator.java | 23 + .../main/java/com/vaadin/client/WidgetLoader.java | 35 + .../src/main/java/com/vaadin/client/WidgetMap.java | 74 + .../src/main/java/com/vaadin/client/WidgetSet.java | 154 + .../main/java/com/vaadin/client/WidgetUtil.java | 1821 ++++ .../vaadin/client/annotations/OnStateChange.java | 50 + .../AbstractServerConnectorEvent.java | 45 + .../communication/AtmospherePushConnection.java | 634 ++ .../communication/ConnectionStateHandler.java | 202 + .../client/communication/Date_Serializer.java | 45 + .../DefaultConnectionStateHandler.java | 595 ++ .../communication/DefaultReconnectDialog.java | 126 + .../client/communication/DiffJSONSerializer.java | 32 + .../HasJavaScriptConnectorHelper.java | 23 + .../com/vaadin/client/communication/Heartbeat.java | 173 + .../client/communication/JSONSerializer.java | 72 + .../communication/JavaScriptMethodInvocation.java | 34 + .../vaadin/client/communication/JsonDecoder.java | 309 + .../vaadin/client/communication/JsonEncoder.java | 333 + .../client/communication/MessageHandler.java | 1815 ++++ .../vaadin/client/communication/MessageSender.java | 412 + .../client/communication/PushConnection.java | 121 + .../client/communication/ReconnectDialog.java | 93 + .../vaadin/client/communication/RpcManager.java | 148 + .../com/vaadin/client/communication/RpcProxy.java | 71 + .../client/communication/ServerRpcQueue.java | 342 + .../client/communication/StateChangeEvent.java | 327 + .../communication/TranslatedURLReference.java | 48 + .../communication/URLReference_Serializer.java | 59 + .../vaadin/client/communication/XhrConnection.java | 278 + .../client/communication/XhrConnectionError.java | 106 + .../client/componentlocator/ComponentLocator.java | 253 + .../componentlocator/LegacyLocatorStrategy.java | 719 ++ .../client/componentlocator/LocatorStrategy.java | 122 + .../client/componentlocator/LocatorUtil.java | 105 + .../client/componentlocator/SelectorPredicate.java | 228 + .../VaadinFinderLocatorStrategy.java | 750 ++ .../connectors/AbstractRendererConnector.java | 182 + .../AbstractSelectionModelConnector.java | 82 + .../client/connectors/ButtonRendererConnector.java | 44 + .../connectors/ClickableRendererConnector.java | 62 + .../client/connectors/DateRendererConnector.java | 34 + .../DetailComponentManagerConnector.java | 36 + .../vaadin/client/connectors/GridConnector.java | 1294 +++ .../client/connectors/ImageRendererConnector.java | 57 + .../connectors/JavaScriptRendererConnector.java | 280 + .../connectors/MultiSelectionModelConnector.java | 382 + .../connectors/NoSelectionModelConnector.java | 45 + .../client/connectors/NumberRendererConnector.java | 35 + .../connectors/ProgressBarRendererConnector.java | 35 + .../client/connectors/RpcDataSourceConnector.java | 253 + .../connectors/SingleSelectionModelConnector.java | 180 + .../client/connectors/TextRendererConnector.java | 34 + .../connectors/UnsafeHtmlRendererConnector.java | 43 + .../client/data/AbstractRemoteDataSource.java | 774 ++ .../java/com/vaadin/client/data/CacheStrategy.java | 183 + .../com/vaadin/client/data/DataChangeHandler.java | 82 + .../java/com/vaadin/client/data/DataSource.java | 197 + .../client/debug/internal/AnalyzeLayoutsPanel.java | 269 + .../client/debug/internal/ConnectorInfoPanel.java | 113 + .../vaadin/client/debug/internal/DebugButton.java | 109 + .../debug/internal/ErrorNotificationHandler.java | 89 + .../client/debug/internal/HierarchyPanel.java | 178 + .../client/debug/internal/HierarchySection.java | 404 + .../vaadin/client/debug/internal/Highlight.java | 245 + .../com/vaadin/client/debug/internal/Icon.java | 68 + .../vaadin/client/debug/internal/InfoSection.java | 320 + .../vaadin/client/debug/internal/LogSection.java | 361 + .../client/debug/internal/NetworkSection.java | 98 + .../debug/internal/OptimizedWidgetsetPanel.java | 143 + .../client/debug/internal/ProfilerSection.java | 160 + .../com/vaadin/client/debug/internal/Section.java | 76 + .../debug/internal/SelectConnectorListener.java | 37 + .../vaadin/client/debug/internal/SelectorPath.java | 272 + .../client/debug/internal/TestBenchSection.java | 284 + .../vaadin/client/debug/internal/VDebugWindow.java | 1137 +++ .../debug/internal/theme/DebugWindowStyles.java | 49 + .../vaadin/client/event/PointerCancelEvent.java | 62 + .../vaadin/client/event/PointerCancelHandler.java | 34 + .../com/vaadin/client/event/PointerDownEvent.java | 61 + .../vaadin/client/event/PointerDownHandler.java | 34 + .../java/com/vaadin/client/event/PointerEvent.java | 173 + .../vaadin/client/event/PointerEventSupport.java | 55 + .../client/event/PointerEventSupportImpl.java | 51 + .../client/event/PointerEventSupportImplIE10.java | 34 + .../event/PointerEventSupportImplModernIE.java | 72 + .../com/vaadin/client/event/PointerMoveEvent.java | 61 + .../vaadin/client/event/PointerMoveHandler.java | 34 + .../com/vaadin/client/event/PointerUpEvent.java | 61 + .../com/vaadin/client/event/PointerUpHandler.java | 34 + .../extensions/AbstractExtensionConnector.java | 53 + .../extensions/BrowserWindowOpenerConnector.java | 94 + .../client/extensions/FileDownloaderConnector.java | 85 + .../client/extensions/ResponsiveConnector.java | 449 + .../JavaScriptManagerConnector.java | 144 + .../vaadin/client/metadata/AsyncBundleLoader.java | 103 + .../vaadin/client/metadata/BundleLoadCallback.java | 22 + .../client/metadata/ConnectorBundleLoader.java | 209 + .../vaadin/client/metadata/InvokationHandler.java | 21 + .../java/com/vaadin/client/metadata/Invoker.java | 20 + .../com/vaadin/client/metadata/JsniInvoker.java | 53 + .../java/com/vaadin/client/metadata/Method.java | 117 + .../vaadin/client/metadata/NoDataException.java | 25 + .../client/metadata/OnStateChangeMethod.java | 111 + .../java/com/vaadin/client/metadata/Property.java | 143 + .../com/vaadin/client/metadata/ProxyHandler.java | 23 + .../main/java/com/vaadin/client/metadata/Type.java | 151 + .../java/com/vaadin/client/metadata/TypeData.java | 31 + .../com/vaadin/client/metadata/TypeDataStore.java | 488 ++ .../vaadin/client/renderers/ButtonRenderer.java | 44 + .../vaadin/client/renderers/ClickableRenderer.java | 233 + .../vaadin/client/renderers/ComplexRenderer.java | 157 + .../com/vaadin/client/renderers/DateRenderer.java | 108 + .../com/vaadin/client/renderers/HtmlRenderer.java | 41 + .../com/vaadin/client/renderers/ImageRenderer.java | 49 + .../vaadin/client/renderers/NumberRenderer.java | 71 + .../client/renderers/ProgressBarRenderer.java | 47 + .../java/com/vaadin/client/renderers/Renderer.java | 54 + .../com/vaadin/client/renderers/TextRenderer.java | 32 + .../vaadin/client/renderers/WidgetRenderer.java | 111 + .../client/ui/AbstractClickEventHandler.java | 246 + .../client/ui/AbstractComponentConnector.java | 793 ++ .../ui/AbstractComponentContainerConnector.java | 26 + .../com/vaadin/client/ui/AbstractConnector.java | 508 ++ .../vaadin/client/ui/AbstractFieldConnector.java | 62 + .../client/ui/AbstractHasComponentsConnector.java | 71 + .../vaadin/client/ui/AbstractLayoutConnector.java | 27 + .../AbstractSingleComponentContainerConnector.java | 61 + .../src/main/java/com/vaadin/client/ui/Action.java | 83 + .../java/com/vaadin/client/ui/ActionOwner.java | 31 + .../java/com/vaadin/client/ui/CalendarEntry.java | 140 + .../com/vaadin/client/ui/ClickEventHandler.java | 62 + .../client/ui/ConnectorFocusAndBlurHandler.java | 87 + .../src/main/java/com/vaadin/client/ui/Field.java | 28 + .../com/vaadin/client/ui/FocusElementPanel.java | 87 + .../main/java/com/vaadin/client/ui/FocusUtil.java | 94 + .../com/vaadin/client/ui/FocusableFlexTable.java | 122 + .../com/vaadin/client/ui/FocusableFlowPanel.java | 117 + .../com/vaadin/client/ui/FocusableScrollPanel.java | 206 + .../main/java/com/vaadin/client/ui/FontIcon.java | 149 + .../src/main/java/com/vaadin/client/ui/Icon.java | 59 + .../main/java/com/vaadin/client/ui/ImageIcon.java | 76 + .../client/ui/JavaScriptComponentConnector.java | 63 + .../com/vaadin/client/ui/JavaScriptWidget.java | 37 + .../vaadin/client/ui/JsniMousewheelHandler.java | 80 + .../vaadin/client/ui/LayoutClickEventHandler.java | 52 + .../java/com/vaadin/client/ui/LegacyConnector.java | 35 + .../java/com/vaadin/client/ui/ManagedLayout.java | 22 + .../com/vaadin/client/ui/MediaBaseConnector.java | 92 + .../com/vaadin/client/ui/PostLayoutListener.java | 38 + .../vaadin/client/ui/ShortcutActionHandler.java | 312 + .../com/vaadin/client/ui/SimpleFocusablePanel.java | 91 + .../com/vaadin/client/ui/SimpleManagedLayout.java | 20 + .../java/com/vaadin/client/ui/SubPartAware.java | 60 + .../com/vaadin/client/ui/TouchScrollDelegate.java | 723 ++ .../main/java/com/vaadin/client/ui/TreeAction.java | 68 + .../client/ui/UnknownComponentConnector.java | 47 + .../java/com/vaadin/client/ui/VAbsoluteLayout.java | 507 ++ .../com/vaadin/client/ui/VAbstractSplitPanel.java | 867 ++ .../main/java/com/vaadin/client/ui/VAccordion.java | 471 ++ .../src/main/java/com/vaadin/client/ui/VAudio.java | 33 + .../java/com/vaadin/client/ui/VBrowserFrame.java | 157 + .../main/java/com/vaadin/client/ui/VButton.java | 482 ++ .../main/java/com/vaadin/client/ui/VCalendar.java | 1500 ++++ .../java/com/vaadin/client/ui/VCalendarPanel.java | 2258 +++++ .../main/java/com/vaadin/client/ui/VCheckBox.java | 108 + .../java/com/vaadin/client/ui/VColorPicker.java | 86 + .../com/vaadin/client/ui/VColorPickerArea.java | 199 + .../java/com/vaadin/client/ui/VContextMenu.java | 331 + .../main/java/com/vaadin/client/ui/VCssLayout.java | 68 + .../com/vaadin/client/ui/VCustomComponent.java | 30 + .../java/com/vaadin/client/ui/VCustomLayout.java | 447 + .../main/java/com/vaadin/client/ui/VDateField.java | 229 + .../com/vaadin/client/ui/VDateFieldCalendar.java | 129 + .../com/vaadin/client/ui/VDragAndDropWrapper.java | 730 ++ .../vaadin/client/ui/VDragAndDropWrapperIE.java | 95 + .../main/java/com/vaadin/client/ui/VEmbedded.java | 263 + .../java/com/vaadin/client/ui/VFilterSelect.java | 2351 ++++++ .../src/main/java/com/vaadin/client/ui/VFlash.java | 250 + .../src/main/java/com/vaadin/client/ui/VForm.java | 139 + .../java/com/vaadin/client/ui/VFormLayout.java | 392 + .../java/com/vaadin/client/ui/VGridLayout.java | 925 ++ .../com/vaadin/client/ui/VHorizontalLayout.java | 42 + .../src/main/java/com/vaadin/client/ui/VImage.java | 27 + .../src/main/java/com/vaadin/client/ui/VLabel.java | 72 + .../java/com/vaadin/client/ui/VLazyExecutor.java | 64 + .../src/main/java/com/vaadin/client/ui/VLink.java | 148 + .../java/com/vaadin/client/ui/VListSelect.java | 154 + .../main/java/com/vaadin/client/ui/VMediaBase.java | 90 + .../main/java/com/vaadin/client/ui/VMenuBar.java | 1718 ++++ .../java/com/vaadin/client/ui/VNativeButton.java | 162 + .../java/com/vaadin/client/ui/VNativeSelect.java | 154 + .../java/com/vaadin/client/ui/VNotification.java | 683 ++ .../java/com/vaadin/client/ui/VOptionGroup.java | 311 + .../com/vaadin/client/ui/VOptionGroupBase.java | 218 + .../main/java/com/vaadin/client/ui/VOverlay.java | 177 + .../src/main/java/com/vaadin/client/ui/VPanel.java | 204 + .../java/com/vaadin/client/ui/VPasswordField.java | 33 + .../java/com/vaadin/client/ui/VPopupCalendar.java | 737 ++ .../main/java/com/vaadin/client/ui/VPopupView.java | 466 + .../java/com/vaadin/client/ui/VProgressBar.java | 101 + .../com/vaadin/client/ui/VProgressIndicator.java | 34 + .../java/com/vaadin/client/ui/VRichTextArea.java | 363 + .../java/com/vaadin/client/ui/VScrollTable.java | 8370 ++++++++++++++++++ .../main/java/com/vaadin/client/ui/VSlider.java | 675 ++ .../vaadin/client/ui/VSplitPanelHorizontal.java | 49 + .../com/vaadin/client/ui/VSplitPanelVertical.java | 49 + .../main/java/com/vaadin/client/ui/VTabsheet.java | 1998 +++++ .../java/com/vaadin/client/ui/VTabsheetBase.java | 201 + .../java/com/vaadin/client/ui/VTabsheetPanel.java | 202 + .../main/java/com/vaadin/client/ui/VTextArea.java | 336 + .../main/java/com/vaadin/client/ui/VTextField.java | 531 ++ .../java/com/vaadin/client/ui/VTextualDate.java | 410 + .../src/main/java/com/vaadin/client/ui/VTree.java | 2262 +++++ .../main/java/com/vaadin/client/ui/VTreeTable.java | 904 ++ .../java/com/vaadin/client/ui/VTwinColSelect.java | 630 ++ client/src/main/java/com/vaadin/client/ui/VUI.java | 508 ++ .../com/vaadin/client/ui/VUnknownComponent.java | 40 + .../main/java/com/vaadin/client/ui/VUpload.java | 393 + .../java/com/vaadin/client/ui/VVerticalLayout.java | 42 + .../src/main/java/com/vaadin/client/ui/VVideo.java | 70 + .../main/java/com/vaadin/client/ui/VWindow.java | 1487 ++++ .../ui/absolutelayout/AbsoluteLayoutConnector.java | 271 + .../client/ui/accordion/AccordionConnector.java | 144 + .../java/com/vaadin/client/ui/aria/AriaHelper.java | 189 + .../vaadin/client/ui/aria/HandlesAriaCaption.java | 37 + .../vaadin/client/ui/aria/HandlesAriaInvalid.java | 33 + .../vaadin/client/ui/aria/HandlesAriaRequired.java | 32 + .../com/vaadin/client/ui/audio/AudioConnector.java | 78 + .../ui/browserframe/BrowserFrameConnector.java | 49 + .../vaadin/client/ui/button/ButtonConnector.java | 131 + .../client/ui/calendar/CalendarConnector.java | 707 ++ .../vaadin/client/ui/calendar/VCalendarAction.java | 138 + .../client/ui/calendar/schedule/CalendarDay.java | 61 + .../client/ui/calendar/schedule/CalendarEvent.java | 319 + .../client/ui/calendar/schedule/DateCell.java | 853 ++ .../ui/calendar/schedule/DateCellContainer.java | 117 + .../ui/calendar/schedule/DateCellDayEvent.java | 657 ++ .../client/ui/calendar/schedule/DateCellGroup.java | 59 + .../client/ui/calendar/schedule/DateUtil.java | 70 + .../client/ui/calendar/schedule/DayToolbar.java | 179 + .../calendar/schedule/FocusableComplexPanel.java | 122 + .../client/ui/calendar/schedule/FocusableGrid.java | 134 + .../client/ui/calendar/schedule/FocusableHTML.java | 124 + .../client/ui/calendar/schedule/HasTooltipKey.java | 33 + .../ui/calendar/schedule/MonthEventLabel.java | 173 + .../client/ui/calendar/schedule/MonthGrid.java | 215 + .../client/ui/calendar/schedule/SimpleDayCell.java | 734 ++ .../ui/calendar/schedule/SimpleDayToolbar.java | 97 + .../ui/calendar/schedule/SimpleWeekToolbar.java | 109 + .../client/ui/calendar/schedule/WeekGrid.java | 688 ++ .../calendar/schedule/WeekGridMinuteTimeRange.java | 62 + .../client/ui/calendar/schedule/WeekLabel.java | 51 + .../ui/calendar/schedule/WeeklyLongEvents.java | 189 + .../schedule/WeeklyLongEventsDateCell.java | 67 + .../calendar/schedule/dd/CalendarDropHandler.java | 66 + .../schedule/dd/CalendarMonthDropHandler.java | 171 + .../schedule/dd/CalendarWeekDropHandler.java | 186 + .../client/ui/checkbox/CheckBoxConnector.java | 155 + .../colorpicker/AbstractColorPickerConnector.java | 114 + .../ui/colorpicker/ColorPickerAreaConnector.java | 67 + .../ui/colorpicker/ColorPickerConnector.java | 69 + .../colorpicker/ColorPickerGradientConnector.java | 85 + .../ui/colorpicker/ColorPickerGridConnector.java | 94 + .../ui/colorpicker/VColorPickerGradient.java | 209 + .../client/ui/colorpicker/VColorPickerGrid.java | 147 + .../client/ui/combobox/ComboBoxConnector.java | 350 + .../client/ui/csslayout/CssLayoutConnector.java | 203 + .../customcomponent/CustomComponentConnector.java | 50 + .../ui/customfield/CustomFieldConnector.java | 123 + .../ui/customlayout/CustomLayoutConnector.java | 159 + .../ui/datefield/AbstractDateFieldConnector.java | 125 + .../client/ui/datefield/DateFieldConnector.java | 218 + .../ui/datefield/InlineDateFieldConnector.java | 130 + .../ui/datefield/PopupDateFieldConnector.java | 25 + .../client/ui/datefield/TextualDateConnector.java | 67 + .../vaadin/client/ui/dd/DDEventHandleStrategy.java | 418 + .../main/java/com/vaadin/client/ui/dd/DDUtil.java | 81 + .../vaadin/client/ui/dd/DragAndDropHandler.java | 257 + .../java/com/vaadin/client/ui/dd/DragHandle.java | 219 + .../com/vaadin/client/ui/dd/DragImageModifier.java | 40 + .../vaadin/client/ui/dd/VAbstractDropHandler.java | 154 + .../java/com/vaadin/client/ui/dd/VAcceptAll.java | 32 + .../com/vaadin/client/ui/dd/VAcceptCallback.java | 29 + .../com/vaadin/client/ui/dd/VAcceptCriteria.java | 34 + .../com/vaadin/client/ui/dd/VAcceptCriterion.java | 57 + .../client/ui/dd/VAcceptCriterionFactory.java | 25 + .../main/java/com/vaadin/client/ui/dd/VAnd.java | 54 + .../vaadin/client/ui/dd/VContainsDataFlavor.java | 33 + .../vaadin/client/ui/dd/VDragAndDropManager.java | 761 ++ .../java/com/vaadin/client/ui/dd/VDragEvent.java | 316 + .../client/ui/dd/VDragEventServerCallback.java | 24 + .../com/vaadin/client/ui/dd/VDragSourceIs.java | 54 + .../java/com/vaadin/client/ui/dd/VDropHandler.java | 85 + .../com/vaadin/client/ui/dd/VHasDropHandler.java | 29 + .../com/vaadin/client/ui/dd/VHtml5DragEvent.java | 88 + .../java/com/vaadin/client/ui/dd/VHtml5File.java | 49 + .../java/com/vaadin/client/ui/dd/VIsOverId.java | 54 + .../java/com/vaadin/client/ui/dd/VItemIdIs.java | 50 + .../client/ui/dd/VLazyInitItemIdentifiers.java | 93 + .../main/java/com/vaadin/client/ui/dd/VNot.java | 76 + .../src/main/java/com/vaadin/client/ui/dd/VOr.java | 62 + .../com/vaadin/client/ui/dd/VOverTreeNode.java | 31 + .../com/vaadin/client/ui/dd/VServerAccept.java | 51 + .../com/vaadin/client/ui/dd/VSourceIsTarget.java | 37 + .../com/vaadin/client/ui/dd/VTargetDetailIs.java | 51 + .../com/vaadin/client/ui/dd/VTargetInSubtree.java | 56 + .../com/vaadin/client/ui/dd/VTransferable.java | 81 + .../DragAndDropWrapperConnector.java | 124 + .../client/ui/embedded/EmbeddedConnector.java | 274 + .../com/vaadin/client/ui/flash/FlashConnector.java | 55 + .../com/vaadin/client/ui/form/FormConnector.java | 236 + .../client/ui/formlayout/FormLayoutConnector.java | 294 + .../client/ui/gridlayout/GridLayoutConnector.java | 227 + .../com/vaadin/client/ui/image/ImageConnector.java | 82 + .../com/vaadin/client/ui/label/LabelConnector.java | 82 + .../ui/layout/ComponentConnectorLayoutSlot.java | 111 + .../client/ui/layout/ElementResizeEvent.java | 37 + .../client/ui/layout/ElementResizeListener.java | 21 + .../client/ui/layout/LayoutDependencyTree.java | 763 ++ .../java/com/vaadin/client/ui/layout/Margins.java | 98 + .../vaadin/client/ui/layout/MayScrollChildren.java | 22 + .../com/vaadin/client/ui/layout/VLayoutSlot.java | 306 + .../com/vaadin/client/ui/link/LinkConnector.java | 103 + .../client/ui/listselect/ListSelectConnector.java | 31 + .../java/com/vaadin/client/ui/menubar/MenuBar.java | 637 ++ .../vaadin/client/ui/menubar/MenuBarConnector.java | 223 + .../com/vaadin/client/ui/menubar/MenuItem.java | 205 + .../ui/nativebutton/NativeButtonConnector.java | 98 + .../ui/nativeselect/NativeSelectConnector.java | 38 + .../ui/optiongroup/OptionGroupBaseConnector.java | 117 + .../ui/optiongroup/OptionGroupConnector.java | 87 + .../AbstractOrderedLayoutConnector.java | 710 ++ .../client/ui/orderedlayout/CaptionPosition.java | 24 + .../orderedlayout/HorizontalLayoutConnector.java | 48 + .../com/vaadin/client/ui/orderedlayout/Slot.java | 832 ++ .../ui/orderedlayout/VAbstractOrderedLayout.java | 742 ++ .../ui/orderedlayout/VerticalLayoutConnector.java | 47 + .../com/vaadin/client/ui/panel/PanelConnector.java | 256 + .../ui/passwordfield/PasswordFieldConnector.java | 31 + .../client/ui/popupview/PopupViewConnector.java | 145 + .../client/ui/popupview/VisibilityChangeEvent.java | 50 + .../ui/popupview/VisibilityChangeHandler.java | 23 + .../ui/progressindicator/ProgressBarConnector.java | 56 + .../ProgressIndicatorConnector.java | 75 + .../ui/richtextarea/RichTextAreaConnector.java | 144 + .../client/ui/richtextarea/VRichTextToolbar.java | 476 ++ .../vaadin/client/ui/slider/SliderConnector.java | 97 + .../ui/splitpanel/AbstractSplitPanelConnector.java | 268 + .../splitpanel/HorizontalSplitPanelConnector.java | 37 + .../ui/splitpanel/VerticalSplitPanelConnector.java | 37 + .../com/vaadin/client/ui/table/TableConnector.java | 556 ++ .../client/ui/tabsheet/TabsheetBaseConnector.java | 118 + .../client/ui/tabsheet/TabsheetConnector.java | 192 + .../client/ui/textarea/TextAreaConnector.java | 91 + .../client/ui/textfield/TextFieldConnector.java | 129 + .../com/vaadin/client/ui/tree/TreeConnector.java | 400 + .../client/ui/treetable/TreeTableConnector.java | 151 + .../ui/twincolselect/TwinColSelectConnector.java | 84 + .../java/com/vaadin/client/ui/ui/UIConnector.java | 1126 +++ .../vaadin/client/ui/upload/UploadConnector.java | 112 + .../ui/upload/UploadIFrameOnloadStrategy.java | 39 + .../ui/upload/UploadIFrameOnloadStrategyIE.java | 42 + .../com/vaadin/client/ui/video/VideoConnector.java | 50 + .../vaadin/client/ui/window/WindowConnector.java | 512 ++ .../vaadin/client/ui/window/WindowMoveEvent.java | 82 + .../vaadin/client/ui/window/WindowMoveHandler.java | 35 + .../com/vaadin/client/widget/escalator/Cell.java | 85 + .../widget/escalator/ColumnConfiguration.java | 196 + .../client/widget/escalator/EscalatorUpdater.java | 155 + .../client/widget/escalator/FlyweightCell.java | 201 + .../client/widget/escalator/FlyweightRow.java | 294 + .../client/widget/escalator/PositionFunction.java | 118 + .../com/vaadin/client/widget/escalator/Row.java | 48 + .../client/widget/escalator/RowContainer.java | 281 + .../widget/escalator/RowVisibilityChangeEvent.java | 99 + .../escalator/RowVisibilityChangeHandler.java | 38 + .../client/widget/escalator/ScrollbarBundle.java | 866 ++ .../com/vaadin/client/widget/escalator/Spacer.java | 47 + .../client/widget/escalator/SpacerUpdater.java | 64 + .../vaadin/client/widget/grid/AutoScroller.java | 643 ++ .../vaadin/client/widget/grid/CellReference.java | 151 + .../client/widget/grid/CellStyleGenerator.java | 40 + .../client/widget/grid/DataAvailableEvent.java | 55 + .../client/widget/grid/DataAvailableHandler.java | 37 + .../widget/grid/DefaultEditorEventHandler.java | 328 + .../client/widget/grid/DetailsGenerator.java | 46 + .../vaadin/client/widget/grid/EditorHandler.java | 174 + .../client/widget/grid/EventCellReference.java | 128 + .../widget/grid/HeightAwareDetailsGenerator.java | 45 + .../client/widget/grid/RendererCellReference.java | 93 + .../vaadin/client/widget/grid/RowReference.java | 104 + .../client/widget/grid/RowStyleGenerator.java | 40 + .../widget/grid/datasources/ListDataSource.java | 478 ++ .../client/widget/grid/datasources/ListSorter.java | 175 + .../grid/events/AbstractGridKeyEventHandler.java | 44 + .../grid/events/AbstractGridMouseEventHandler.java | 39 + .../widget/grid/events/BodyClickHandler.java | 28 + .../widget/grid/events/BodyDoubleClickHandler.java | 29 + .../widget/grid/events/BodyKeyDownHandler.java | 28 + .../widget/grid/events/BodyKeyPressHandler.java | 28 + .../widget/grid/events/BodyKeyUpHandler.java | 28 + .../widget/grid/events/ColumnReorderEvent.java | 51 + .../widget/grid/events/ColumnReorderHandler.java | 40 + .../widget/grid/events/ColumnResizeEvent.java | 67 + .../widget/grid/events/ColumnResizeHandler.java | 40 + .../grid/events/ColumnVisibilityChangeEvent.java | 93 + .../grid/events/ColumnVisibilityChangeHandler.java | 39 + .../widget/grid/events/FooterClickHandler.java | 28 + .../grid/events/FooterDoubleClickHandler.java | 29 + .../widget/grid/events/FooterKeyDownHandler.java | 28 + .../widget/grid/events/FooterKeyPressHandler.java | 28 + .../widget/grid/events/FooterKeyUpHandler.java | 28 + .../client/widget/grid/events/GridClickEvent.java | 50 + .../widget/grid/events/GridDoubleClickEvent.java | 52 + .../widget/grid/events/GridKeyDownEvent.java | 121 + .../widget/grid/events/GridKeyPressEvent.java | 74 + .../client/widget/grid/events/GridKeyUpEvent.java | 121 + .../widget/grid/events/HeaderClickHandler.java | 28 + .../grid/events/HeaderDoubleClickHandler.java | 29 + .../widget/grid/events/HeaderKeyDownHandler.java | 28 + .../widget/grid/events/HeaderKeyPressHandler.java | 28 + .../widget/grid/events/HeaderKeyUpHandler.java | 28 + .../client/widget/grid/events/ScrollEvent.java | 40 + .../client/widget/grid/events/ScrollHandler.java | 35 + .../client/widget/grid/events/SelectAllEvent.java | 59 + .../widget/grid/events/SelectAllHandler.java | 37 + .../selection/AbstractRowHandleSelectionModel.java | 66 + .../widget/grid/selection/ClickSelectHandler.java | 77 + .../grid/selection/HasSelectionHandlers.java | 42 + .../grid/selection/MultiSelectionRenderer.java | 763 ++ .../widget/grid/selection/SelectionEvent.java | 178 + .../widget/grid/selection/SelectionHandler.java | 39 + .../widget/grid/selection/SelectionModel.java | 258 + .../widget/grid/selection/SelectionModelMulti.java | 273 + .../widget/grid/selection/SelectionModelNone.java | 73 + .../grid/selection/SelectionModelSingle.java | 175 + .../widget/grid/selection/SpaceSelectHandler.java | 137 + .../com/vaadin/client/widget/grid/sort/Sort.java | 154 + .../vaadin/client/widget/grid/sort/SortEvent.java | 113 + .../client/widget/grid/sort/SortHandler.java | 38 + .../vaadin/client/widget/grid/sort/SortOrder.java | 90 + .../java/com/vaadin/client/widgets/Escalator.java | 6697 +++++++++++++++ .../main/java/com/vaadin/client/widgets/Grid.java | 8877 ++++++++++++++++++++ .../java/com/vaadin/client/widgets/Overlay.java | 1012 +++ .../resources/com/vaadin/DefaultWidgetSet.gwt.xml | 42 + .../src/main/resources/com/vaadin/Vaadin.gwt.xml | 93 + .../vaadin/VaadinBrowserSpecificOverrides.gwt.xml | 47 + .../client/debug/internal/theme/debugwindow.css | 322 + .../vaadin/client/debug/internal/theme/font.eot | Bin 0 -> 7752 bytes .../vaadin/client/debug/internal/theme/font.svg | 37 + .../vaadin/client/debug/internal/theme/font.ttf | Bin 0 -> 7588 bytes .../vaadin/client/debug/internal/theme/font.woff | Bin 0 -> 7664 bytes .../ui/doc-files/IOrderedLayout_alignment.png | Bin 0 -> 14779 bytes ...OrderedLayout_component_handles_the_caption.png | Bin 0 -> 5676 bytes .../client/ui/doc-files/IOrderedLayout_h150.png | Bin 0 -> 9802 bytes .../ui/doc-files/IOrderedLayout_horizontal.png | Bin 0 -> 5769 bytes .../IOrderedLayout_horizontal_spacing.png | Bin 0 -> 5862 bytes .../client/ui/doc-files/IOrderedLayout_margin.png | Bin 0 -> 6828 bytes .../ui/doc-files/IOrderedLayout_no_caption.png | Bin 0 -> 2113 bytes .../ui/doc-files/IOrderedLayout_normal_caption.png | Bin 0 -> 3571 bytes .../ui/doc-files/IOrderedLayout_special-margin.png | Bin 0 -> 3763 bytes .../ui/doc-files/IOrderedLayout_vertical.png | Bin 0 -> 6140 bytes .../doc-files/IOrderedLayout_vertical_spacing.png | Bin 0 -> 6652 bytes .../client/ui/doc-files/IOrderedLayout_w300.png | Bin 0 -> 9543 bytes .../ui/doc-files/IOrderedLayout_w300_h150.png | Bin 0 -> 10067 bytes .../VRichTextToolbar$Strings.properties | 35 + .../vaadin/client/ui/richtextarea/backColors.gif | Bin 0 -> 104 bytes .../com/vaadin/client/ui/richtextarea/bold.gif | Bin 0 -> 900 bytes .../vaadin/client/ui/richtextarea/createLink.gif | Bin 0 -> 954 bytes .../vaadin/client/ui/richtextarea/fontSizes.gif | Bin 0 -> 96 bytes .../com/vaadin/client/ui/richtextarea/fonts.gif | Bin 0 -> 147 bytes .../vaadin/client/ui/richtextarea/foreColors.gif | Bin 0 -> 173 bytes .../com/vaadin/client/ui/richtextarea/gwtLogo.png | Bin 0 -> 11454 bytes .../com/vaadin/client/ui/richtextarea/hr.gif | Bin 0 -> 853 bytes .../com/vaadin/client/ui/richtextarea/indent.gif | Bin 0 -> 76 bytes .../vaadin/client/ui/richtextarea/insertImage.gif | Bin 0 -> 946 bytes .../com/vaadin/client/ui/richtextarea/italic.gif | Bin 0 -> 190 bytes .../client/ui/richtextarea/justifyCenter.gif | Bin 0 -> 70 bytes .../vaadin/client/ui/richtextarea/justifyLeft.gif | Bin 0 -> 71 bytes .../vaadin/client/ui/richtextarea/justifyRight.gif | Bin 0 -> 70 bytes .../com/vaadin/client/ui/richtextarea/ol.gif | Bin 0 -> 204 bytes .../com/vaadin/client/ui/richtextarea/outdent.gif | Bin 0 -> 76 bytes .../vaadin/client/ui/richtextarea/removeFormat.gif | Bin 0 -> 962 bytes .../vaadin/client/ui/richtextarea/removeLink.gif | Bin 0 -> 585 bytes .../client/ui/richtextarea/strikeThrough.gif | Bin 0 -> 915 bytes .../vaadin/client/ui/richtextarea/subscript.gif | Bin 0 -> 933 bytes .../vaadin/client/ui/richtextarea/superscript.gif | Bin 0 -> 232 bytes .../com/vaadin/client/ui/richtextarea/ul.gif | Bin 0 -> 133 bytes .../vaadin/client/ui/richtextarea/underline.gif | Bin 0 -> 914 bytes .../vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml | 9 + .../ApplicationConnectionURLGenerationTest.java | 74 + .../com/vaadin/client/DateTimeServiceTest.java | 122 + .../java/com/vaadin/client/LocatorUtilTest.java | 80 + .../client/VBrowserDetailsUserAgentParserTest.java | 651 ++ .../communication/ServerMessageHandlerTest.java | 54 + .../vaadin/client/ui/grid/ListDataSourceTest.java | 192 + .../vaadin/client/ui/grid/PartitioningTest.java | 104 + .../ApplicationConnectionURLGenerationTest.java | 74 - .../src/com/vaadin/client/DateTimeServiceTest.java | 122 - .../src/com/vaadin/client/LocatorUtilTest.java | 80 - .../client/VBrowserDetailsUserAgentParserTest.java | 651 -- .../communication/ServerMessageHandlerTest.java | 54 - .../vaadin/client/ui/grid/ListDataSourceTest.java | 192 - .../vaadin/client/ui/grid/PartitioningTest.java | 104 - ivysettings.xml | 2 +- pom.xml | 1 + uitest/ivy.xml | 3 +- widgets/ivy.xml | 2 +- 1109 files changed, 135073 insertions(+), 134962 deletions(-) delete mode 100644 client/build.xml create mode 100644 client/pom.xml delete mode 100755 client/src/com/vaadin/DefaultWidgetSet.gwt.xml delete mode 100644 client/src/com/vaadin/Vaadin.gwt.xml delete mode 100644 client/src/com/vaadin/VaadinBrowserSpecificOverrides.gwt.xml delete mode 100644 client/src/com/vaadin/client/AnimationUtil.java delete mode 100644 client/src/com/vaadin/client/ApplicationConfiguration.java delete mode 100644 client/src/com/vaadin/client/ApplicationConnection.java delete mode 100644 client/src/com/vaadin/client/BrowserInfo.java delete mode 100644 client/src/com/vaadin/client/CSSRule.java delete mode 100644 client/src/com/vaadin/client/ComponentConnector.java delete mode 100644 client/src/com/vaadin/client/ComponentDetail.java delete mode 100644 client/src/com/vaadin/client/ComponentDetailMap.java delete mode 100644 client/src/com/vaadin/client/ComputedStyle.java delete mode 100644 client/src/com/vaadin/client/ConnectorHierarchyChangeEvent.java delete mode 100644 client/src/com/vaadin/client/ConnectorMap.java delete mode 100644 client/src/com/vaadin/client/ContainerResizedListener.java delete mode 100644 client/src/com/vaadin/client/DateTimeService.java delete mode 100644 client/src/com/vaadin/client/DeferredWorker.java delete mode 100644 client/src/com/vaadin/client/DirectionalManagedLayout.java delete mode 100644 client/src/com/vaadin/client/EventHelper.java delete mode 100644 client/src/com/vaadin/client/FastStringMap.java delete mode 100644 client/src/com/vaadin/client/FastStringSet.java delete mode 100644 client/src/com/vaadin/client/Focusable.java delete mode 100644 client/src/com/vaadin/client/HasChildMeasurementHintConnector.java delete mode 100644 client/src/com/vaadin/client/HasComponentsConnector.java delete mode 100644 client/src/com/vaadin/client/JavaScriptConnectorHelper.java delete mode 100644 client/src/com/vaadin/client/JavaScriptExtension.java delete mode 100644 client/src/com/vaadin/client/JsArrayObject.java delete mode 100644 client/src/com/vaadin/client/LayoutManager.java delete mode 100644 client/src/com/vaadin/client/LayoutManagerIE8.java delete mode 100644 client/src/com/vaadin/client/LocaleNotLoadedException.java delete mode 100644 client/src/com/vaadin/client/LocaleService.java delete mode 100644 client/src/com/vaadin/client/MeasuredSize.java delete mode 100644 client/src/com/vaadin/client/MouseEventDetailsBuilder.java delete mode 100644 client/src/com/vaadin/client/Paintable.java delete mode 100644 client/src/com/vaadin/client/Profiler.java delete mode 100644 client/src/com/vaadin/client/RenderInformation.java delete mode 100644 client/src/com/vaadin/client/RenderSpace.java delete mode 100644 client/src/com/vaadin/client/ResourceLoader.java delete mode 100644 client/src/com/vaadin/client/ServerConnector.java delete mode 100644 client/src/com/vaadin/client/SimpleTree.java delete mode 100644 client/src/com/vaadin/client/StyleConstants.java delete mode 100644 client/src/com/vaadin/client/SuperDevMode.java delete mode 100644 client/src/com/vaadin/client/TooltipInfo.java delete mode 100644 client/src/com/vaadin/client/UIDL.java delete mode 100644 client/src/com/vaadin/client/Util.java delete mode 100644 client/src/com/vaadin/client/VCaption.java delete mode 100644 client/src/com/vaadin/client/VCaptionWrapper.java delete mode 100644 client/src/com/vaadin/client/VConsole.java delete mode 100644 client/src/com/vaadin/client/VErrorMessage.java delete mode 100644 client/src/com/vaadin/client/VLoadingIndicator.java delete mode 100644 client/src/com/vaadin/client/VSchedulerImpl.java delete mode 100644 client/src/com/vaadin/client/VTooltip.java delete mode 100644 client/src/com/vaadin/client/VUIDLBrowser.java delete mode 100644 client/src/com/vaadin/client/ValueMap.java delete mode 100644 client/src/com/vaadin/client/WidgetInstantiator.java delete mode 100644 client/src/com/vaadin/client/WidgetLoader.java delete mode 100644 client/src/com/vaadin/client/WidgetMap.java delete mode 100644 client/src/com/vaadin/client/WidgetSet.java delete mode 100644 client/src/com/vaadin/client/WidgetUtil.java delete mode 100644 client/src/com/vaadin/client/annotations/OnStateChange.java delete mode 100644 client/src/com/vaadin/client/communication/AbstractServerConnectorEvent.java delete mode 100644 client/src/com/vaadin/client/communication/AtmospherePushConnection.java delete mode 100644 client/src/com/vaadin/client/communication/ConnectionStateHandler.java delete mode 100644 client/src/com/vaadin/client/communication/Date_Serializer.java delete mode 100644 client/src/com/vaadin/client/communication/DefaultConnectionStateHandler.java delete mode 100644 client/src/com/vaadin/client/communication/DefaultReconnectDialog.java delete mode 100644 client/src/com/vaadin/client/communication/DiffJSONSerializer.java delete mode 100644 client/src/com/vaadin/client/communication/HasJavaScriptConnectorHelper.java delete mode 100644 client/src/com/vaadin/client/communication/Heartbeat.java delete mode 100644 client/src/com/vaadin/client/communication/JSONSerializer.java delete mode 100644 client/src/com/vaadin/client/communication/JavaScriptMethodInvocation.java delete mode 100644 client/src/com/vaadin/client/communication/JsonDecoder.java delete mode 100644 client/src/com/vaadin/client/communication/JsonEncoder.java delete mode 100644 client/src/com/vaadin/client/communication/MessageHandler.java delete mode 100644 client/src/com/vaadin/client/communication/MessageSender.java delete mode 100644 client/src/com/vaadin/client/communication/PushConnection.java delete mode 100644 client/src/com/vaadin/client/communication/ReconnectDialog.java delete mode 100644 client/src/com/vaadin/client/communication/RpcManager.java delete mode 100644 client/src/com/vaadin/client/communication/RpcProxy.java delete mode 100644 client/src/com/vaadin/client/communication/ServerRpcQueue.java delete mode 100644 client/src/com/vaadin/client/communication/StateChangeEvent.java delete mode 100644 client/src/com/vaadin/client/communication/TranslatedURLReference.java delete mode 100644 client/src/com/vaadin/client/communication/URLReference_Serializer.java delete mode 100644 client/src/com/vaadin/client/communication/XhrConnection.java delete mode 100644 client/src/com/vaadin/client/communication/XhrConnectionError.java delete mode 100644 client/src/com/vaadin/client/componentlocator/ComponentLocator.java delete mode 100644 client/src/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java delete mode 100644 client/src/com/vaadin/client/componentlocator/LocatorStrategy.java delete mode 100644 client/src/com/vaadin/client/componentlocator/LocatorUtil.java delete mode 100644 client/src/com/vaadin/client/componentlocator/SelectorPredicate.java delete mode 100644 client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java delete mode 100644 client/src/com/vaadin/client/connectors/AbstractRendererConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/ButtonRendererConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/ClickableRendererConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/DateRendererConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/DetailComponentManagerConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/GridConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/ImageRendererConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/JavaScriptRendererConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/NumberRendererConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/ProgressBarRendererConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/TextRendererConnector.java delete mode 100644 client/src/com/vaadin/client/connectors/UnsafeHtmlRendererConnector.java delete mode 100644 client/src/com/vaadin/client/data/AbstractRemoteDataSource.java delete mode 100644 client/src/com/vaadin/client/data/CacheStrategy.java delete mode 100644 client/src/com/vaadin/client/data/DataChangeHandler.java delete mode 100644 client/src/com/vaadin/client/data/DataSource.java delete mode 100644 client/src/com/vaadin/client/debug/internal/AnalyzeLayoutsPanel.java delete mode 100644 client/src/com/vaadin/client/debug/internal/ConnectorInfoPanel.java delete mode 100644 client/src/com/vaadin/client/debug/internal/DebugButton.java delete mode 100644 client/src/com/vaadin/client/debug/internal/ErrorNotificationHandler.java delete mode 100644 client/src/com/vaadin/client/debug/internal/HierarchyPanel.java delete mode 100644 client/src/com/vaadin/client/debug/internal/HierarchySection.java delete mode 100644 client/src/com/vaadin/client/debug/internal/Highlight.java delete mode 100644 client/src/com/vaadin/client/debug/internal/Icon.java delete mode 100644 client/src/com/vaadin/client/debug/internal/InfoSection.java delete mode 100644 client/src/com/vaadin/client/debug/internal/LogSection.java delete mode 100644 client/src/com/vaadin/client/debug/internal/NetworkSection.java delete mode 100644 client/src/com/vaadin/client/debug/internal/OptimizedWidgetsetPanel.java delete mode 100644 client/src/com/vaadin/client/debug/internal/ProfilerSection.java delete mode 100644 client/src/com/vaadin/client/debug/internal/Section.java delete mode 100644 client/src/com/vaadin/client/debug/internal/SelectConnectorListener.java delete mode 100644 client/src/com/vaadin/client/debug/internal/SelectorPath.java delete mode 100644 client/src/com/vaadin/client/debug/internal/TestBenchSection.java delete mode 100644 client/src/com/vaadin/client/debug/internal/VDebugWindow.java delete mode 100644 client/src/com/vaadin/client/debug/internal/theme/DebugWindowStyles.java delete mode 100644 client/src/com/vaadin/client/debug/internal/theme/debugwindow.css delete mode 100755 client/src/com/vaadin/client/debug/internal/theme/font.eot delete mode 100755 client/src/com/vaadin/client/debug/internal/theme/font.svg delete mode 100755 client/src/com/vaadin/client/debug/internal/theme/font.ttf delete mode 100755 client/src/com/vaadin/client/debug/internal/theme/font.woff delete mode 100644 client/src/com/vaadin/client/event/PointerCancelEvent.java delete mode 100644 client/src/com/vaadin/client/event/PointerCancelHandler.java delete mode 100644 client/src/com/vaadin/client/event/PointerDownEvent.java delete mode 100644 client/src/com/vaadin/client/event/PointerDownHandler.java delete mode 100644 client/src/com/vaadin/client/event/PointerEvent.java delete mode 100644 client/src/com/vaadin/client/event/PointerEventSupport.java delete mode 100644 client/src/com/vaadin/client/event/PointerEventSupportImpl.java delete mode 100644 client/src/com/vaadin/client/event/PointerEventSupportImplIE10.java delete mode 100644 client/src/com/vaadin/client/event/PointerEventSupportImplModernIE.java delete mode 100644 client/src/com/vaadin/client/event/PointerMoveEvent.java delete mode 100644 client/src/com/vaadin/client/event/PointerMoveHandler.java delete mode 100644 client/src/com/vaadin/client/event/PointerUpEvent.java delete mode 100644 client/src/com/vaadin/client/event/PointerUpHandler.java delete mode 100644 client/src/com/vaadin/client/extensions/AbstractExtensionConnector.java delete mode 100644 client/src/com/vaadin/client/extensions/BrowserWindowOpenerConnector.java delete mode 100644 client/src/com/vaadin/client/extensions/FileDownloaderConnector.java delete mode 100644 client/src/com/vaadin/client/extensions/ResponsiveConnector.java delete mode 100644 client/src/com/vaadin/client/extensions/javascriptmanager/JavaScriptManagerConnector.java delete mode 100644 client/src/com/vaadin/client/metadata/AsyncBundleLoader.java delete mode 100644 client/src/com/vaadin/client/metadata/BundleLoadCallback.java delete mode 100644 client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java delete mode 100644 client/src/com/vaadin/client/metadata/InvokationHandler.java delete mode 100644 client/src/com/vaadin/client/metadata/Invoker.java delete mode 100644 client/src/com/vaadin/client/metadata/JsniInvoker.java delete mode 100644 client/src/com/vaadin/client/metadata/Method.java delete mode 100644 client/src/com/vaadin/client/metadata/NoDataException.java delete mode 100644 client/src/com/vaadin/client/metadata/OnStateChangeMethod.java delete mode 100644 client/src/com/vaadin/client/metadata/Property.java delete mode 100644 client/src/com/vaadin/client/metadata/ProxyHandler.java delete mode 100644 client/src/com/vaadin/client/metadata/Type.java delete mode 100644 client/src/com/vaadin/client/metadata/TypeData.java delete mode 100644 client/src/com/vaadin/client/metadata/TypeDataStore.java delete mode 100644 client/src/com/vaadin/client/renderers/ButtonRenderer.java delete mode 100644 client/src/com/vaadin/client/renderers/ClickableRenderer.java delete mode 100644 client/src/com/vaadin/client/renderers/ComplexRenderer.java delete mode 100644 client/src/com/vaadin/client/renderers/DateRenderer.java delete mode 100644 client/src/com/vaadin/client/renderers/HtmlRenderer.java delete mode 100644 client/src/com/vaadin/client/renderers/ImageRenderer.java delete mode 100644 client/src/com/vaadin/client/renderers/NumberRenderer.java delete mode 100644 client/src/com/vaadin/client/renderers/ProgressBarRenderer.java delete mode 100644 client/src/com/vaadin/client/renderers/Renderer.java delete mode 100644 client/src/com/vaadin/client/renderers/TextRenderer.java delete mode 100644 client/src/com/vaadin/client/renderers/WidgetRenderer.java delete mode 100644 client/src/com/vaadin/client/ui/AbstractClickEventHandler.java delete mode 100644 client/src/com/vaadin/client/ui/AbstractComponentConnector.java delete mode 100644 client/src/com/vaadin/client/ui/AbstractComponentContainerConnector.java delete mode 100644 client/src/com/vaadin/client/ui/AbstractConnector.java delete mode 100644 client/src/com/vaadin/client/ui/AbstractFieldConnector.java delete mode 100644 client/src/com/vaadin/client/ui/AbstractHasComponentsConnector.java delete mode 100644 client/src/com/vaadin/client/ui/AbstractLayoutConnector.java delete mode 100644 client/src/com/vaadin/client/ui/AbstractSingleComponentContainerConnector.java delete mode 100644 client/src/com/vaadin/client/ui/Action.java delete mode 100644 client/src/com/vaadin/client/ui/ActionOwner.java delete mode 100644 client/src/com/vaadin/client/ui/CalendarEntry.java delete mode 100644 client/src/com/vaadin/client/ui/ClickEventHandler.java delete mode 100644 client/src/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java delete mode 100644 client/src/com/vaadin/client/ui/Field.java delete mode 100644 client/src/com/vaadin/client/ui/FocusElementPanel.java delete mode 100644 client/src/com/vaadin/client/ui/FocusUtil.java delete mode 100644 client/src/com/vaadin/client/ui/FocusableFlexTable.java delete mode 100644 client/src/com/vaadin/client/ui/FocusableFlowPanel.java delete mode 100644 client/src/com/vaadin/client/ui/FocusableScrollPanel.java delete mode 100644 client/src/com/vaadin/client/ui/FontIcon.java delete mode 100644 client/src/com/vaadin/client/ui/Icon.java delete mode 100644 client/src/com/vaadin/client/ui/ImageIcon.java delete mode 100644 client/src/com/vaadin/client/ui/JavaScriptComponentConnector.java delete mode 100644 client/src/com/vaadin/client/ui/JavaScriptWidget.java delete mode 100644 client/src/com/vaadin/client/ui/JsniMousewheelHandler.java delete mode 100644 client/src/com/vaadin/client/ui/LayoutClickEventHandler.java delete mode 100644 client/src/com/vaadin/client/ui/LegacyConnector.java delete mode 100644 client/src/com/vaadin/client/ui/ManagedLayout.java delete mode 100644 client/src/com/vaadin/client/ui/MediaBaseConnector.java delete mode 100644 client/src/com/vaadin/client/ui/PostLayoutListener.java delete mode 100644 client/src/com/vaadin/client/ui/ShortcutActionHandler.java delete mode 100644 client/src/com/vaadin/client/ui/SimpleFocusablePanel.java delete mode 100644 client/src/com/vaadin/client/ui/SimpleManagedLayout.java delete mode 100644 client/src/com/vaadin/client/ui/SubPartAware.java delete mode 100644 client/src/com/vaadin/client/ui/TouchScrollDelegate.java delete mode 100644 client/src/com/vaadin/client/ui/TreeAction.java delete mode 100644 client/src/com/vaadin/client/ui/UnknownComponentConnector.java delete mode 100644 client/src/com/vaadin/client/ui/VAbsoluteLayout.java delete mode 100644 client/src/com/vaadin/client/ui/VAbstractSplitPanel.java delete mode 100644 client/src/com/vaadin/client/ui/VAccordion.java delete mode 100644 client/src/com/vaadin/client/ui/VAudio.java delete mode 100644 client/src/com/vaadin/client/ui/VBrowserFrame.java delete mode 100644 client/src/com/vaadin/client/ui/VButton.java delete mode 100644 client/src/com/vaadin/client/ui/VCalendar.java delete mode 100644 client/src/com/vaadin/client/ui/VCalendarPanel.java delete mode 100644 client/src/com/vaadin/client/ui/VCheckBox.java delete mode 100644 client/src/com/vaadin/client/ui/VColorPicker.java delete mode 100644 client/src/com/vaadin/client/ui/VColorPickerArea.java delete mode 100644 client/src/com/vaadin/client/ui/VContextMenu.java delete mode 100644 client/src/com/vaadin/client/ui/VCssLayout.java delete mode 100644 client/src/com/vaadin/client/ui/VCustomComponent.java delete mode 100644 client/src/com/vaadin/client/ui/VCustomLayout.java delete mode 100644 client/src/com/vaadin/client/ui/VDateField.java delete mode 100644 client/src/com/vaadin/client/ui/VDateFieldCalendar.java delete mode 100644 client/src/com/vaadin/client/ui/VDragAndDropWrapper.java delete mode 100644 client/src/com/vaadin/client/ui/VDragAndDropWrapperIE.java delete mode 100644 client/src/com/vaadin/client/ui/VEmbedded.java delete mode 100644 client/src/com/vaadin/client/ui/VFilterSelect.java delete mode 100644 client/src/com/vaadin/client/ui/VFlash.java delete mode 100644 client/src/com/vaadin/client/ui/VForm.java delete mode 100644 client/src/com/vaadin/client/ui/VFormLayout.java delete mode 100644 client/src/com/vaadin/client/ui/VGridLayout.java delete mode 100644 client/src/com/vaadin/client/ui/VHorizontalLayout.java delete mode 100644 client/src/com/vaadin/client/ui/VImage.java delete mode 100644 client/src/com/vaadin/client/ui/VLabel.java delete mode 100644 client/src/com/vaadin/client/ui/VLazyExecutor.java delete mode 100644 client/src/com/vaadin/client/ui/VLink.java delete mode 100644 client/src/com/vaadin/client/ui/VListSelect.java delete mode 100644 client/src/com/vaadin/client/ui/VMediaBase.java delete mode 100644 client/src/com/vaadin/client/ui/VMenuBar.java delete mode 100644 client/src/com/vaadin/client/ui/VNativeButton.java delete mode 100644 client/src/com/vaadin/client/ui/VNativeSelect.java delete mode 100644 client/src/com/vaadin/client/ui/VNotification.java delete mode 100644 client/src/com/vaadin/client/ui/VOptionGroup.java delete mode 100644 client/src/com/vaadin/client/ui/VOptionGroupBase.java delete mode 100644 client/src/com/vaadin/client/ui/VOverlay.java delete mode 100644 client/src/com/vaadin/client/ui/VPanel.java delete mode 100644 client/src/com/vaadin/client/ui/VPasswordField.java delete mode 100644 client/src/com/vaadin/client/ui/VPopupCalendar.java delete mode 100644 client/src/com/vaadin/client/ui/VPopupView.java delete mode 100644 client/src/com/vaadin/client/ui/VProgressBar.java delete mode 100644 client/src/com/vaadin/client/ui/VProgressIndicator.java delete mode 100644 client/src/com/vaadin/client/ui/VRichTextArea.java delete mode 100644 client/src/com/vaadin/client/ui/VScrollTable.java delete mode 100644 client/src/com/vaadin/client/ui/VSlider.java delete mode 100644 client/src/com/vaadin/client/ui/VSplitPanelHorizontal.java delete mode 100644 client/src/com/vaadin/client/ui/VSplitPanelVertical.java delete mode 100644 client/src/com/vaadin/client/ui/VTabsheet.java delete mode 100644 client/src/com/vaadin/client/ui/VTabsheetBase.java delete mode 100644 client/src/com/vaadin/client/ui/VTabsheetPanel.java delete mode 100644 client/src/com/vaadin/client/ui/VTextArea.java delete mode 100644 client/src/com/vaadin/client/ui/VTextField.java delete mode 100644 client/src/com/vaadin/client/ui/VTextualDate.java delete mode 100644 client/src/com/vaadin/client/ui/VTree.java delete mode 100644 client/src/com/vaadin/client/ui/VTreeTable.java delete mode 100644 client/src/com/vaadin/client/ui/VTwinColSelect.java delete mode 100644 client/src/com/vaadin/client/ui/VUI.java delete mode 100644 client/src/com/vaadin/client/ui/VUnknownComponent.java delete mode 100644 client/src/com/vaadin/client/ui/VUpload.java delete mode 100644 client/src/com/vaadin/client/ui/VVerticalLayout.java delete mode 100644 client/src/com/vaadin/client/ui/VVideo.java delete mode 100644 client/src/com/vaadin/client/ui/VWindow.java delete mode 100644 client/src/com/vaadin/client/ui/absolutelayout/AbsoluteLayoutConnector.java delete mode 100644 client/src/com/vaadin/client/ui/accordion/AccordionConnector.java delete mode 100644 client/src/com/vaadin/client/ui/aria/AriaHelper.java delete mode 100644 client/src/com/vaadin/client/ui/aria/HandlesAriaCaption.java delete mode 100644 client/src/com/vaadin/client/ui/aria/HandlesAriaInvalid.java delete mode 100644 client/src/com/vaadin/client/ui/aria/HandlesAriaRequired.java delete mode 100644 client/src/com/vaadin/client/ui/audio/AudioConnector.java delete mode 100644 client/src/com/vaadin/client/ui/browserframe/BrowserFrameConnector.java delete mode 100644 client/src/com/vaadin/client/ui/button/ButtonConnector.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/CalendarConnector.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/VCalendarAction.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/CalendarDay.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/CalendarEvent.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/DateCell.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/DateCellContainer.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/DateCellGroup.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/DateUtil.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/FocusableComplexPanel.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/FocusableGrid.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/FocusableHTML.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/HasTooltipKey.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/MonthEventLabel.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayToolbar.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/SimpleWeekToolbar.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/WeekGrid.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/WeekGridMinuteTimeRange.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/WeekLabel.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEvents.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEventsDateCell.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarDropHandler.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarMonthDropHandler.java delete mode 100644 client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarWeekDropHandler.java delete mode 100644 client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java delete mode 100644 client/src/com/vaadin/client/ui/colorpicker/AbstractColorPickerConnector.java delete mode 100644 client/src/com/vaadin/client/ui/colorpicker/ColorPickerAreaConnector.java delete mode 100644 client/src/com/vaadin/client/ui/colorpicker/ColorPickerConnector.java delete mode 100644 client/src/com/vaadin/client/ui/colorpicker/ColorPickerGradientConnector.java delete mode 100644 client/src/com/vaadin/client/ui/colorpicker/ColorPickerGridConnector.java delete mode 100644 client/src/com/vaadin/client/ui/colorpicker/VColorPickerGradient.java delete mode 100644 client/src/com/vaadin/client/ui/colorpicker/VColorPickerGrid.java delete mode 100644 client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java delete mode 100644 client/src/com/vaadin/client/ui/csslayout/CssLayoutConnector.java delete mode 100644 client/src/com/vaadin/client/ui/customcomponent/CustomComponentConnector.java delete mode 100644 client/src/com/vaadin/client/ui/customfield/CustomFieldConnector.java delete mode 100644 client/src/com/vaadin/client/ui/customlayout/CustomLayoutConnector.java delete mode 100644 client/src/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java delete mode 100644 client/src/com/vaadin/client/ui/datefield/DateFieldConnector.java delete mode 100644 client/src/com/vaadin/client/ui/datefield/InlineDateFieldConnector.java delete mode 100644 client/src/com/vaadin/client/ui/datefield/PopupDateFieldConnector.java delete mode 100644 client/src/com/vaadin/client/ui/datefield/TextualDateConnector.java delete mode 100644 client/src/com/vaadin/client/ui/dd/DDEventHandleStrategy.java delete mode 100644 client/src/com/vaadin/client/ui/dd/DDUtil.java delete mode 100644 client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java delete mode 100644 client/src/com/vaadin/client/ui/dd/DragHandle.java delete mode 100644 client/src/com/vaadin/client/ui/dd/DragImageModifier.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VAbstractDropHandler.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VAcceptAll.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VAcceptCallback.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VAcceptCriteria.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VAcceptCriterion.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VAcceptCriterionFactory.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VAnd.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VContainsDataFlavor.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VDragAndDropManager.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VDragEvent.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VDragEventServerCallback.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VDragSourceIs.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VDropHandler.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VHasDropHandler.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VHtml5DragEvent.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VHtml5File.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VIsOverId.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VItemIdIs.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VLazyInitItemIdentifiers.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VNot.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VOr.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VOverTreeNode.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VServerAccept.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VSourceIsTarget.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VTargetDetailIs.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VTargetInSubtree.java delete mode 100644 client/src/com/vaadin/client/ui/dd/VTransferable.java delete mode 100644 client/src/com/vaadin/client/ui/doc-files/IOrderedLayout_alignment.png delete mode 100644 client/src/com/vaadin/client/ui/doc-files/IOrderedLayout_component_handles_the_caption.png delete mode 100644 client/src/com/vaadin/client/ui/doc-files/IOrderedLayout_h150.png delete mode 100644 client/src/com/vaadin/client/ui/doc-files/IOrderedLayout_horizontal.png delete mode 100644 client/src/com/vaadin/client/ui/doc-files/IOrderedLayout_horizontal_spacing.png delete mode 100644 client/src/com/vaadin/client/ui/doc-files/IOrderedLayout_margin.png delete mode 100644 client/src/com/vaadin/client/ui/doc-files/IOrderedLayout_no_caption.png delete mode 100644 client/src/com/vaadin/client/ui/doc-files/IOrderedLayout_normal_caption.png delete mode 100644 client/src/com/vaadin/client/ui/doc-files/IOrderedLayout_special-margin.png delete mode 100644 client/src/com/vaadin/client/ui/doc-files/IOrderedLayout_vertical.png delete mode 100644 client/src/com/vaadin/client/ui/doc-files/IOrderedLayout_vertical_spacing.png delete mode 100644 client/src/com/vaadin/client/ui/doc-files/IOrderedLayout_w300.png delete mode 100644 client/src/com/vaadin/client/ui/doc-files/IOrderedLayout_w300_h150.png delete mode 100644 client/src/com/vaadin/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java delete mode 100644 client/src/com/vaadin/client/ui/embedded/EmbeddedConnector.java delete mode 100644 client/src/com/vaadin/client/ui/flash/FlashConnector.java delete mode 100644 client/src/com/vaadin/client/ui/form/FormConnector.java delete mode 100644 client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java delete mode 100644 client/src/com/vaadin/client/ui/gridlayout/GridLayoutConnector.java delete mode 100644 client/src/com/vaadin/client/ui/image/ImageConnector.java delete mode 100644 client/src/com/vaadin/client/ui/label/LabelConnector.java delete mode 100644 client/src/com/vaadin/client/ui/layout/ComponentConnectorLayoutSlot.java delete mode 100644 client/src/com/vaadin/client/ui/layout/ElementResizeEvent.java delete mode 100644 client/src/com/vaadin/client/ui/layout/ElementResizeListener.java delete mode 100644 client/src/com/vaadin/client/ui/layout/LayoutDependencyTree.java delete mode 100644 client/src/com/vaadin/client/ui/layout/Margins.java delete mode 100644 client/src/com/vaadin/client/ui/layout/MayScrollChildren.java delete mode 100644 client/src/com/vaadin/client/ui/layout/VLayoutSlot.java delete mode 100644 client/src/com/vaadin/client/ui/link/LinkConnector.java delete mode 100644 client/src/com/vaadin/client/ui/listselect/ListSelectConnector.java delete mode 100644 client/src/com/vaadin/client/ui/menubar/MenuBar.java delete mode 100644 client/src/com/vaadin/client/ui/menubar/MenuBarConnector.java delete mode 100644 client/src/com/vaadin/client/ui/menubar/MenuItem.java delete mode 100644 client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java delete mode 100644 client/src/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java delete mode 100644 client/src/com/vaadin/client/ui/optiongroup/OptionGroupBaseConnector.java delete mode 100644 client/src/com/vaadin/client/ui/optiongroup/OptionGroupConnector.java delete mode 100644 client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java delete mode 100644 client/src/com/vaadin/client/ui/orderedlayout/CaptionPosition.java delete mode 100644 client/src/com/vaadin/client/ui/orderedlayout/HorizontalLayoutConnector.java delete mode 100644 client/src/com/vaadin/client/ui/orderedlayout/Slot.java delete mode 100644 client/src/com/vaadin/client/ui/orderedlayout/VAbstractOrderedLayout.java delete mode 100644 client/src/com/vaadin/client/ui/orderedlayout/VerticalLayoutConnector.java delete mode 100644 client/src/com/vaadin/client/ui/panel/PanelConnector.java delete mode 100644 client/src/com/vaadin/client/ui/passwordfield/PasswordFieldConnector.java delete mode 100644 client/src/com/vaadin/client/ui/popupview/PopupViewConnector.java delete mode 100644 client/src/com/vaadin/client/ui/popupview/VisibilityChangeEvent.java delete mode 100644 client/src/com/vaadin/client/ui/popupview/VisibilityChangeHandler.java delete mode 100644 client/src/com/vaadin/client/ui/progressindicator/ProgressBarConnector.java delete mode 100644 client/src/com/vaadin/client/ui/progressindicator/ProgressIndicatorConnector.java delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/backColors.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/bold.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/createLink.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/fontSizes.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/fonts.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/foreColors.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/gwtLogo.png delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/hr.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/indent.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/insertImage.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/italic.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/justifyCenter.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/justifyLeft.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/justifyRight.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/ol.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/outdent.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/removeFormat.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/removeLink.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/strikeThrough.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/subscript.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/superscript.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/ul.gif delete mode 100644 client/src/com/vaadin/client/ui/richtextarea/underline.gif delete mode 100644 client/src/com/vaadin/client/ui/slider/SliderConnector.java delete mode 100644 client/src/com/vaadin/client/ui/splitpanel/AbstractSplitPanelConnector.java delete mode 100644 client/src/com/vaadin/client/ui/splitpanel/HorizontalSplitPanelConnector.java delete mode 100644 client/src/com/vaadin/client/ui/splitpanel/VerticalSplitPanelConnector.java delete mode 100644 client/src/com/vaadin/client/ui/table/TableConnector.java delete mode 100644 client/src/com/vaadin/client/ui/tabsheet/TabsheetBaseConnector.java delete mode 100644 client/src/com/vaadin/client/ui/tabsheet/TabsheetConnector.java delete mode 100644 client/src/com/vaadin/client/ui/textarea/TextAreaConnector.java delete mode 100644 client/src/com/vaadin/client/ui/textfield/TextFieldConnector.java delete mode 100644 client/src/com/vaadin/client/ui/tree/TreeConnector.java delete mode 100644 client/src/com/vaadin/client/ui/treetable/TreeTableConnector.java delete mode 100644 client/src/com/vaadin/client/ui/twincolselect/TwinColSelectConnector.java delete mode 100644 client/src/com/vaadin/client/ui/ui/UIConnector.java delete mode 100644 client/src/com/vaadin/client/ui/upload/UploadConnector.java delete mode 100644 client/src/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategy.java delete mode 100644 client/src/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategyIE.java delete mode 100644 client/src/com/vaadin/client/ui/video/VideoConnector.java delete mode 100644 client/src/com/vaadin/client/ui/window/WindowConnector.java delete mode 100644 client/src/com/vaadin/client/ui/window/WindowMoveEvent.java delete mode 100644 client/src/com/vaadin/client/ui/window/WindowMoveHandler.java delete mode 100644 client/src/com/vaadin/client/widget/escalator/Cell.java delete mode 100644 client/src/com/vaadin/client/widget/escalator/ColumnConfiguration.java delete mode 100644 client/src/com/vaadin/client/widget/escalator/EscalatorUpdater.java delete mode 100644 client/src/com/vaadin/client/widget/escalator/FlyweightCell.java delete mode 100644 client/src/com/vaadin/client/widget/escalator/FlyweightRow.java delete mode 100644 client/src/com/vaadin/client/widget/escalator/PositionFunction.java delete mode 100644 client/src/com/vaadin/client/widget/escalator/Row.java delete mode 100644 client/src/com/vaadin/client/widget/escalator/RowContainer.java delete mode 100644 client/src/com/vaadin/client/widget/escalator/RowVisibilityChangeEvent.java delete mode 100644 client/src/com/vaadin/client/widget/escalator/RowVisibilityChangeHandler.java delete mode 100644 client/src/com/vaadin/client/widget/escalator/ScrollbarBundle.java delete mode 100644 client/src/com/vaadin/client/widget/escalator/Spacer.java delete mode 100644 client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java delete mode 100644 client/src/com/vaadin/client/widget/grid/AutoScroller.java delete mode 100644 client/src/com/vaadin/client/widget/grid/CellReference.java delete mode 100644 client/src/com/vaadin/client/widget/grid/CellStyleGenerator.java delete mode 100644 client/src/com/vaadin/client/widget/grid/DataAvailableEvent.java delete mode 100644 client/src/com/vaadin/client/widget/grid/DataAvailableHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/DetailsGenerator.java delete mode 100644 client/src/com/vaadin/client/widget/grid/EditorHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/EventCellReference.java delete mode 100644 client/src/com/vaadin/client/widget/grid/HeightAwareDetailsGenerator.java delete mode 100644 client/src/com/vaadin/client/widget/grid/RendererCellReference.java delete mode 100644 client/src/com/vaadin/client/widget/grid/RowReference.java delete mode 100644 client/src/com/vaadin/client/widget/grid/RowStyleGenerator.java delete mode 100644 client/src/com/vaadin/client/widget/grid/datasources/ListDataSource.java delete mode 100644 client/src/com/vaadin/client/widget/grid/datasources/ListSorter.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/AbstractGridKeyEventHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/AbstractGridMouseEventHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/BodyClickHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/BodyDoubleClickHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/BodyKeyDownHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/BodyKeyPressHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/BodyKeyUpHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/ColumnResizeEvent.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/ColumnResizeHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/FooterClickHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/FooterDoubleClickHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/FooterKeyDownHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/FooterKeyPressHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/FooterKeyUpHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/GridClickEvent.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/GridDoubleClickEvent.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/GridKeyDownEvent.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/GridKeyPressEvent.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/GridKeyUpEvent.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/HeaderClickHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/HeaderDoubleClickHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/HeaderKeyDownHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/HeaderKeyPressHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/HeaderKeyUpHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/ScrollEvent.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/ScrollHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/SelectAllEvent.java delete mode 100644 client/src/com/vaadin/client/widget/grid/events/SelectAllHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/selection/AbstractRowHandleSelectionModel.java delete mode 100644 client/src/com/vaadin/client/widget/grid/selection/ClickSelectHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/selection/HasSelectionHandlers.java delete mode 100644 client/src/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java delete mode 100644 client/src/com/vaadin/client/widget/grid/selection/SelectionEvent.java delete mode 100644 client/src/com/vaadin/client/widget/grid/selection/SelectionHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/selection/SelectionModel.java delete mode 100644 client/src/com/vaadin/client/widget/grid/selection/SelectionModelMulti.java delete mode 100644 client/src/com/vaadin/client/widget/grid/selection/SelectionModelNone.java delete mode 100644 client/src/com/vaadin/client/widget/grid/selection/SelectionModelSingle.java delete mode 100644 client/src/com/vaadin/client/widget/grid/selection/SpaceSelectHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/sort/Sort.java delete mode 100644 client/src/com/vaadin/client/widget/grid/sort/SortEvent.java delete mode 100644 client/src/com/vaadin/client/widget/grid/sort/SortHandler.java delete mode 100644 client/src/com/vaadin/client/widget/grid/sort/SortOrder.java delete mode 100644 client/src/com/vaadin/client/widgets/Escalator.java delete mode 100644 client/src/com/vaadin/client/widgets/Grid.java delete mode 100644 client/src/com/vaadin/client/widgets/Overlay.java delete mode 100644 client/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml create mode 100644 client/src/main/java/com/vaadin/client/AnimationUtil.java create mode 100644 client/src/main/java/com/vaadin/client/ApplicationConfiguration.java create mode 100644 client/src/main/java/com/vaadin/client/ApplicationConnection.java create mode 100644 client/src/main/java/com/vaadin/client/BrowserInfo.java create mode 100644 client/src/main/java/com/vaadin/client/CSSRule.java create mode 100644 client/src/main/java/com/vaadin/client/ComponentConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ComponentDetail.java create mode 100644 client/src/main/java/com/vaadin/client/ComponentDetailMap.java create mode 100644 client/src/main/java/com/vaadin/client/ComputedStyle.java create mode 100644 client/src/main/java/com/vaadin/client/ConnectorHierarchyChangeEvent.java create mode 100644 client/src/main/java/com/vaadin/client/ConnectorMap.java create mode 100644 client/src/main/java/com/vaadin/client/ContainerResizedListener.java create mode 100644 client/src/main/java/com/vaadin/client/DateTimeService.java create mode 100644 client/src/main/java/com/vaadin/client/DeferredWorker.java create mode 100644 client/src/main/java/com/vaadin/client/DirectionalManagedLayout.java create mode 100644 client/src/main/java/com/vaadin/client/EventHelper.java create mode 100644 client/src/main/java/com/vaadin/client/FastStringMap.java create mode 100644 client/src/main/java/com/vaadin/client/FastStringSet.java create mode 100644 client/src/main/java/com/vaadin/client/Focusable.java create mode 100644 client/src/main/java/com/vaadin/client/HasChildMeasurementHintConnector.java create mode 100644 client/src/main/java/com/vaadin/client/HasComponentsConnector.java create mode 100644 client/src/main/java/com/vaadin/client/JavaScriptConnectorHelper.java create mode 100644 client/src/main/java/com/vaadin/client/JavaScriptExtension.java create mode 100644 client/src/main/java/com/vaadin/client/JsArrayObject.java create mode 100644 client/src/main/java/com/vaadin/client/LayoutManager.java create mode 100644 client/src/main/java/com/vaadin/client/LayoutManagerIE8.java create mode 100644 client/src/main/java/com/vaadin/client/LocaleNotLoadedException.java create mode 100644 client/src/main/java/com/vaadin/client/LocaleService.java create mode 100644 client/src/main/java/com/vaadin/client/MeasuredSize.java create mode 100644 client/src/main/java/com/vaadin/client/MouseEventDetailsBuilder.java create mode 100644 client/src/main/java/com/vaadin/client/Paintable.java create mode 100644 client/src/main/java/com/vaadin/client/Profiler.java create mode 100644 client/src/main/java/com/vaadin/client/RenderInformation.java create mode 100644 client/src/main/java/com/vaadin/client/RenderSpace.java create mode 100644 client/src/main/java/com/vaadin/client/ResourceLoader.java create mode 100644 client/src/main/java/com/vaadin/client/ServerConnector.java create mode 100644 client/src/main/java/com/vaadin/client/SimpleTree.java create mode 100644 client/src/main/java/com/vaadin/client/StyleConstants.java create mode 100644 client/src/main/java/com/vaadin/client/SuperDevMode.java create mode 100644 client/src/main/java/com/vaadin/client/TooltipInfo.java create mode 100644 client/src/main/java/com/vaadin/client/UIDL.java create mode 100644 client/src/main/java/com/vaadin/client/Util.java create mode 100644 client/src/main/java/com/vaadin/client/VCaption.java create mode 100644 client/src/main/java/com/vaadin/client/VCaptionWrapper.java create mode 100644 client/src/main/java/com/vaadin/client/VConsole.java create mode 100644 client/src/main/java/com/vaadin/client/VErrorMessage.java create mode 100644 client/src/main/java/com/vaadin/client/VLoadingIndicator.java create mode 100644 client/src/main/java/com/vaadin/client/VSchedulerImpl.java create mode 100644 client/src/main/java/com/vaadin/client/VTooltip.java create mode 100644 client/src/main/java/com/vaadin/client/VUIDLBrowser.java create mode 100644 client/src/main/java/com/vaadin/client/ValueMap.java create mode 100644 client/src/main/java/com/vaadin/client/WidgetInstantiator.java create mode 100644 client/src/main/java/com/vaadin/client/WidgetLoader.java create mode 100644 client/src/main/java/com/vaadin/client/WidgetMap.java create mode 100644 client/src/main/java/com/vaadin/client/WidgetSet.java create mode 100644 client/src/main/java/com/vaadin/client/WidgetUtil.java create mode 100644 client/src/main/java/com/vaadin/client/annotations/OnStateChange.java create mode 100644 client/src/main/java/com/vaadin/client/communication/AbstractServerConnectorEvent.java create mode 100644 client/src/main/java/com/vaadin/client/communication/AtmospherePushConnection.java create mode 100644 client/src/main/java/com/vaadin/client/communication/ConnectionStateHandler.java create mode 100644 client/src/main/java/com/vaadin/client/communication/Date_Serializer.java create mode 100644 client/src/main/java/com/vaadin/client/communication/DefaultConnectionStateHandler.java create mode 100644 client/src/main/java/com/vaadin/client/communication/DefaultReconnectDialog.java create mode 100644 client/src/main/java/com/vaadin/client/communication/DiffJSONSerializer.java create mode 100644 client/src/main/java/com/vaadin/client/communication/HasJavaScriptConnectorHelper.java create mode 100644 client/src/main/java/com/vaadin/client/communication/Heartbeat.java create mode 100644 client/src/main/java/com/vaadin/client/communication/JSONSerializer.java create mode 100644 client/src/main/java/com/vaadin/client/communication/JavaScriptMethodInvocation.java create mode 100644 client/src/main/java/com/vaadin/client/communication/JsonDecoder.java create mode 100644 client/src/main/java/com/vaadin/client/communication/JsonEncoder.java create mode 100644 client/src/main/java/com/vaadin/client/communication/MessageHandler.java create mode 100644 client/src/main/java/com/vaadin/client/communication/MessageSender.java create mode 100644 client/src/main/java/com/vaadin/client/communication/PushConnection.java create mode 100644 client/src/main/java/com/vaadin/client/communication/ReconnectDialog.java create mode 100644 client/src/main/java/com/vaadin/client/communication/RpcManager.java create mode 100644 client/src/main/java/com/vaadin/client/communication/RpcProxy.java create mode 100644 client/src/main/java/com/vaadin/client/communication/ServerRpcQueue.java create mode 100644 client/src/main/java/com/vaadin/client/communication/StateChangeEvent.java create mode 100644 client/src/main/java/com/vaadin/client/communication/TranslatedURLReference.java create mode 100644 client/src/main/java/com/vaadin/client/communication/URLReference_Serializer.java create mode 100644 client/src/main/java/com/vaadin/client/communication/XhrConnection.java create mode 100644 client/src/main/java/com/vaadin/client/communication/XhrConnectionError.java create mode 100644 client/src/main/java/com/vaadin/client/componentlocator/ComponentLocator.java create mode 100644 client/src/main/java/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java create mode 100644 client/src/main/java/com/vaadin/client/componentlocator/LocatorStrategy.java create mode 100644 client/src/main/java/com/vaadin/client/componentlocator/LocatorUtil.java create mode 100644 client/src/main/java/com/vaadin/client/componentlocator/SelectorPredicate.java create mode 100644 client/src/main/java/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/AbstractRendererConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/AbstractSelectionModelConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/ButtonRendererConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/ClickableRendererConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/DateRendererConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/DetailComponentManagerConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/GridConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/ImageRendererConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/JavaScriptRendererConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/MultiSelectionModelConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/NoSelectionModelConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/NumberRendererConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/ProgressBarRendererConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/RpcDataSourceConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/SingleSelectionModelConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/TextRendererConnector.java create mode 100644 client/src/main/java/com/vaadin/client/connectors/UnsafeHtmlRendererConnector.java create mode 100644 client/src/main/java/com/vaadin/client/data/AbstractRemoteDataSource.java create mode 100644 client/src/main/java/com/vaadin/client/data/CacheStrategy.java create mode 100644 client/src/main/java/com/vaadin/client/data/DataChangeHandler.java create mode 100644 client/src/main/java/com/vaadin/client/data/DataSource.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/AnalyzeLayoutsPanel.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/ConnectorInfoPanel.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/DebugButton.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/ErrorNotificationHandler.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/HierarchyPanel.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/HierarchySection.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/Highlight.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/Icon.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/InfoSection.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/LogSection.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/NetworkSection.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/OptimizedWidgetsetPanel.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/ProfilerSection.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/Section.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/SelectConnectorListener.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/SelectorPath.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/TestBenchSection.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/VDebugWindow.java create mode 100644 client/src/main/java/com/vaadin/client/debug/internal/theme/DebugWindowStyles.java create mode 100644 client/src/main/java/com/vaadin/client/event/PointerCancelEvent.java create mode 100644 client/src/main/java/com/vaadin/client/event/PointerCancelHandler.java create mode 100644 client/src/main/java/com/vaadin/client/event/PointerDownEvent.java create mode 100644 client/src/main/java/com/vaadin/client/event/PointerDownHandler.java create mode 100644 client/src/main/java/com/vaadin/client/event/PointerEvent.java create mode 100644 client/src/main/java/com/vaadin/client/event/PointerEventSupport.java create mode 100644 client/src/main/java/com/vaadin/client/event/PointerEventSupportImpl.java create mode 100644 client/src/main/java/com/vaadin/client/event/PointerEventSupportImplIE10.java create mode 100644 client/src/main/java/com/vaadin/client/event/PointerEventSupportImplModernIE.java create mode 100644 client/src/main/java/com/vaadin/client/event/PointerMoveEvent.java create mode 100644 client/src/main/java/com/vaadin/client/event/PointerMoveHandler.java create mode 100644 client/src/main/java/com/vaadin/client/event/PointerUpEvent.java create mode 100644 client/src/main/java/com/vaadin/client/event/PointerUpHandler.java create mode 100644 client/src/main/java/com/vaadin/client/extensions/AbstractExtensionConnector.java create mode 100644 client/src/main/java/com/vaadin/client/extensions/BrowserWindowOpenerConnector.java create mode 100644 client/src/main/java/com/vaadin/client/extensions/FileDownloaderConnector.java create mode 100644 client/src/main/java/com/vaadin/client/extensions/ResponsiveConnector.java create mode 100644 client/src/main/java/com/vaadin/client/extensions/javascriptmanager/JavaScriptManagerConnector.java create mode 100644 client/src/main/java/com/vaadin/client/metadata/AsyncBundleLoader.java create mode 100644 client/src/main/java/com/vaadin/client/metadata/BundleLoadCallback.java create mode 100644 client/src/main/java/com/vaadin/client/metadata/ConnectorBundleLoader.java create mode 100644 client/src/main/java/com/vaadin/client/metadata/InvokationHandler.java create mode 100644 client/src/main/java/com/vaadin/client/metadata/Invoker.java create mode 100644 client/src/main/java/com/vaadin/client/metadata/JsniInvoker.java create mode 100644 client/src/main/java/com/vaadin/client/metadata/Method.java create mode 100644 client/src/main/java/com/vaadin/client/metadata/NoDataException.java create mode 100644 client/src/main/java/com/vaadin/client/metadata/OnStateChangeMethod.java create mode 100644 client/src/main/java/com/vaadin/client/metadata/Property.java create mode 100644 client/src/main/java/com/vaadin/client/metadata/ProxyHandler.java create mode 100644 client/src/main/java/com/vaadin/client/metadata/Type.java create mode 100644 client/src/main/java/com/vaadin/client/metadata/TypeData.java create mode 100644 client/src/main/java/com/vaadin/client/metadata/TypeDataStore.java create mode 100644 client/src/main/java/com/vaadin/client/renderers/ButtonRenderer.java create mode 100644 client/src/main/java/com/vaadin/client/renderers/ClickableRenderer.java create mode 100644 client/src/main/java/com/vaadin/client/renderers/ComplexRenderer.java create mode 100644 client/src/main/java/com/vaadin/client/renderers/DateRenderer.java create mode 100644 client/src/main/java/com/vaadin/client/renderers/HtmlRenderer.java create mode 100644 client/src/main/java/com/vaadin/client/renderers/ImageRenderer.java create mode 100644 client/src/main/java/com/vaadin/client/renderers/NumberRenderer.java create mode 100644 client/src/main/java/com/vaadin/client/renderers/ProgressBarRenderer.java create mode 100644 client/src/main/java/com/vaadin/client/renderers/Renderer.java create mode 100644 client/src/main/java/com/vaadin/client/renderers/TextRenderer.java create mode 100644 client/src/main/java/com/vaadin/client/renderers/WidgetRenderer.java create mode 100644 client/src/main/java/com/vaadin/client/ui/AbstractClickEventHandler.java create mode 100644 client/src/main/java/com/vaadin/client/ui/AbstractComponentConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/AbstractComponentContainerConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/AbstractConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/AbstractFieldConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/AbstractHasComponentsConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/AbstractLayoutConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/AbstractSingleComponentContainerConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/Action.java create mode 100644 client/src/main/java/com/vaadin/client/ui/ActionOwner.java create mode 100644 client/src/main/java/com/vaadin/client/ui/CalendarEntry.java create mode 100644 client/src/main/java/com/vaadin/client/ui/ClickEventHandler.java create mode 100644 client/src/main/java/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java create mode 100644 client/src/main/java/com/vaadin/client/ui/Field.java create mode 100644 client/src/main/java/com/vaadin/client/ui/FocusElementPanel.java create mode 100644 client/src/main/java/com/vaadin/client/ui/FocusUtil.java create mode 100644 client/src/main/java/com/vaadin/client/ui/FocusableFlexTable.java create mode 100644 client/src/main/java/com/vaadin/client/ui/FocusableFlowPanel.java create mode 100644 client/src/main/java/com/vaadin/client/ui/FocusableScrollPanel.java create mode 100644 client/src/main/java/com/vaadin/client/ui/FontIcon.java create mode 100644 client/src/main/java/com/vaadin/client/ui/Icon.java create mode 100644 client/src/main/java/com/vaadin/client/ui/ImageIcon.java create mode 100644 client/src/main/java/com/vaadin/client/ui/JavaScriptComponentConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/JavaScriptWidget.java create mode 100644 client/src/main/java/com/vaadin/client/ui/JsniMousewheelHandler.java create mode 100644 client/src/main/java/com/vaadin/client/ui/LayoutClickEventHandler.java create mode 100644 client/src/main/java/com/vaadin/client/ui/LegacyConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/ManagedLayout.java create mode 100644 client/src/main/java/com/vaadin/client/ui/MediaBaseConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/PostLayoutListener.java create mode 100644 client/src/main/java/com/vaadin/client/ui/ShortcutActionHandler.java create mode 100644 client/src/main/java/com/vaadin/client/ui/SimpleFocusablePanel.java create mode 100644 client/src/main/java/com/vaadin/client/ui/SimpleManagedLayout.java create mode 100644 client/src/main/java/com/vaadin/client/ui/SubPartAware.java create mode 100644 client/src/main/java/com/vaadin/client/ui/TouchScrollDelegate.java create mode 100644 client/src/main/java/com/vaadin/client/ui/TreeAction.java create mode 100644 client/src/main/java/com/vaadin/client/ui/UnknownComponentConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VAbsoluteLayout.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VAbstractSplitPanel.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VAccordion.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VAudio.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VBrowserFrame.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VButton.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VCalendar.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VCalendarPanel.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VCheckBox.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VColorPicker.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VColorPickerArea.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VContextMenu.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VCssLayout.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VCustomComponent.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VCustomLayout.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VDateField.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VDateFieldCalendar.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VDragAndDropWrapper.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VDragAndDropWrapperIE.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VEmbedded.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VFilterSelect.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VFlash.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VForm.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VFormLayout.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VGridLayout.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VHorizontalLayout.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VImage.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VLabel.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VLazyExecutor.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VLink.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VListSelect.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VMediaBase.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VMenuBar.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VNativeButton.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VNativeSelect.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VNotification.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VOptionGroup.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VOptionGroupBase.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VOverlay.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VPanel.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VPasswordField.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VPopupCalendar.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VPopupView.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VProgressBar.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VProgressIndicator.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VRichTextArea.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VScrollTable.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VSlider.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VSplitPanelHorizontal.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VSplitPanelVertical.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VTabsheet.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VTabsheetBase.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VTabsheetPanel.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VTextArea.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VTextField.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VTextualDate.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VTree.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VTreeTable.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VTwinColSelect.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VUI.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VUnknownComponent.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VUpload.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VVerticalLayout.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VVideo.java create mode 100644 client/src/main/java/com/vaadin/client/ui/VWindow.java create mode 100644 client/src/main/java/com/vaadin/client/ui/absolutelayout/AbsoluteLayoutConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/accordion/AccordionConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/aria/AriaHelper.java create mode 100644 client/src/main/java/com/vaadin/client/ui/aria/HandlesAriaCaption.java create mode 100644 client/src/main/java/com/vaadin/client/ui/aria/HandlesAriaInvalid.java create mode 100644 client/src/main/java/com/vaadin/client/ui/aria/HandlesAriaRequired.java create mode 100644 client/src/main/java/com/vaadin/client/ui/audio/AudioConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/browserframe/BrowserFrameConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/button/ButtonConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/CalendarConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/VCalendarAction.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/CalendarDay.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/CalendarEvent.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/DateCell.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/DateCellContainer.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/DateCellGroup.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/DateUtil.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/DayToolbar.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/FocusableComplexPanel.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/FocusableGrid.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/FocusableHTML.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/HasTooltipKey.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/MonthEventLabel.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/MonthGrid.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/SimpleDayToolbar.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/SimpleWeekToolbar.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/WeekGrid.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/WeekGridMinuteTimeRange.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/WeekLabel.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/WeeklyLongEvents.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/WeeklyLongEventsDateCell.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/dd/CalendarDropHandler.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/dd/CalendarMonthDropHandler.java create mode 100644 client/src/main/java/com/vaadin/client/ui/calendar/schedule/dd/CalendarWeekDropHandler.java create mode 100644 client/src/main/java/com/vaadin/client/ui/checkbox/CheckBoxConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/colorpicker/AbstractColorPickerConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/colorpicker/ColorPickerAreaConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/colorpicker/ColorPickerConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/colorpicker/ColorPickerGradientConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/colorpicker/ColorPickerGridConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/colorpicker/VColorPickerGradient.java create mode 100644 client/src/main/java/com/vaadin/client/ui/colorpicker/VColorPickerGrid.java create mode 100644 client/src/main/java/com/vaadin/client/ui/combobox/ComboBoxConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/csslayout/CssLayoutConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/customcomponent/CustomComponentConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/customfield/CustomFieldConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/customlayout/CustomLayoutConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/datefield/DateFieldConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/datefield/InlineDateFieldConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/datefield/PopupDateFieldConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/datefield/TextualDateConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/DDEventHandleStrategy.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/DDUtil.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/DragAndDropHandler.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/DragHandle.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/DragImageModifier.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VAbstractDropHandler.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VAcceptAll.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VAcceptCallback.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VAcceptCriteria.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VAcceptCriterion.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VAcceptCriterionFactory.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VAnd.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VContainsDataFlavor.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VDragAndDropManager.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VDragEvent.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VDragEventServerCallback.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VDragSourceIs.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VDropHandler.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VHasDropHandler.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VHtml5DragEvent.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VHtml5File.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VIsOverId.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VItemIdIs.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VLazyInitItemIdentifiers.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VNot.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VOr.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VOverTreeNode.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VServerAccept.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VSourceIsTarget.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VTargetDetailIs.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VTargetInSubtree.java create mode 100644 client/src/main/java/com/vaadin/client/ui/dd/VTransferable.java create mode 100644 client/src/main/java/com/vaadin/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/embedded/EmbeddedConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/flash/FlashConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/form/FormConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/formlayout/FormLayoutConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/gridlayout/GridLayoutConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/image/ImageConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/label/LabelConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/layout/ComponentConnectorLayoutSlot.java create mode 100644 client/src/main/java/com/vaadin/client/ui/layout/ElementResizeEvent.java create mode 100644 client/src/main/java/com/vaadin/client/ui/layout/ElementResizeListener.java create mode 100644 client/src/main/java/com/vaadin/client/ui/layout/LayoutDependencyTree.java create mode 100644 client/src/main/java/com/vaadin/client/ui/layout/Margins.java create mode 100644 client/src/main/java/com/vaadin/client/ui/layout/MayScrollChildren.java create mode 100644 client/src/main/java/com/vaadin/client/ui/layout/VLayoutSlot.java create mode 100644 client/src/main/java/com/vaadin/client/ui/link/LinkConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/listselect/ListSelectConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/menubar/MenuBar.java create mode 100644 client/src/main/java/com/vaadin/client/ui/menubar/MenuBarConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/menubar/MenuItem.java create mode 100644 client/src/main/java/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/optiongroup/OptionGroupBaseConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/optiongroup/OptionGroupConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/orderedlayout/CaptionPosition.java create mode 100644 client/src/main/java/com/vaadin/client/ui/orderedlayout/HorizontalLayoutConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/orderedlayout/Slot.java create mode 100644 client/src/main/java/com/vaadin/client/ui/orderedlayout/VAbstractOrderedLayout.java create mode 100644 client/src/main/java/com/vaadin/client/ui/orderedlayout/VerticalLayoutConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/panel/PanelConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/passwordfield/PasswordFieldConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/popupview/PopupViewConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/popupview/VisibilityChangeEvent.java create mode 100644 client/src/main/java/com/vaadin/client/ui/popupview/VisibilityChangeHandler.java create mode 100644 client/src/main/java/com/vaadin/client/ui/progressindicator/ProgressBarConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/progressindicator/ProgressIndicatorConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java create mode 100644 client/src/main/java/com/vaadin/client/ui/slider/SliderConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/splitpanel/AbstractSplitPanelConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/splitpanel/HorizontalSplitPanelConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/splitpanel/VerticalSplitPanelConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/table/TableConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/tabsheet/TabsheetBaseConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/tabsheet/TabsheetConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/textarea/TextAreaConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/textfield/TextFieldConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/tree/TreeConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/treetable/TreeTableConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/twincolselect/TwinColSelectConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/ui/UIConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/upload/UploadConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategy.java create mode 100644 client/src/main/java/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategyIE.java create mode 100644 client/src/main/java/com/vaadin/client/ui/video/VideoConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/window/WindowConnector.java create mode 100644 client/src/main/java/com/vaadin/client/ui/window/WindowMoveEvent.java create mode 100644 client/src/main/java/com/vaadin/client/ui/window/WindowMoveHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/escalator/Cell.java create mode 100644 client/src/main/java/com/vaadin/client/widget/escalator/ColumnConfiguration.java create mode 100644 client/src/main/java/com/vaadin/client/widget/escalator/EscalatorUpdater.java create mode 100644 client/src/main/java/com/vaadin/client/widget/escalator/FlyweightCell.java create mode 100644 client/src/main/java/com/vaadin/client/widget/escalator/FlyweightRow.java create mode 100644 client/src/main/java/com/vaadin/client/widget/escalator/PositionFunction.java create mode 100644 client/src/main/java/com/vaadin/client/widget/escalator/Row.java create mode 100644 client/src/main/java/com/vaadin/client/widget/escalator/RowContainer.java create mode 100644 client/src/main/java/com/vaadin/client/widget/escalator/RowVisibilityChangeEvent.java create mode 100644 client/src/main/java/com/vaadin/client/widget/escalator/RowVisibilityChangeHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/escalator/ScrollbarBundle.java create mode 100644 client/src/main/java/com/vaadin/client/widget/escalator/Spacer.java create mode 100644 client/src/main/java/com/vaadin/client/widget/escalator/SpacerUpdater.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/AutoScroller.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/CellReference.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/CellStyleGenerator.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/DataAvailableEvent.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/DataAvailableHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/DetailsGenerator.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/EditorHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/EventCellReference.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/HeightAwareDetailsGenerator.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/RendererCellReference.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/RowReference.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/RowStyleGenerator.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/datasources/ListDataSource.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/datasources/ListSorter.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/AbstractGridKeyEventHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/AbstractGridMouseEventHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/BodyClickHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/BodyDoubleClickHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/BodyKeyDownHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/BodyKeyPressHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/BodyKeyUpHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/ColumnResizeEvent.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/ColumnResizeHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/FooterClickHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/FooterDoubleClickHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/FooterKeyDownHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/FooterKeyPressHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/FooterKeyUpHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/GridClickEvent.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/GridDoubleClickEvent.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/GridKeyDownEvent.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/GridKeyPressEvent.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/GridKeyUpEvent.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/HeaderClickHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/HeaderDoubleClickHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/HeaderKeyDownHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/HeaderKeyPressHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/HeaderKeyUpHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/ScrollEvent.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/ScrollHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/SelectAllEvent.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/events/SelectAllHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/selection/AbstractRowHandleSelectionModel.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/selection/ClickSelectHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/selection/HasSelectionHandlers.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionEvent.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModel.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelMulti.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelNone.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelSingle.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/selection/SpaceSelectHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/sort/Sort.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/sort/SortEvent.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/sort/SortHandler.java create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/sort/SortOrder.java create mode 100644 client/src/main/java/com/vaadin/client/widgets/Escalator.java create mode 100644 client/src/main/java/com/vaadin/client/widgets/Grid.java create mode 100644 client/src/main/java/com/vaadin/client/widgets/Overlay.java create mode 100755 client/src/main/resources/com/vaadin/DefaultWidgetSet.gwt.xml create mode 100644 client/src/main/resources/com/vaadin/Vaadin.gwt.xml create mode 100644 client/src/main/resources/com/vaadin/VaadinBrowserSpecificOverrides.gwt.xml create mode 100644 client/src/main/resources/com/vaadin/client/debug/internal/theme/debugwindow.css create mode 100755 client/src/main/resources/com/vaadin/client/debug/internal/theme/font.eot create mode 100755 client/src/main/resources/com/vaadin/client/debug/internal/theme/font.svg create mode 100755 client/src/main/resources/com/vaadin/client/debug/internal/theme/font.ttf create mode 100755 client/src/main/resources/com/vaadin/client/debug/internal/theme/font.woff create mode 100644 client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_alignment.png create mode 100644 client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_component_handles_the_caption.png create mode 100644 client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_h150.png create mode 100644 client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_horizontal.png create mode 100644 client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_horizontal_spacing.png create mode 100644 client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_margin.png create mode 100644 client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_no_caption.png create mode 100644 client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_normal_caption.png create mode 100644 client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_special-margin.png create mode 100644 client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_vertical.png create mode 100644 client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_vertical_spacing.png create mode 100644 client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_w300.png create mode 100644 client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_w300_h150.png create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/backColors.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/bold.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/createLink.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/fontSizes.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/fonts.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/foreColors.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/gwtLogo.png create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/hr.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/indent.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/insertImage.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/italic.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyCenter.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyLeft.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyRight.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/ol.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/outdent.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/removeFormat.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/removeLink.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/strikeThrough.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/subscript.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/superscript.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/ul.gif create mode 100644 client/src/main/resources/com/vaadin/client/ui/richtextarea/underline.gif create mode 100644 client/src/main/resources/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml create mode 100644 client/src/test/java/com/vaadin/client/ApplicationConnectionURLGenerationTest.java create mode 100755 client/src/test/java/com/vaadin/client/DateTimeServiceTest.java create mode 100644 client/src/test/java/com/vaadin/client/LocatorUtilTest.java create mode 100644 client/src/test/java/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java create mode 100644 client/src/test/java/com/vaadin/client/communication/ServerMessageHandlerTest.java create mode 100644 client/src/test/java/com/vaadin/client/ui/grid/ListDataSourceTest.java create mode 100644 client/src/test/java/com/vaadin/client/ui/grid/PartitioningTest.java delete mode 100644 client/tests/src/com/vaadin/client/ApplicationConnectionURLGenerationTest.java delete mode 100755 client/tests/src/com/vaadin/client/DateTimeServiceTest.java delete mode 100644 client/tests/src/com/vaadin/client/LocatorUtilTest.java delete mode 100644 client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java delete mode 100644 client/tests/src/com/vaadin/client/communication/ServerMessageHandlerTest.java delete mode 100644 client/tests/src/com/vaadin/client/ui/grid/ListDataSourceTest.java delete mode 100644 client/tests/src/com/vaadin/client/ui/grid/PartitioningTest.java diff --git a/.classpath b/.classpath index ae0241e601..2cd430abfe 100644 --- a/.classpath +++ b/.classpath @@ -1,9 +1,9 @@ - + - + diff --git a/.gitignore b/.gitignore index d412437ad7..2b15b7e5b0 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ result push/target/ shared/target/ server/target/ +client/target/ .sass-cache phantomjsdriver.log diff --git a/client-compiled/ivy.xml b/client-compiled/ivy.xml index 748ce9bd56..dc7a396a4e 100644 --- a/client-compiled/ivy.xml +++ b/client-compiled/ivy.xml @@ -24,7 +24,7 @@ + rev="${vaadin.version}" conf="compile-module-> default" /> diff --git a/client-compiler/ivy.xml b/client-compiler/ivy.xml index 45031b55f9..8aa7858647 100644 --- a/client-compiler/ivy.xml +++ b/client-compiler/ivy.xml @@ -25,7 +25,7 @@ + rev="${vaadin.version}" conf="build,test -> default" /> diff --git a/client/build.xml b/client/build.xml deleted file mode 100644 index 1e65dc37c5..0000000000 --- a/client/build.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - Compiles build helpers used when building other - modules. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/client/ivy.xml b/client/ivy.xml index 7d0c88a965..e612b6786d 100644 --- a/client/ivy.xml +++ b/client/ivy.xml @@ -8,43 +8,13 @@ revision="${vaadin.version}" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - + + conf="ide-> default" /> diff --git a/client/pom.xml b/client/pom.xml new file mode 100644 index 0000000000..1d65a68487 --- /dev/null +++ b/client/pom.xml @@ -0,0 +1,218 @@ + + + 4.0.0 + + com.vaadin + vaadin-root + 7.7.0-SNAPSHOT + + com.vaadin + vaadin-client + vaadin-client + jar + + + Vaadin Ltd + + https://vaadin.com/ + Vaadin client + + + + + + com.vaadin + vaadin-shared + ${project.version} + + + + com.vaadin + vaadin-server + ${project.version} + + + com.vaadin + vaadin-sass-compiler + + + + + + + org.w3c.css + sac + 1.3 + + + + + javax.validation + validation-api + ${javax.validation.version} + provided + + + + javax.validation + validation-api + ${javax.validation.version} + sources + + + + + junit + junit + 4.11 + test + + + + org.easymock + easymock + 3.0 + test + + + + + com.vaadin.external.gwt + gwt-user + ${vaadin.gwt.version} + provided + + + com.vaadin.external.gwt + gwt-elemental + ${vaadin.gwt.version} + provided + + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.10 + + + unpack-dependencies + prepare-package + + unpack + + + + + com.vaadin.external.gwt + gwt-user + + META-INF/**, + **/*.gwtar, + com/google/gwt/*/server/**, + com/google/gwt/*/shared/**, + com/google/gwt/*/*/shared/**, + com/google/web/bindery/*/shared/**, + com/google/gwt/user/client/rpc/IsSerializable.*, + com/google/gwt/thirdparty/streamhtmlparser/**, + org/w3c/**, + javax/servlet/** + + + + com.vaadin.external.gwt + gwt-elemental + META-INF/** + + + ${project.build.directory}/classes + false + true + + + + + + + + maven-resources-plugin + + + + copy-sources + + prepare-package + + copy-resources + + + ${project.build.directory}/classes + + + src/main/resources + false + + + src/main/java + false + + + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + JavaSE-1.6 + com.vaadin.*;version="${project.version}",com.google.*;version="${project.version}" + + + + + bundle-manifest + prepare-package + + manifest + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + false + + true + + + 1 + com.vaadin.DefaultWidgetSet + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + + + + diff --git a/client/src/com/vaadin/DefaultWidgetSet.gwt.xml b/client/src/com/vaadin/DefaultWidgetSet.gwt.xml deleted file mode 100755 index ad345e44e2..0000000000 --- a/client/src/com/vaadin/DefaultWidgetSet.gwt.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/client/src/com/vaadin/Vaadin.gwt.xml b/client/src/com/vaadin/Vaadin.gwt.xml deleted file mode 100644 index 35b225560e..0000000000 --- a/client/src/com/vaadin/Vaadin.gwt.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/client/src/com/vaadin/VaadinBrowserSpecificOverrides.gwt.xml b/client/src/com/vaadin/VaadinBrowserSpecificOverrides.gwt.xml deleted file mode 100644 index ceedde50a6..0000000000 --- a/client/src/com/vaadin/VaadinBrowserSpecificOverrides.gwt.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/client/src/com/vaadin/client/AnimationUtil.java b/client/src/com/vaadin/client/AnimationUtil.java deleted file mode 100644 index 063a0a163e..0000000000 --- a/client/src/com/vaadin/client/AnimationUtil.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Style; - -/** - * Utility methods for working with CSS transitions and animations. - * - * @author Vaadin Ltd - * @since 7.3 - */ -public class AnimationUtil { - - /** - * For internal use only. May be removed or replaced in the future. - * - * Set the animation-duration CSS property. - * - * @param elem - * the element whose animation-duration to set - * @param duration - * the duration as a valid CSS value - */ - public static void setAnimationDuration(Element elem, String duration) { - Style style = elem.getStyle(); - style.setProperty(ANIMATION_PROPERTY_NAME + "Duration", duration); - } - - /** - * For internal use only. May be removed or replaced in the future. - * - * Set the animation-delay CSS property. - * - * @param elem - * the element whose animation-delay to set - * @param delay - * the delay as a valid CSS value - */ - public static void setAnimationDelay(Element elem, String delay) { - Style style = elem.getStyle(); - style.setProperty(ANIMATION_PROPERTY_NAME + "Delay", delay); - } - - /** For internal use only. May be removed or replaced in the future. */ - public static native JavaScriptObject addAnimationEndListener(Element elem, - AnimationEndListener listener) - /*-{ - var callbackFunc = $entry(function(e) { - listener.@com.vaadin.client.AnimationUtil.AnimationEndListener::onAnimationEnd(Lcom/google/gwt/dom/client/NativeEvent;)(e); - }); - - elem.addEventListener(@com.vaadin.client.AnimationUtil::ANIMATION_END_EVENT_NAME, callbackFunc, false); - - // Store function reference for later removal - if(!elem._vaadin_animationend_callbacks) { - elem._vaadin_animationend_callbacks = []; - } - elem._vaadin_animationend_callbacks.push(callbackFunc); - - return callbackFunc; - }-*/; - - /** For internal use only. May be removed or replaced in the future. */ - public static native void removeAnimationEndListener(Element elem, - JavaScriptObject listener) - /*-{ - elem.removeEventListener(@com.vaadin.client.AnimationUtil::ANIMATION_END_EVENT_NAME, listener, false); - }-*/; - - /** For internal use only. May be removed or replaced in the future. */ - public static native void removeAllAnimationEndListeners(Element elem) - /*-{ - if(elem._vaadin_animationend_callbacks) { - var callbacks = elem._vaadin_animationend_callbacks; - for(var i=0; i < callbacks.length; i++) { - elem.removeEventListener(@com.vaadin.client.AnimationUtil::ANIMATION_END_EVENT_NAME, callbacks[i], false); - } - } - }-*/; - - /** For internal use only. May be removed or replaced in the future. */ - public interface AnimationEndListener { - public void onAnimationEnd(NativeEvent event); - } - - /** For internal use only. May be removed or replaced in the future. */ - public static native String getAnimationName(NativeEvent event) - /*-{ - if(event.webkitAnimationName) - return event.webkitAnimationName; - else if(event.animationName) - return event.animationName; - else if(event.mozAnimationName) - return event.mozAnimationName; - else if(event.oAnimationName) - return event.oAnimationName; - - return ""; - }-*/; - - /** For internal use only. May be removed or replaced in the future. */ - public static native String getAnimationName(ComputedStyle cstyle) - /*-{ - var cs = cstyle.@com.vaadin.client.ComputedStyle::computedStyle; - - if(!cs.getPropertyValue) - return ""; - - if(cs.getPropertyValue("-webkit-animation-name")) - return cs.getPropertyValue("-webkit-animation-name"); - - else if(cs.getPropertyValue("animation-name")) - return cs.getPropertyValue("animation-name"); - - else if(cs.getPropertyValue("-moz-animation-name")) - return cs.getPropertyValue("-moz-animation-name"); - - else if(cs.getPropertyValue("-o-animation-name")) - return cs.getPropertyValue("-o-animation-name"); - - return ""; - }-*/; - - private static final String ANIMATION_END_EVENT_NAME = whichAnimationEndEvent(); - - private static native String whichAnimationEndEvent() - /*-{ - var el = document.createElement('fakeelement'); - var anims = { - 'animationName': 'animationend', - 'OAnimationName': 'oAnimationEnd', - 'MozAnimation': 'animationend', - 'WebkitAnimation': 'webkitAnimationEnd' - } - - for(var a in anims){ - if( el.style[a] !== undefined ){ - return anims[a]; - } - } - }-*/; - - private static final String ANIMATION_PROPERTY_NAME = whichAnimationProperty(); - - private static native String whichAnimationProperty() - /*-{ - var el = document.createElement('fakeelement'); - var anims = [ - 'animation', - 'oAnimation', - 'mozAnimation', - 'webkitAnimation' - ] - - for(var i=0; i < anims.length; i++) { - if( el.style[anims[i]] !== undefined ){ - return anims[i]; - } - } - }-*/; - -} diff --git a/client/src/com/vaadin/client/ApplicationConfiguration.java b/client/src/com/vaadin/client/ApplicationConfiguration.java deleted file mode 100644 index f14802c931..0000000000 --- a/client/src/com/vaadin/client/ApplicationConfiguration.java +++ /dev/null @@ -1,876 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.google.gwt.core.client.EntryPoint; -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.GWT.UncaughtExceptionHandler; -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.JsArrayString; -import com.google.gwt.core.client.RunAsyncCallback; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.core.client.ScriptInjector; -import com.google.gwt.dom.client.Element; -import com.google.gwt.logging.client.LogConfiguration; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.Window; -import com.vaadin.client.debug.internal.ErrorNotificationHandler; -import com.vaadin.client.debug.internal.HierarchySection; -import com.vaadin.client.debug.internal.InfoSection; -import com.vaadin.client.debug.internal.LogSection; -import com.vaadin.client.debug.internal.NetworkSection; -import com.vaadin.client.debug.internal.ProfilerSection; -import com.vaadin.client.debug.internal.Section; -import com.vaadin.client.debug.internal.TestBenchSection; -import com.vaadin.client.debug.internal.VDebugWindow; -import com.vaadin.client.debug.internal.theme.DebugWindowStyles; -import com.vaadin.client.event.PointerEventSupport; -import com.vaadin.client.metadata.BundleLoadCallback; -import com.vaadin.client.metadata.ConnectorBundleLoader; -import com.vaadin.client.metadata.NoDataException; -import com.vaadin.client.metadata.TypeData; -import com.vaadin.client.ui.UnknownComponentConnector; -import com.vaadin.client.ui.ui.UIConnector; -import com.vaadin.shared.ApplicationConstants; -import com.vaadin.shared.ui.ui.UIConstants; - -public class ApplicationConfiguration implements EntryPoint { - - /** - * Helper class for reading configuration options from the bootstap - * javascript - * - * @since 7.0 - */ - private static class JsoConfiguration extends JavaScriptObject { - protected JsoConfiguration() { - // JSO Constructor - } - - /** - * Reads a configuration parameter as a string. Please note that the - * javascript value of the parameter should also be a string, or else an - * undefined exception may be thrown. - * - * @param name - * name of the configuration parameter - * @return value of the configuration parameter, or null if - * not defined - */ - private native String getConfigString(String name) - /*-{ - var value = this.getConfig(name); - if (value === null || value === undefined) { - return null; - } else { - return value +""; - } - }-*/; - - /** - * Reads a configuration parameter as a boolean object. Please note that - * the javascript value of the parameter should also be a boolean, or - * else an undefined exception may be thrown. - * - * @param name - * name of the configuration parameter - * @return boolean value of the configuration paramter, or - * null if no value is defined - */ - private native Boolean getConfigBoolean(String name) - /*-{ - var value = this.getConfig(name); - if (value === null || value === undefined) { - return null; - } else { - // $entry not needed as function is not exported - return @java.lang.Boolean::valueOf(Z)(value); - } - }-*/; - - /** - * Reads a configuration parameter as an integer object. Please note - * that the javascript value of the parameter should also be an integer, - * or else an undefined exception may be thrown. - * - * @param name - * name of the configuration parameter - * @return integer value of the configuration paramter, or - * null if no value is defined - */ - private native Integer getConfigInteger(String name) - /*-{ - var value = this.getConfig(name); - if (value === null || value === undefined) { - return null; - } else { - // $entry not needed as function is not exported - return @java.lang.Integer::valueOf(I)(value); - } - }-*/; - - /** - * Reads a configuration parameter as an {@link ErrorMessage} object. - * Please note that the javascript value of the parameter should also be - * an object with appropriate fields, or else an undefined exception may - * be thrown when calling this method or when calling methods on the - * returned object. - * - * @param name - * name of the configuration parameter - * @return error message with the given name, or null if no - * value is defined - */ - private native ErrorMessage getConfigError(String name) - /*-{ - return this.getConfig(name); - }-*/; - - /** - * Returns a native javascript object containing version information - * from the server. - * - * @return a javascript object with the version information - */ - private native JavaScriptObject getVersionInfoJSObject() - /*-{ - return this.getConfig("versionInfo"); - }-*/; - - /** - * Gets the version of the Vaadin framework used on the server. - * - * @return a string with the version - * - * @see com.vaadin.server.VaadinServlet#VERSION - */ - private native String getVaadinVersion() - /*-{ - return this.getConfig("versionInfo").vaadinVersion; - }-*/; - - /** - * Gets the version of the Atmosphere framework. - * - * @return a string with the version - * - * @see org.atmosphere.util#getRawVersion() - */ - private native String getAtmosphereVersion() - /*-{ - return this.getConfig("versionInfo").atmosphereVersion; - }-*/; - - /** - * Gets the JS version used in the Atmosphere framework. - * - * @return a string with the version - */ - private native String getAtmosphereJSVersion() - /*-{ - if ($wnd.jQueryVaadin != undefined){ - return $wnd.jQueryVaadin.atmosphere.version; - } - else { - return null; - } - }-*/; - - private native String getUIDL() - /*-{ - return this.getConfig("uidl"); - }-*/; - } - - /** - * Wraps a native javascript object containing fields for an error message - * - * @since 7.0 - */ - public static final class ErrorMessage extends JavaScriptObject { - - protected ErrorMessage() { - // JSO constructor - } - - public final native String getCaption() - /*-{ - return this.caption; - }-*/; - - public final native String getMessage() - /*-{ - return this.message; - }-*/; - - public final native String getUrl() - /*-{ - return this.url; - }-*/; - } - - private static WidgetSet widgetSet = GWT.create(WidgetSet.class); - - private String id; - /** - * The URL to the VAADIN directory containing themes and widgetsets. Should - * always end with a slash (/). - */ - private String vaadinDirUrl; - private String serviceUrl; - private int uiId; - private boolean standalone; - private ErrorMessage communicationError; - private ErrorMessage authorizationError; - private ErrorMessage sessionExpiredError; - private int heartbeatInterval; - - private HashMap unknownComponents; - - private Map> classes = new HashMap>(); - - private boolean widgetsetVersionSent = false; - private static boolean moduleLoaded = false; - - static// TODO consider to make this hashmap per application - LinkedList callbacks = new LinkedList(); - - private static int dependenciesLoading; - - private static ArrayList runningApplications = new ArrayList(); - - private Map componentInheritanceMap = new HashMap(); - private Map tagToServerSideClassName = new HashMap(); - - /** - * Checks whether path info in requests to the server-side service should be - * in a request parameter (named v-resourcePath) or appended to - * the end of the service URL. - * - * @see #getServiceUrl() - * - * @return true if path info should be a request parameter; - * false if the path info goes after the service URL - */ - public boolean useServiceUrlPathParam() { - return getServiceUrlParameterName() != null; - } - - /** - * Return the name of the parameter used to to send data to the service url. - * This method should only be called if {@link #useServiceUrlPathParam()} is - * true. - * - * @since 7.1.6 - * @return The parameter name, by default v-resourcePath - */ - public String getServiceUrlParameterName() { - return getJsoConfiguration(id).getConfigString( - ApplicationConstants.SERVICE_URL_PARAMETER_NAME); - } - - public String getRootPanelId() { - return id; - } - - /** - * Gets the URL to the server-side VaadinService. If - * {@link #useServiceUrlPathParam()} return true, the requested - * path info should be in the v-resourcePath query parameter; - * else the path info should be appended to the end of the URL. - * - * @see #useServiceUrlPathParam() - * - * @return the URL to the server-side service as a string - */ - public String getServiceUrl() { - return serviceUrl; - } - - /** - * @return the theme name used when initializing the application - * @deprecated as of 7.3. Use {@link UIConnector#getActiveTheme()} to get - * the theme currently in use - */ - @Deprecated - public String getThemeName() { - return getJsoConfiguration(id).getConfigString("theme"); - } - - /** - * Gets the URL of the VAADIN directory on the server. - * - * @return the URL of the VAADIN directory - */ - public String getVaadinDirUrl() { - return vaadinDirUrl; - } - - public void setAppId(String appId) { - id = appId; - } - - /** - * Gets the initial UIDL from the DOM, if it was provided during the init - * process. - * - * @return - */ - public String getUIDL() { - return getJsoConfiguration(id).getUIDL(); - } - - /** - * @return true if the application is served by std. Vaadin servlet and is - * considered to be the only or main content of the host page. - */ - public boolean isStandalone() { - return standalone; - } - - /** - * Gets the UI id of the server-side UI associated with this client-side - * instance. The UI id should be included in every request originating from - * this instance in order to associate the request with the right UI - * instance on the server. - * - * @return the UI id - */ - public int getUIId() { - return uiId; - } - - /** - * @return The interval in seconds between heartbeat requests, or a - * non-positive number if heartbeat is disabled. - */ - public int getHeartbeatInterval() { - return heartbeatInterval; - } - - public JavaScriptObject getVersionInfoJSObject() { - return getJsoConfiguration(id).getVersionInfoJSObject(); - } - - public ErrorMessage getCommunicationError() { - return communicationError; - } - - public ErrorMessage getAuthorizationError() { - return authorizationError; - } - - public ErrorMessage getSessionExpiredError() { - return sessionExpiredError; - } - - /** - * Reads the configuration values defined by the bootstrap javascript. - */ - private void loadFromDOM() { - JsoConfiguration jsoConfiguration = getJsoConfiguration(id); - serviceUrl = jsoConfiguration - .getConfigString(ApplicationConstants.SERVICE_URL); - if (serviceUrl == null || "".equals(serviceUrl)) { - /* - * Use the current url without query parameters and fragment as the - * default value. - */ - serviceUrl = Window.Location.getHref().replaceFirst("[?#].*", ""); - } else { - /* - * Resolve potentially relative URLs to ensure they point to the - * desired locations even if the base URL of the page changes later - * (e.g. with pushState) - */ - serviceUrl = WidgetUtil.getAbsoluteUrl(serviceUrl); - } - // Ensure there's an ending slash (to make appending e.g. UIDL work) - if (!useServiceUrlPathParam() && !serviceUrl.endsWith("/")) { - serviceUrl += '/'; - } - - vaadinDirUrl = WidgetUtil.getAbsoluteUrl(jsoConfiguration - .getConfigString(ApplicationConstants.VAADIN_DIR_URL)); - uiId = jsoConfiguration.getConfigInteger(UIConstants.UI_ID_PARAMETER) - .intValue(); - - // null -> false - standalone = jsoConfiguration.getConfigBoolean("standalone") == Boolean.TRUE; - - heartbeatInterval = jsoConfiguration - .getConfigInteger("heartbeatInterval"); - - communicationError = jsoConfiguration.getConfigError("comErrMsg"); - authorizationError = jsoConfiguration.getConfigError("authErrMsg"); - sessionExpiredError = jsoConfiguration.getConfigError("sessExpMsg"); - } - - /** - * Starts the application with a given id by reading the configuration - * options stored by the bootstrap javascript. - * - * @param applicationId - * id of the application to load, this is also the id of the html - * element into which the application should be rendered. - */ - public static void startApplication(final String applicationId) { - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - - @Override - public void execute() { - Profiler.enter("ApplicationConfiguration.startApplication"); - ApplicationConfiguration appConf = getConfigFromDOM(applicationId); - ApplicationConnection a = GWT - .create(ApplicationConnection.class); - a.init(widgetSet, appConf); - runningApplications.add(a); - Profiler.leave("ApplicationConfiguration.startApplication"); - - a.start(); - } - }); - } - - public static List getRunningApplications() { - return runningApplications; - } - - /** - * Gets the configuration object for a specific application from the - * bootstrap javascript. - * - * @param appId - * the id of the application to get configuration data for - * @return a native javascript object containing the configuration data - */ - private native static JsoConfiguration getJsoConfiguration(String appId) - /*-{ - return $wnd.vaadin.getApp(appId); - }-*/; - - public static ApplicationConfiguration getConfigFromDOM(String appId) { - ApplicationConfiguration conf = new ApplicationConfiguration(); - conf.setAppId(appId); - conf.loadFromDOM(); - return conf; - } - - public String getServletVersion() { - return getJsoConfiguration(id).getVaadinVersion(); - } - - /** - * Return Atmosphere version. - * - * @since 7.4 - * - * @return Atmosphere version. - */ - public String getAtmosphereVersion() { - return getJsoConfiguration(id).getAtmosphereVersion(); - } - - /** - * Return Atmosphere JS version. - * - * @since 7.4 - * - * @return Atmosphere JS version. - */ - public String getAtmosphereJSVersion() { - return getJsoConfiguration(id).getAtmosphereJSVersion(); - } - - public Class getConnectorClassByEncodedTag( - int tag) { - Class type = classes.get(tag); - if (type == null && !classes.containsKey(tag)) { - // Initialize if not already loaded - Integer currentTag = Integer.valueOf(tag); - while (type == null && currentTag != null) { - String serverSideClassNameForTag = getServerSideClassNameForTag(currentTag); - if (TypeData.hasIdentifier(serverSideClassNameForTag)) { - try { - type = (Class) TypeData - .getClass(serverSideClassNameForTag); - } catch (NoDataException e) { - throw new RuntimeException(e); - } - } - currentTag = getParentTag(currentTag.intValue()); - } - if (type == null) { - type = UnknownComponentConnector.class; - if (unknownComponents == null) { - unknownComponents = new HashMap(); - } - unknownComponents.put(tag, getServerSideClassNameForTag(tag)); - } - classes.put(tag, type); - } - return type; - } - - public void addComponentInheritanceInfo(ValueMap valueMap) { - JsArrayString keyArray = valueMap.getKeyArray(); - for (int i = 0; i < keyArray.length(); i++) { - String key = keyArray.get(i); - int value = valueMap.getInt(key); - componentInheritanceMap.put(Integer.parseInt(key), value); - } - } - - public void addComponentMappings(ValueMap valueMap, WidgetSet widgetSet) { - JsArrayString keyArray = valueMap.getKeyArray(); - for (int i = 0; i < keyArray.length(); i++) { - String key = keyArray.get(i).intern(); - int value = valueMap.getInt(key); - tagToServerSideClassName.put(value, key); - } - - for (int i = 0; i < keyArray.length(); i++) { - String key = keyArray.get(i).intern(); - int value = valueMap.getInt(key); - widgetSet.ensureConnectorLoaded(value, this); - } - } - - /** - * Returns all tags for given class. Tags are used in - * {@link ApplicationConfiguration} to keep track of different classes and - * their hierarchy - * - * @since 7.2 - * @param classname - * name of class which tags we want - * @return Integer array of tags pointing to this classname - */ - public Integer[] getTagsForServerSideClassName(String classname) { - List tags = new ArrayList(); - - for (Map.Entry entry : tagToServerSideClassName - .entrySet()) { - if (classname.equals(entry.getValue())) { - tags.add(entry.getKey()); - } - } - - Integer[] out = new Integer[tags.size()]; - return tags.toArray(out); - } - - public Integer getParentTag(int tag) { - return componentInheritanceMap.get(tag); - } - - public String getServerSideClassNameForTag(Integer tag) { - return tagToServerSideClassName.get(tag); - } - - String getUnknownServerClassNameByTag(int tag) { - if (unknownComponents != null) { - String className = unknownComponents.get(tag); - if (className == null) { - className = "unknown class with id " + tag; - } - return className; - } - return null; - } - - /** - * @since 7.6 - * @param c - */ - public static void runWhenDependenciesLoaded(Command c) { - if (dependenciesLoading == 0) { - c.execute(); - } else { - callbacks.add(c); - } - } - - static void startDependencyLoading() { - dependenciesLoading++; - } - - static void endDependencyLoading() { - dependenciesLoading--; - if (dependenciesLoading == 0 && !callbacks.isEmpty()) { - for (Command cmd : callbacks) { - cmd.execute(); - } - callbacks.clear(); - } else if (dependenciesLoading == 0 - && !ConnectorBundleLoader.get().isBundleLoaded( - ConnectorBundleLoader.DEFERRED_BUNDLE_NAME)) { - ConnectorBundleLoader.get().loadBundle( - ConnectorBundleLoader.DEFERRED_BUNDLE_NAME, - new BundleLoadCallback() { - @Override - public void loaded() { - // Nothing to do - } - - @Override - public void failed(Throwable reason) { - getLogger().log(Level.SEVERE, - "Error loading deferred bundle", reason); - } - }); - } - } - - private boolean vaadinBootstrapLoaded() { - Element window = ScriptInjector.TOP_WINDOW.cast(); - return window.getPropertyJSO("vaadin") != null; - } - - @Override - public void onModuleLoad() { - - // Don't run twice if the module has been inherited several times, - // and don't continue if vaadinBootstrap was not executed. - if (moduleLoaded || !vaadinBootstrapLoaded()) { - getLogger() - .log(Level.WARNING, - "vaadinBootstrap.js was not loaded, skipping vaadin application configuration."); - return; - } - moduleLoaded = true; - - Profiler.initialize(); - Profiler.enter("ApplicationConfiguration.onModuleLoad"); - - BrowserInfo browserInfo = BrowserInfo.get(); - - // Enable iOS6 cast fix (see #10460) - if (browserInfo.isIOS6() && browserInfo.isWebkit()) { - enableIOS6castFix(); - } - - // Enable IE prompt fix (#13367) - if (browserInfo.isIE() && browserInfo.getBrowserMajorVersion() >= 10) { - enableIEPromptFix(); - } - - // Register pointer events (must be done before any events are used) - PointerEventSupport.init(); - - // Prepare the debugging window - if (isDebugMode()) { - /* - * XXX Lots of implementation details here right now. This should be - * cleared up when an API for extending the debug window is - * implemented. - */ - VDebugWindow window = VDebugWindow.get(); - - if (LogConfiguration.loggingIsEnabled()) { - window.addSection((Section) GWT.create(LogSection.class)); - } - window.addSection((Section) GWT.create(InfoSection.class)); - window.addSection((Section) GWT.create(HierarchySection.class)); - window.addSection((Section) GWT.create(NetworkSection.class)); - window.addSection((Section) GWT.create(TestBenchSection.class)); - if (Profiler.isEnabled()) { - window.addSection((Section) GWT.create(ProfilerSection.class)); - } - - if (isQuietDebugMode()) { - window.close(); - } else { - // Load debug window styles asynchronously - GWT.runAsync(new RunAsyncCallback() { - @Override - public void onSuccess() { - DebugWindowStyles dws = GWT - .create(DebugWindowStyles.class); - dws.css().ensureInjected(); - } - - @Override - public void onFailure(Throwable reason) { - Window.alert("Failed to load Vaadin debug window styles"); - } - }); - - window.init(); - } - - // Connect to the legacy API - VConsole.setImplementation(window); - - Handler errorNotificationHandler = GWT - .create(ErrorNotificationHandler.class); - Logger.getLogger("").addHandler(errorNotificationHandler); - } - - if (LogConfiguration.loggingIsEnabled()) { - GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { - - @Override - public void onUncaughtException(Throwable e) { - /* - * If the debug window is not enabled (?debug), this will - * not show anything to normal users. "a1 is not an object" - * style errors helps nobody, especially end user. It does - * not work tells just as much. - */ - getLogger().log(Level.SEVERE, e.getMessage(), e); - } - }); - - if (isProductionMode()) { - // Disable all logging if in production mode - Logger.getLogger("").setLevel(Level.OFF); - } - } - Profiler.leave("ApplicationConfiguration.onModuleLoad"); - - if (SuperDevMode.enableBasedOnParameter()) { - // Do not start any application as super dev mode will refresh the - // page once done compiling - return; - } - registerCallback(GWT.getModuleName()); - } - - /** - * Fix to iOS6 failing when comparing with 0 directly after the kind of - * comparison done by GWT when a double or float is cast to an int. Forcing - * another trivial operation (other than a compare to 0) after the dangerous - * comparison makes the issue go away. See #10460. - */ - private static native void enableIOS6castFix() - /*-{ - Math.max = function(a,b) {return (a > b === 1 < 2)? a : b} - Math.min = function(a,b) {return (a < b === 1 < 2)? a : b} - }-*/; - - /** - * Make Metro versions of IE suggest switching to the desktop when - * window.prompt is called. - */ - private static native void enableIEPromptFix() - /*-{ - var prompt = $wnd.prompt; - $wnd.prompt = function () { - var result = prompt.apply($wnd, Array.prototype.slice.call(arguments)); - if (result === undefined) { - // force the browser to suggest desktop mode - showModalDialog(); - return null; - } else { - return result; - } - }; - }-*/; - - /** - * Registers that callback that the bootstrap javascript uses to start - * applications once the widgetset is loaded and all required information is - * available - * - * @param widgetsetName - * the name of this widgetset - */ - public native static void registerCallback(String widgetsetName) - /*-{ - var callbackHandler = $entry(@com.vaadin.client.ApplicationConfiguration::startApplication(Ljava/lang/String;)); - $wnd.vaadin.registerWidgetset(widgetsetName, callbackHandler); - }-*/; - - /** - * Checks if client side is in debug mode. Practically this is invoked by - * adding ?debug parameter to URI. Please note that debug mode is always - * disabled if production mode is enabled, but disabling production mode - * does not automatically enable debug mode. - * - * @see #isProductionMode() - * - * @return true if client side is currently been debugged - */ - public static boolean isDebugMode() { - return isDebugAvailable() - && Window.Location.getParameter("debug") != null; - } - - /** - * Checks if production mode is enabled. When production mode is enabled, - * client-side logging is disabled. There may also be other performance - * optimizations. - * - * @since 7.1.2 - * @return true if production mode is enabled; otherwise - * false. - */ - public static boolean isProductionMode() { - return !isDebugAvailable(); - } - - private native static boolean isDebugAvailable() - /*-{ - if($wnd.vaadin.debug) { - return true; - } else { - return false; - } - }-*/; - - /** - * Checks whether debug logging should be quiet - * - * @return true if debug logging should be quiet - */ - public static boolean isQuietDebugMode() { - String debugParameter = Window.Location.getParameter("debug"); - return isDebugAvailable() && debugParameter != null - && debugParameter.startsWith("q"); - } - - /** - * Checks whether the widget set version has been sent to the server. It is - * sent in the first UIDL request. - * - * @return true if browser information has already been sent - */ - public boolean isWidgetsetVersionSent() { - return widgetsetVersionSent; - } - - /** - * Registers that the widget set version has been sent to the server. - */ - public void setWidgetsetVersionSent() { - widgetsetVersionSent = true; - } - - private static final Logger getLogger() { - return Logger.getLogger(ApplicationConfiguration.class.getName()); - } -} diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java deleted file mode 100644 index ce55c13ce5..0000000000 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ /dev/null @@ -1,1642 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client; - -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Logger; - -import com.google.gwt.aria.client.LiveValue; -import com.google.gwt.aria.client.RelevantValue; -import com.google.gwt.aria.client.Roles; -import com.google.gwt.core.client.Duration; -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.JsArrayString; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.shared.EventBus; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.GwtEvent; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.event.shared.HasHandlers; -import com.google.gwt.event.shared.SimpleEventBus; -import com.google.gwt.http.client.URL; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.ui.HasWidgets; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConfiguration.ErrorMessage; -import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; -import com.vaadin.client.ResourceLoader.ResourceLoadEvent; -import com.vaadin.client.ResourceLoader.ResourceLoadListener; -import com.vaadin.client.communication.ConnectionStateHandler; -import com.vaadin.client.communication.Heartbeat; -import com.vaadin.client.communication.MessageHandler; -import com.vaadin.client.communication.MessageSender; -import com.vaadin.client.communication.RpcManager; -import com.vaadin.client.communication.ServerRpcQueue; -import com.vaadin.client.componentlocator.ComponentLocator; -import com.vaadin.client.metadata.ConnectorBundleLoader; -import com.vaadin.client.ui.AbstractComponentConnector; -import com.vaadin.client.ui.FontIcon; -import com.vaadin.client.ui.Icon; -import com.vaadin.client.ui.ImageIcon; -import com.vaadin.client.ui.VContextMenu; -import com.vaadin.client.ui.VNotification; -import com.vaadin.client.ui.VOverlay; -import com.vaadin.client.ui.ui.UIConnector; -import com.vaadin.shared.VaadinUriResolver; -import com.vaadin.shared.Version; -import com.vaadin.shared.communication.LegacyChangeVariablesInvocation; -import com.vaadin.shared.util.SharedUtil; - -/** - * This is the client side communication "engine", managing client-server - * communication with its server side counterpart - * com.vaadin.server.VaadinService. - * - * Client-side connectors receive updates from the corresponding server-side - * connector (typically component) as state updates or RPC calls. The connector - * has the possibility to communicate back with its server side counter part - * through RPC calls. - * - * TODO document better - * - * Entry point classes (widgetsets) define onModuleLoad(). - */ -public class ApplicationConnection implements HasHandlers { - - @Deprecated - public static final String MODIFIED_CLASSNAME = StyleConstants.MODIFIED; - - @Deprecated - public static final String DISABLED_CLASSNAME = StyleConstants.DISABLED; - - @Deprecated - public static final String REQUIRED_CLASSNAME = StyleConstants.REQUIRED; - - @Deprecated - public static final String REQUIRED_CLASSNAME_EXT = StyleConstants.REQUIRED_EXT; - - @Deprecated - public static final String ERROR_CLASSNAME_EXT = StyleConstants.ERROR_EXT; - - /** - * A string that, if found in a non-JSON response to a UIDL request, will - * cause the browser to refresh the page. If followed by a colon, optional - * whitespace, and a URI, causes the browser to synchronously load the URI. - * - *

- * This allows, for instance, a servlet filter to redirect the application - * to a custom login page when the session expires. For example: - *

- * - *
-     * if (sessionExpired) {
-     *     response.setHeader("Content-Type", "text/html");
-     *     response.getWriter().write(
-     *             myLoginPageHtml + "<!-- Vaadin-Refresh: "
-     *                     + request.getContextPath() + " -->");
-     * }
-     * 
- */ - public static final String UIDL_REFRESH_TOKEN = "Vaadin-Refresh"; - - private final HashMap resourcesMap = new HashMap(); - - private WidgetSet widgetSet; - - private VContextMenu contextMenu = null; - - private final UIConnector uIConnector; - - protected boolean cssLoaded = false; - - /** Parameters for this application connection loaded from the web-page */ - private ApplicationConfiguration configuration; - - private final LayoutManager layoutManager; - - private final RpcManager rpcManager; - - /** Event bus for communication events */ - private EventBus eventBus = GWT.create(SimpleEventBus.class); - - public enum ApplicationState { - INITIALIZING, RUNNING, TERMINATED; - } - - private ApplicationState applicationState = ApplicationState.INITIALIZING; - - /** - * The communication handler methods are called at certain points during - * communication with the server. This allows for making add-ons that keep - * track of different aspects of the communication. - */ - public interface CommunicationHandler extends EventHandler { - void onRequestStarting(RequestStartingEvent e); - - void onResponseHandlingStarted(ResponseHandlingStartedEvent e); - - void onResponseHandlingEnded(ResponseHandlingEndedEvent e); - } - - public static class RequestStartingEvent extends ApplicationConnectionEvent { - - public static Type TYPE = new Type(); - - public RequestStartingEvent(ApplicationConnection connection) { - super(connection); - } - - @Override - public Type getAssociatedType() { - return TYPE; - } - - @Override - protected void dispatch(CommunicationHandler handler) { - handler.onRequestStarting(this); - } - } - - public static class ResponseHandlingEndedEvent extends - ApplicationConnectionEvent { - - public static Type TYPE = new Type(); - - public ResponseHandlingEndedEvent(ApplicationConnection connection) { - super(connection); - } - - @Override - public Type getAssociatedType() { - return TYPE; - } - - @Override - protected void dispatch(CommunicationHandler handler) { - handler.onResponseHandlingEnded(this); - } - } - - public static abstract class ApplicationConnectionEvent extends - GwtEvent { - - private ApplicationConnection connection; - - protected ApplicationConnectionEvent(ApplicationConnection connection) { - this.connection = connection; - } - - public ApplicationConnection getConnection() { - return connection; - } - - } - - public static class ResponseHandlingStartedEvent extends - ApplicationConnectionEvent { - - public ResponseHandlingStartedEvent(ApplicationConnection connection) { - super(connection); - } - - public static Type TYPE = new Type(); - - @Override - public Type getAssociatedType() { - return TYPE; - } - - @Override - protected void dispatch(CommunicationHandler handler) { - handler.onResponseHandlingStarted(this); - } - } - - /** - * Event triggered when a application is stopped by calling - * {@link ApplicationConnection#setApplicationRunning(false)}. - * - * To listen for the event add a {@link ApplicationStoppedHandler} by - * invoking - * {@link ApplicationConnection#addHandler(ApplicationConnection.ApplicationStoppedEvent.Type, ApplicationStoppedHandler)} - * to the {@link ApplicationConnection} - * - * @since 7.1.8 - * @author Vaadin Ltd - */ - public static class ApplicationStoppedEvent extends - GwtEvent { - - public static Type TYPE = new Type(); - - @Override - public Type getAssociatedType() { - return TYPE; - } - - @Override - protected void dispatch(ApplicationStoppedHandler listener) { - listener.onApplicationStopped(this); - } - } - - /** - * Allows custom handling of communication errors. - */ - public interface CommunicationErrorHandler { - /** - * Called when a communication error has occurred. Returning - * true from this method suppresses error handling. - * - * @param details - * A string describing the error. - * @param statusCode - * The HTTP status code (e.g. 404, etc). - * @return true if the error reporting should be suppressed, false to - * perform normal error reporting. - */ - public boolean onError(String details, int statusCode); - } - - /** - * A listener for listening to application stopped events. The listener can - * be added to a {@link ApplicationConnection} by invoking - * {@link ApplicationConnection#addHandler(ApplicationStoppedEvent.Type, ApplicationStoppedHandler)} - * - * @since 7.1.8 - * @author Vaadin Ltd - */ - public interface ApplicationStoppedHandler extends EventHandler { - - /** - * Triggered when the {@link ApplicationConnection} marks a previously - * running application as stopped by invoking - * {@link ApplicationConnection#setApplicationRunning(false)} - * - * @param event - * the event triggered by the {@link ApplicationConnection} - */ - void onApplicationStopped(ApplicationStoppedEvent event); - } - - private CommunicationErrorHandler communicationErrorDelegate = null; - - private VLoadingIndicator loadingIndicator; - - private Heartbeat heartbeat = GWT.create(Heartbeat.class); - - private boolean tooltipInitialized = false; - - private final VaadinUriResolver uriResolver = new VaadinUriResolver() { - @Override - protected String getVaadinDirUrl() { - return getConfiguration().getVaadinDirUrl(); - } - - @Override - protected String getServiceUrlParameterName() { - return getConfiguration().getServiceUrlParameterName(); - } - - @Override - protected String getServiceUrl() { - return getConfiguration().getServiceUrl(); - } - - @Override - protected String getThemeUri() { - return ApplicationConnection.this.getThemeUri(); - } - - @Override - protected String encodeQueryStringParameterValue(String queryString) { - return URL.encodeQueryString(queryString); - } - }; - - public static class MultiStepDuration extends Duration { - private int previousStep = elapsedMillis(); - - public void logDuration(String message) { - logDuration(message, 0); - } - - public void logDuration(String message, int minDuration) { - int currentTime = elapsedMillis(); - int stepDuration = currentTime - previousStep; - if (stepDuration >= minDuration) { - getLogger().info(message + ": " + stepDuration + " ms"); - } - previousStep = currentTime; - } - } - - public ApplicationConnection() { - // Assuming UI data is eagerly loaded - ConnectorBundleLoader.get().loadBundle( - ConnectorBundleLoader.EAGER_BUNDLE_NAME, null); - uIConnector = GWT.create(UIConnector.class); - rpcManager = GWT.create(RpcManager.class); - layoutManager = GWT.create(LayoutManager.class); - tooltip = GWT.create(VTooltip.class); - loadingIndicator = GWT.create(VLoadingIndicator.class); - serverRpcQueue = GWT.create(ServerRpcQueue.class); - connectionStateHandler = GWT.create(ConnectionStateHandler.class); - messageHandler = GWT.create(MessageHandler.class); - messageSender = GWT.create(MessageSender.class); - } - - public void init(WidgetSet widgetSet, ApplicationConfiguration cnf) { - getLogger().info("Starting application " + cnf.getRootPanelId()); - getLogger().info("Using theme: " + cnf.getThemeName()); - - getLogger().info( - "Vaadin application servlet version: " - + cnf.getServletVersion()); - - if (!cnf.getServletVersion().equals(Version.getFullVersion())) { - getLogger() - .severe("Warning: your widget set seems to be built with a different " - + "version than the one used on server. Unexpected " - + "behavior may occur."); - } - - this.widgetSet = widgetSet; - configuration = cnf; - - layoutManager.setConnection(this); - loadingIndicator.setConnection(this); - serverRpcQueue.setConnection(this); - messageHandler.setConnection(this); - messageSender.setConnection(this); - - ComponentLocator componentLocator = new ComponentLocator(this); - - String appRootPanelName = cnf.getRootPanelId(); - // remove the end (window name) of autogenerated rootpanel id - appRootPanelName = appRootPanelName.replaceFirst("-\\d+$", ""); - - initializeTestbenchHooks(componentLocator, appRootPanelName); - - initializeClientHooks(); - - uIConnector.init(cnf.getRootPanelId(), this); - - // Connection state handler preloads the reconnect dialog, which uses - // overlay container. This in turn depends on VUI being attached - // (done in uiConnector.init) - connectionStateHandler.setConnection(this); - - tooltip.setOwner(uIConnector.getWidget()); - - getLoadingIndicator().show(); - - heartbeat.init(this); - - // Ensure the overlay container is added to the dom and set as a live - // area for assistive devices - Element overlayContainer = VOverlay.getOverlayContainer(this); - Roles.getAlertRole().setAriaLiveProperty(overlayContainer, - LiveValue.ASSERTIVE); - VOverlay.setOverlayContainerLabel(this, - getUIConnector().getState().overlayContainerLabel); - Roles.getAlertRole().setAriaRelevantProperty(overlayContainer, - RelevantValue.ADDITIONS); - } - - /** - * Starts this application. Don't call this method directly - it's called by - * {@link ApplicationConfiguration#startNextApplication()}, which should be - * called once this application has started (first response received) or - * failed to start. This ensures that the applications are started in order, - * to avoid session-id problems. - * - */ - public void start() { - String jsonText = configuration.getUIDL(); - if (jsonText == null) { - // initial UIDL not in DOM, request from server - getMessageSender().resynchronize(); - } else { - // initial UIDL provided in DOM, continue as if returned by request - - // Hack to avoid logging an error in endRequest() - getMessageSender().startRequest(); - getMessageHandler().handleMessage( - MessageHandler.parseJson(jsonText)); - } - - // Tooltip can't be created earlier because the - // necessary fields are not setup to add it in the - // correct place in the DOM - if (!tooltipInitialized) { - tooltipInitialized = true; - ApplicationConfiguration.runWhenDependenciesLoaded(new Command() { - @Override - public void execute() { - getVTooltip().initializeAssistiveTooltips(); - } - }); - } - } - - /** - * Checks if there is some work to be done on the client side - * - * @return true if the client has some work to be done, false otherwise - */ - private boolean isActive() { - return !getMessageHandler().isInitialUidlHandled() || isWorkPending() - || getMessageSender().hasActiveRequest() - || isExecutingDeferredCommands(); - } - - private native void initializeTestbenchHooks( - ComponentLocator componentLocator, String TTAppId) - /*-{ - var ap = this; - var client = {}; - client.isActive = $entry(function() { - return ap.@com.vaadin.client.ApplicationConnection::isActive()(); - }); - var vi = ap.@com.vaadin.client.ApplicationConnection::getVersionInfo()(); - if (vi) { - client.getVersionInfo = function() { - return vi; - } - } - - client.getProfilingData = $entry(function() { - var smh = ap.@com.vaadin.client.ApplicationConnection::getMessageHandler(); - var pd = [ - smh.@com.vaadin.client.communication.MessageHandler::lastProcessingTime, - smh.@com.vaadin.client.communication.MessageHandler::totalProcessingTime - ]; - pd = pd.concat(smh.@com.vaadin.client.communication.MessageHandler::serverTimingInfo); - pd[pd.length] = smh.@com.vaadin.client.communication.MessageHandler::bootstrapTime; - return pd; - }); - - client.getElementByPath = $entry(function(id) { - return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementByPath(Ljava/lang/String;)(id); - }); - client.getElementByPathStartingAt = $entry(function(id, element) { - return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementByPathStartingAt(Ljava/lang/String;Lcom/google/gwt/dom/client/Element;)(id, element); - }); - client.getElementsByPath = $entry(function(id) { - return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementsByPath(Ljava/lang/String;)(id); - }); - client.getElementsByPathStartingAt = $entry(function(id, element) { - return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementsByPathStartingAt(Ljava/lang/String;Lcom/google/gwt/dom/client/Element;)(id, element); - }); - client.getPathForElement = $entry(function(element) { - return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getPathForElement(Lcom/google/gwt/dom/client/Element;)(element); - }); - client.initializing = false; - - $wnd.vaadin.clients[TTAppId] = client; - }-*/; - - /** - * Helper for tt initialization - */ - private JavaScriptObject getVersionInfo() { - return configuration.getVersionInfoJSObject(); - } - - /** - * Publishes a JavaScript API for mash-up applications. - *
    - *
  • vaadin.forceSync() sends pending variable changes, in - * effect synchronizing the server and client state. This is done for all - * applications on host page.
  • - *
  • vaadin.postRequestHooks is a map of functions which gets - * called after each XHR made by vaadin application. Note, that it is - * attaching js functions responsibility to create the variable like this: - * - *
    -     * if(!vaadin.postRequestHooks) {vaadin.postRequestHooks = new Object();}
    -     * postRequestHooks.myHook = function(appId) {
    -     *          if(appId == "MyAppOfInterest") {
    -     *                  // do the staff you need on xhr activity
    -     *          }
    -     * }
    -     * 
    First parameter passed to these functions is the identifier - * of Vaadin application that made the request. - *
- * - * TODO make this multi-app aware - */ - private native void initializeClientHooks() - /*-{ - var app = this; - var oldSync; - if ($wnd.vaadin.forceSync) { - oldSync = $wnd.vaadin.forceSync; - } - $wnd.vaadin.forceSync = $entry(function() { - if (oldSync) { - oldSync(); - } - app.@com.vaadin.client.ApplicationConnection::sendPendingVariableChanges()(); - }); - var oldForceLayout; - if ($wnd.vaadin.forceLayout) { - oldForceLayout = $wnd.vaadin.forceLayout; - } - $wnd.vaadin.forceLayout = $entry(function() { - if (oldForceLayout) { - oldForceLayout(); - } - app.@com.vaadin.client.ApplicationConnection::forceLayout()(); - }); - }-*/; - - /** - * Requests an analyze of layouts, to find inconsistencies. Exclusively used - * for debugging during development. - * - * @deprecated as of 7.1. Replaced by {@link UIConnector#analyzeLayouts()} - */ - @Deprecated - public void analyzeLayouts() { - getUIConnector().analyzeLayouts(); - } - - /** - * Sends a request to the server to print details to console that will help - * the developer to locate the corresponding server-side connector in the - * source code. - * - * @param serverConnector - * @deprecated as of 7.1. Replaced by - * {@link UIConnector#showServerDebugInfo(ServerConnector)} - */ - @Deprecated - void highlightConnector(ServerConnector serverConnector) { - getUIConnector().showServerDebugInfo(serverConnector); - } - - int cssWaits = 0; - - protected ServerRpcQueue serverRpcQueue; - protected ConnectionStateHandler connectionStateHandler; - protected MessageHandler messageHandler; - protected MessageSender messageSender; - - static final int MAX_CSS_WAITS = 100; - - public void executeWhenCSSLoaded(final Command c) { - if (!isCSSLoaded() && cssWaits < MAX_CSS_WAITS) { - (new Timer() { - @Override - public void run() { - executeWhenCSSLoaded(c); - } - }).schedule(50); - - // Show this message just once - if (cssWaits++ == 0) { - getLogger().warning( - "Assuming CSS loading is not complete, " - + "postponing render phase. " - + "(.v-loading-indicator height == 0)"); - } - } else { - cssLoaded = true; - if (cssWaits >= MAX_CSS_WAITS) { - getLogger().severe("CSS files may have not loaded properly."); - } - - c.execute(); - } - } - - /** - * Checks whether or not the CSS is loaded. By default checks the size of - * the loading indicator element. - * - * @return - */ - protected boolean isCSSLoaded() { - return cssLoaded - || getLoadingIndicator().getElement().getOffsetHeight() != 0; - } - - /** - * Shows the communication error notification. - * - * @param details - * Optional details. - * @param statusCode - * The status code returned for the request - * - */ - public void showCommunicationError(String details, int statusCode) { - getLogger().severe("Communication error: " + details); - showError(details, configuration.getCommunicationError()); - } - - /** - * Shows the authentication error notification. - * - * @param details - * Optional details. - */ - public void showAuthenticationError(String details) { - getLogger().severe("Authentication error: " + details); - showError(details, configuration.getAuthorizationError()); - } - - /** - * Shows the session expiration notification. - * - * @param details - * Optional details. - */ - public void showSessionExpiredError(String details) { - getLogger().severe("Session expired: " + details); - showError(details, configuration.getSessionExpiredError()); - } - - /** - * Shows an error notification. - * - * @param details - * Optional details. - * @param message - * An ErrorMessage describing the error. - */ - protected void showError(String details, ErrorMessage message) { - VNotification.showError(this, message.getCaption(), - message.getMessage(), details, message.getUrl()); - } - - /** - * Checks if the client has running or scheduled commands - */ - private boolean isWorkPending() { - ConnectorMap connectorMap = getConnectorMap(); - JsArrayObject connectors = connectorMap - .getConnectorsAsJsArray(); - int size = connectors.size(); - for (int i = 0; i < size; i++) { - ServerConnector conn = connectors.get(i); - if (isWorkPending(conn)) { - return true; - } - - if (conn instanceof ComponentConnector) { - ComponentConnector compConn = (ComponentConnector) conn; - if (isWorkPending(compConn.getWidget())) { - return true; - } - } - } - return false; - } - - private static boolean isWorkPending(Object object) { - return object instanceof DeferredWorker - && ((DeferredWorker) object).isWorkPending(); - } - - /** - * Checks if deferred commands are (potentially) still being executed as a - * result of an update from the server. Returns true if a deferred command - * might still be executing, false otherwise. This will not work correctly - * if a deferred command is added in another deferred command. - *

- * Used by the native "client.isActive" function. - *

- * - * @return true if deferred commands are (potentially) being executed, false - * otherwise - */ - private boolean isExecutingDeferredCommands() { - Scheduler s = Scheduler.get(); - if (s instanceof VSchedulerImpl) { - return ((VSchedulerImpl) s).hasWorkQueued(); - } else { - return false; - } - } - - /** - * Returns the loading indicator used by this ApplicationConnection - * - * @return The loading indicator for this ApplicationConnection - */ - public VLoadingIndicator getLoadingIndicator() { - return loadingIndicator; - } - - /** - * Determines whether or not the loading indicator is showing. - * - * @return true if the loading indicator is visible - * @deprecated As of 7.1. Use {@link #getLoadingIndicator()} and - * {@link VLoadingIndicator#isVisible()}.isVisible() instead. - */ - @Deprecated - public boolean isLoadingIndicatorVisible() { - return getLoadingIndicator().isVisible(); - } - - public void loadStyleDependencies(JsArrayString dependencies) { - // Assuming no reason to interpret in a defined order - ResourceLoadListener resourceLoadListener = new ResourceLoadListener() { - @Override - public void onLoad(ResourceLoadEvent event) { - ApplicationConfiguration.endDependencyLoading(); - } - - @Override - public void onError(ResourceLoadEvent event) { - getLogger() - .severe(event.getResourceUrl() - + " could not be loaded, or the load detection failed because the stylesheet is empty."); - // The show must go on - onLoad(event); - } - }; - ResourceLoader loader = ResourceLoader.get(); - for (int i = 0; i < dependencies.length(); i++) { - String url = translateVaadinUri(dependencies.get(i)); - ApplicationConfiguration.startDependencyLoading(); - loader.loadStylesheet(url, resourceLoadListener); - } - } - - public void loadScriptDependencies(final JsArrayString dependencies) { - if (dependencies.length() == 0) { - return; - } - - // Listener that loads the next when one is completed - ResourceLoadListener resourceLoadListener = new ResourceLoadListener() { - @Override - public void onLoad(ResourceLoadEvent event) { - if (dependencies.length() != 0) { - String url = translateVaadinUri(dependencies.shift()); - ApplicationConfiguration.startDependencyLoading(); - // Load next in chain (hopefully already preloaded) - event.getResourceLoader().loadScript(url, this); - } - // Call start for next before calling end for current - ApplicationConfiguration.endDependencyLoading(); - } - - @Override - public void onError(ResourceLoadEvent event) { - getLogger().severe( - event.getResourceUrl() + " could not be loaded."); - // The show must go on - onLoad(event); - } - }; - - ResourceLoader loader = ResourceLoader.get(); - - // Start chain by loading first - String url = translateVaadinUri(dependencies.shift()); - ApplicationConfiguration.startDependencyLoading(); - loader.loadScript(url, resourceLoadListener); - - if (ResourceLoader.supportsInOrderScriptExecution()) { - for (int i = 0; i < dependencies.length(); i++) { - String preloadUrl = translateVaadinUri(dependencies.get(i)); - loader.loadScript(preloadUrl, null); - } - } else { - // Preload all remaining - for (int i = 0; i < dependencies.length(); i++) { - String preloadUrl = translateVaadinUri(dependencies.get(i)); - loader.preloadResource(preloadUrl, null); - } - } - } - - private void addVariableToQueue(String connectorId, String variableName, - Object value, boolean immediate) { - boolean lastOnly = !immediate; - // note that type is now deduced from value - serverRpcQueue.add(new LegacyChangeVariablesInvocation(connectorId, - variableName, value), lastOnly); - if (immediate) { - serverRpcQueue.flush(); - } - } - - /** - * @deprecated as of 7.6, use {@link ServerRpcQueue#flush()} - */ - @Deprecated - public void sendPendingVariableChanges() { - serverRpcQueue.flush(); - } - - /** - * Sends a new value for the given paintables given variable to the server. - *

- * The update is actually queued to be sent at a suitable time. If immediate - * is true, the update is sent as soon as possible. If immediate is false, - * the update will be sent along with the next immediate update. - *

- * - * @param paintableId - * the id of the paintable that owns the variable - * @param variableName - * the name of the variable - * @param newValue - * the new value to be sent - * @param immediate - * true if the update is to be sent as soon as possible - */ - public void updateVariable(String paintableId, String variableName, - ServerConnector newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, newValue, immediate); - } - - /** - * Sends a new value for the given paintables given variable to the server. - *

- * The update is actually queued to be sent at a suitable time. If immediate - * is true, the update is sent as soon as possible. If immediate is false, - * the update will be sent along with the next immediate update. - *

- * - * @param paintableId - * the id of the paintable that owns the variable - * @param variableName - * the name of the variable - * @param newValue - * the new value to be sent - * @param immediate - * true if the update is to be sent as soon as possible - */ - - public void updateVariable(String paintableId, String variableName, - String newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, newValue, immediate); - } - - /** - * Sends a new value for the given paintables given variable to the server. - *

- * The update is actually queued to be sent at a suitable time. If immediate - * is true, the update is sent as soon as possible. If immediate is false, - * the update will be sent along with the next immediate update. - *

- * - * @param paintableId - * the id of the paintable that owns the variable - * @param variableName - * the name of the variable - * @param newValue - * the new value to be sent - * @param immediate - * true if the update is to be sent as soon as possible - */ - - public void updateVariable(String paintableId, String variableName, - int newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, newValue, immediate); - } - - /** - * Sends a new value for the given paintables given variable to the server. - *

- * The update is actually queued to be sent at a suitable time. If immediate - * is true, the update is sent as soon as possible. If immediate is false, - * the update will be sent along with the next immediate update. - *

- * - * @param paintableId - * the id of the paintable that owns the variable - * @param variableName - * the name of the variable - * @param newValue - * the new value to be sent - * @param immediate - * true if the update is to be sent as soon as possible - */ - - public void updateVariable(String paintableId, String variableName, - long newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, newValue, immediate); - } - - /** - * Sends a new value for the given paintables given variable to the server. - *

- * The update is actually queued to be sent at a suitable time. If immediate - * is true, the update is sent as soon as possible. If immediate is false, - * the update will be sent along with the next immediate update. - *

- * - * @param paintableId - * the id of the paintable that owns the variable - * @param variableName - * the name of the variable - * @param newValue - * the new value to be sent - * @param immediate - * true if the update is to be sent as soon as possible - */ - - public void updateVariable(String paintableId, String variableName, - float newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, newValue, immediate); - } - - /** - * Sends a new value for the given paintables given variable to the server. - *

- * The update is actually queued to be sent at a suitable time. If immediate - * is true, the update is sent as soon as possible. If immediate is false, - * the update will be sent along with the next immediate update. - *

- * - * @param paintableId - * the id of the paintable that owns the variable - * @param variableName - * the name of the variable - * @param newValue - * the new value to be sent - * @param immediate - * true if the update is to be sent as soon as possible - */ - - public void updateVariable(String paintableId, String variableName, - double newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, newValue, immediate); - } - - /** - * Sends a new value for the given paintables given variable to the server. - *

- * The update is actually queued to be sent at a suitable time. If immediate - * is true, the update is sent as soon as possible. If immediate is false, - * the update will be sent along with the next immediate update. - *

- * - * @param paintableId - * the id of the paintable that owns the variable - * @param variableName - * the name of the variable - * @param newValue - * the new value to be sent - * @param immediate - * true if the update is to be sent as soon as possible - */ - - public void updateVariable(String paintableId, String variableName, - boolean newValue, boolean immediate) { - addVariableToQueue(paintableId, variableName, newValue, immediate); - } - - /** - * Sends a new value for the given paintables given variable to the server. - *

- * The update is actually queued to be sent at a suitable time. If immediate - * is true, the update is sent as soon as possible. If immediate is false, - * the update will be sent along with the next immediate update. - *

- * - * @param paintableId - * the id of the paintable that owns the variable - * @param variableName - * the name of the variable - * @param map - * the new values to be sent - * @param immediate - * true if the update is to be sent as soon as possible - */ - public void updateVariable(String paintableId, String variableName, - Map map, boolean immediate) { - addVariableToQueue(paintableId, variableName, map, immediate); - } - - /** - * Sends a new value for the given paintables given variable to the server. - *

- * The update is actually queued to be sent at a suitable time. If immediate - * is true, the update is sent as soon as possible. If immediate is false, - * the update will be sent along with the next immediate update. - *

- * A null array is sent as an empty array. - * - * @param paintableId - * the id of the paintable that owns the variable - * @param variableName - * the name of the variable - * @param values - * the new value to be sent - * @param immediate - * true if the update is to be sent as soon as possible - */ - public void updateVariable(String paintableId, String variableName, - String[] values, boolean immediate) { - addVariableToQueue(paintableId, variableName, values, immediate); - } - - /** - * Sends a new value for the given paintables given variable to the server. - *

- * The update is actually queued to be sent at a suitable time. If immediate - * is true, the update is sent as soon as possible. If immediate is false, - * the update will be sent along with the next immediate update. - *

- * A null array is sent as an empty array. - * - * @param paintableId - * the id of the paintable that owns the variable - * @param variableName - * the name of the variable - * @param values - * the new value to be sent - * @param immediate - * true if the update is to be sent as soon as possible - */ - public void updateVariable(String paintableId, String variableName, - Object[] values, boolean immediate) { - addVariableToQueue(paintableId, variableName, values, immediate); - } - - /** - * Does absolutely nothing. Replaced by {@link LayoutManager}. - * - * @param container - * @deprecated As of 7.0, serves no purpose - */ - @Deprecated - public void runDescendentsLayout(HasWidgets container) { - } - - /** - * This will cause re-layouting of all components. Mainly used for - * development. Published to JavaScript. - */ - public void forceLayout() { - Duration duration = new Duration(); - - layoutManager.forceLayout(); - - getLogger().info("forceLayout in " + duration.elapsedMillis() + " ms"); - } - - /** - * Returns false - * - * @param paintable - * @return false, always - * @deprecated As of 7.0, serves no purpose - */ - @Deprecated - private boolean handleComponentRelativeSize(ComponentConnector paintable) { - return false; - } - - /** - * Returns false - * - * @param paintable - * @return false, always - * @deprecated As of 7.0, serves no purpose - */ - @Deprecated - public boolean handleComponentRelativeSize(Widget widget) { - return handleComponentRelativeSize(connectorMap.getConnector(widget)); - - } - - @Deprecated - public ComponentConnector getPaintable(UIDL uidl) { - // Non-component connectors shouldn't be painted from legacy connectors - return (ComponentConnector) getConnector(uidl.getId(), - Integer.parseInt(uidl.getTag())); - } - - /** - * Get either an existing ComponentConnector or create a new - * ComponentConnector with the given type and id. - * - * If a ComponentConnector with the given id already exists, returns it. - * Otherwise creates and registers a new ComponentConnector of the given - * type. - * - * @param connectorId - * Id of the paintable - * @param connectorType - * Type of the connector, as passed from the server side - * - * @return Either an existing ComponentConnector or a new ComponentConnector - * of the given type - */ - public ServerConnector getConnector(String connectorId, int connectorType) { - if (!connectorMap.hasConnector(connectorId)) { - return createAndRegisterConnector(connectorId, connectorType); - } - return connectorMap.getConnector(connectorId); - } - - /** - * Creates a new ServerConnector with the given type and id. - * - * Creates and registers a new ServerConnector of the given type. Should - * never be called with the connector id of an existing connector. - * - * @param connectorId - * Id of the new connector - * @param connectorType - * Type of the connector, as passed from the server side - * - * @return A new ServerConnector of the given type - */ - private ServerConnector createAndRegisterConnector(String connectorId, - int connectorType) { - Profiler.enter("ApplicationConnection.createAndRegisterConnector"); - - // Create and register a new connector with the given type - ServerConnector p = widgetSet.createConnector(connectorType, - configuration); - connectorMap.registerConnector(connectorId, p); - p.doInit(connectorId, this); - - Profiler.leave("ApplicationConnection.createAndRegisterConnector"); - return p; - } - - /** - * Gets a resource that has been pre-loaded via UIDL, such as custom - * layouts. - * - * @param name - * identifier of the resource to get - * @return the resource - */ - public String getResource(String name) { - return resourcesMap.get(name); - } - - /** - * Sets a resource that has been pre-loaded via UIDL, such as custom - * layouts. - * - * @since 7.6 - * @param name - * identifier of the resource to Set - * @param resource - * the resource - */ - public void setResource(String name, String resource) { - resourcesMap.put(name, resource); - } - - /** - * Singleton method to get instance of app's context menu. - * - * @return VContextMenu object - */ - public VContextMenu getContextMenu() { - if (contextMenu == null) { - contextMenu = new VContextMenu(); - contextMenu.setOwner(uIConnector.getWidget()); - DOM.setElementProperty(contextMenu.getElement(), "id", - "PID_VAADIN_CM"); - } - return contextMenu; - } - - /** - * Gets an {@link Icon} instance corresponding to a URI. - * - * @since 7.2 - * @param uri - * @return Icon object - */ - public Icon getIcon(String uri) { - Icon icon; - if (uri == null) { - return null; - } else if (FontIcon.isFontIconUri(uri)) { - icon = GWT.create(FontIcon.class); - } else { - icon = GWT.create(ImageIcon.class); - } - icon.setUri(translateVaadinUri(uri)); - return icon; - } - - /** - * Translates custom protocols in UIDL URI's to be recognizable by browser. - * All uri's from UIDL should be routed via this method before giving them - * to browser due URI's in UIDL may contain custom protocols like theme://. - * - * @param uidlUri - * Vaadin URI from uidl - * @return translated URI ready for browser - */ - public String translateVaadinUri(String uidlUri) { - return uriResolver.resolveVaadinUri(uidlUri); - } - - /** - * Gets the URI for the current theme. Can be used to reference theme - * resources. - * - * @return URI to the current theme - */ - public String getThemeUri() { - return configuration.getVaadinDirUrl() + "themes/" - + getUIConnector().getActiveTheme(); - } - - /* Extended title handling */ - - private final VTooltip tooltip; - - private ConnectorMap connectorMap = GWT.create(ConnectorMap.class); - - /** - * Use to notify that the given component's caption has changed; layouts may - * have to be recalculated. - * - * @param component - * the Paintable whose caption has changed - * @deprecated As of 7.0.2, has not had any effect for a long time - */ - @Deprecated - public void captionSizeUpdated(Widget widget) { - // This doesn't do anything, it's just kept here for compatibility - } - - /** - * Gets the main view - * - * @return the main view - */ - public UIConnector getUIConnector() { - return uIConnector; - } - - /** - * Gets the {@link ApplicationConfiguration} for the current application. - * - * @see ApplicationConfiguration - * @return the configuration for this application - */ - public ApplicationConfiguration getConfiguration() { - return configuration; - } - - /** - * Checks if there is a registered server side listener for the event. The - * list of events which has server side listeners is updated automatically - * before the component is updated so the value is correct if called from - * updatedFromUIDL. - * - * @param connector - * The connector to register event listeners for - * @param eventIdentifier - * The identifier for the event - * @return true if at least one listener has been registered on server side - * for the event identified by eventIdentifier. - * @deprecated As of 7.0. Use - * {@link AbstractConnector#hasEventListener(String)} instead - */ - @Deprecated - public boolean hasEventListeners(ComponentConnector connector, - String eventIdentifier) { - return connector.hasEventListener(eventIdentifier); - } - - /** - * Adds the get parameters to the uri and returns the new uri that contains - * the parameters. - * - * @param uri - * The uri to which the parameters should be added. - * @param extraParams - * One or more parameters in the format "a=b" or "c=d&e=f". An - * empty string is allowed but will not modify the url. - * @return The modified URI with the get parameters in extraParams added. - * @deprecated Use {@link SharedUtil#addGetParameters(String,String)} - * instead - */ - @Deprecated - public static String addGetParameters(String uri, String extraParams) { - return SharedUtil.addGetParameters(uri, extraParams); - } - - ConnectorMap getConnectorMap() { - return connectorMap; - } - - /** - * @deprecated As of 7.0. No longer serves any purpose. - */ - @Deprecated - public void unregisterPaintable(ServerConnector p) { - getLogger().info( - "unregisterPaintable (unnecessarily) called for " - + Util.getConnectorString(p)); - } - - /** - * Get VTooltip instance related to application connection - * - * @return VTooltip instance - */ - public VTooltip getVTooltip() { - return tooltip; - } - - /** - * Method provided for backwards compatibility. Duties previously done by - * this method is now handled by the state change event handler in - * AbstractComponentConnector. The only function this method has is to - * return true if the UIDL is a "cached" update. - * - * @param component - * @param uidl - * @param manageCaption - * @deprecated As of 7.0, no longer serves any purpose - * @return - */ - @Deprecated - public boolean updateComponent(Widget component, UIDL uidl, - boolean manageCaption) { - ComponentConnector connector = getConnectorMap() - .getConnector(component); - if (!AbstractComponentConnector.isRealUpdate(uidl)) { - return true; - } - - if (!manageCaption) { - getLogger() - .warning( - Util.getConnectorString(connector) - + " called updateComponent with manageCaption=false. The parameter was ignored - override delegateCaption() to return false instead. It is however not recommended to use caption this way at all."); - } - return false; - } - - /** - * @deprecated As of 7.0. Use - * {@link AbstractComponentConnector#hasEventListener(String)} - * instead - */ - @Deprecated - public boolean hasEventListeners(Widget widget, String eventIdentifier) { - ComponentConnector connector = getConnectorMap().getConnector(widget); - if (connector == null) { - /* - * No connector will exist in cases where Vaadin widgets have been - * re-used without implementing server<->client communication. - */ - return false; - } - - return hasEventListeners(getConnectorMap().getConnector(widget), - eventIdentifier); - } - - LayoutManager getLayoutManager() { - return layoutManager; - } - - /** - * Schedules a heartbeat request to occur after the configured heartbeat - * interval elapses if the interval is a positive number. Otherwise, does - * nothing. - * - * @deprecated as of 7.2, use {@link Heartbeat#schedule()} instead - */ - @Deprecated - protected void scheduleHeartbeat() { - heartbeat.schedule(); - } - - /** - * Sends a heartbeat request to the server. - *

- * Heartbeat requests are used to inform the server that the client-side is - * still alive. If the client page is closed or the connection lost, the - * server will eventually close the inactive UI. - * - * @deprecated as of 7.2, use {@link Heartbeat#send()} instead - */ - @Deprecated - protected void sendHeartbeat() { - heartbeat.send(); - } - - public void handleCommunicationError(String details, int statusCode) { - boolean handled = false; - if (communicationErrorDelegate != null) { - handled = communicationErrorDelegate.onError(details, statusCode); - - } - - if (!handled) { - showCommunicationError(details, statusCode); - } - - } - - /** - * Sets the delegate that is called whenever a communication error occurrs. - * - * @param delegate - * the delegate. - */ - public void setCommunicationErrorDelegate(CommunicationErrorHandler delegate) { - communicationErrorDelegate = delegate; - } - - public void setApplicationRunning(boolean applicationRunning) { - if (getApplicationState() == ApplicationState.TERMINATED) { - if (applicationRunning) { - getLogger() - .severe("Tried to restart a terminated application. This is not supported"); - } else { - getLogger() - .warning( - "Tried to stop a terminated application. This should not be done"); - } - return; - } else if (getApplicationState() == ApplicationState.INITIALIZING) { - if (applicationRunning) { - applicationState = ApplicationState.RUNNING; - } else { - getLogger() - .warning( - "Tried to stop the application before it has started. This should not be done"); - } - } else if (getApplicationState() == ApplicationState.RUNNING) { - if (!applicationRunning) { - applicationState = ApplicationState.TERMINATED; - eventBus.fireEvent(new ApplicationStoppedEvent()); - } else { - getLogger() - .warning( - "Tried to start an already running application. This should not be done"); - } - } - } - - /** - * Checks if the application is in the {@link ApplicationState#RUNNING} - * state. - * - * @since 7.6 - * @return true if the application is in the running state, false otherwise - */ - public boolean isApplicationRunning() { - return applicationState == ApplicationState.RUNNING; - } - - public HandlerRegistration addHandler( - GwtEvent.Type type, H handler) { - return eventBus.addHandler(type, handler); - } - - @Override - public void fireEvent(GwtEvent event) { - eventBus.fireEvent(event); - } - - /** - * Calls {@link ComponentConnector#flush()} on the active connector. Does - * nothing if there is no active (focused) connector. - */ - public void flushActiveConnector() { - ComponentConnector activeConnector = getActiveConnector(); - if (activeConnector == null) { - return; - } - activeConnector.flush(); - } - - /** - * Gets the active connector for focused element in browser. - * - * @return Connector for focused element or null. - */ - private ComponentConnector getActiveConnector() { - Element focusedElement = WidgetUtil.getFocusedElement(); - if (focusedElement == null) { - return null; - } - return Util.getConnectorForElement(this, getUIConnector().getWidget(), - focusedElement); - } - - private static Logger getLogger() { - return Logger.getLogger(ApplicationConnection.class.getName()); - } - - /** - * Returns the hearbeat instance. - */ - public Heartbeat getHeartbeat() { - return heartbeat; - } - - /** - * Returns the state of this application. An application state goes from - * "initializing" to "running" to "stopped". There is no way for an - * application to go back to a previous state, i.e. a stopped application - * can never be re-started - * - * @since 7.6 - * @return the current state of this application - */ - public ApplicationState getApplicationState() { - return applicationState; - } - - /** - * Gets the server RPC queue for this application - * - * @since 7.6 - * @return the server RPC queue - */ - public ServerRpcQueue getServerRpcQueue() { - return serverRpcQueue; - } - - /** - * Gets the communication error handler for this application - * - * @since 7.6 - * @return the server RPC queue - */ - public ConnectionStateHandler getConnectionStateHandler() { - return connectionStateHandler; - } - - /** - * Gets the (server to client) message handler for this application - * - * @since 7.6 - * @return the message handler - */ - public MessageHandler getMessageHandler() { - return messageHandler; - } - - /** - * Gets the server rpc manager for this application - * - * @since 7.6 - * @return the server rpc manager - */ - public RpcManager getRpcManager() { - return rpcManager; - } - - /** - * Gets the (client to server) message sender for this application - * - * @since 7.6 - * @return the message sender - */ - public MessageSender getMessageSender() { - return messageSender; - } - - /** - * @since 7.6 - * @return the widget set - */ - public WidgetSet getWidgetSet() { - return widgetSet; - } - - public int getLastSeenServerSyncId() { - return getMessageHandler().getLastSeenServerSyncId(); - } - -} diff --git a/client/src/com/vaadin/client/BrowserInfo.java b/client/src/com/vaadin/client/BrowserInfo.java deleted file mode 100644 index 8dcefddcf5..0000000000 --- a/client/src/com/vaadin/client/BrowserInfo.java +++ /dev/null @@ -1,511 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client; - -import com.google.gwt.user.client.ui.RootPanel; -import com.vaadin.shared.VBrowserDetails; - -/** - * Class used to query information about web browser. - * - * Browser details are detected only once and those are stored in this singleton - * class. - * - */ -public class BrowserInfo { - - private static final String BROWSER_OPERA = "op"; - private static final String BROWSER_IE = "ie"; - private static final String BROWSER_EDGE = "edge"; - private static final String BROWSER_FIREFOX = "ff"; - private static final String BROWSER_SAFARI = "sa"; - - public static final String ENGINE_GECKO = "gecko"; - public static final String ENGINE_WEBKIT = "webkit"; - public static final String ENGINE_PRESTO = "presto"; - public static final String ENGINE_TRIDENT = "trident"; - - private static final String OS_WINDOWS = "win"; - private static final String OS_LINUX = "lin"; - private static final String OS_MACOSX = "mac"; - private static final String OS_ANDROID = "android"; - private static final String OS_IOS = "ios"; - - // Common CSS class for all touch devices - private static final String UI_TOUCH = "touch"; - - private static BrowserInfo instance; - - private static String cssClass = null; - - static { - // Add browser dependent v-* classnames to body to help css hacks - String browserClassnames = get().getCSSClass(); - RootPanel.get().addStyleName(browserClassnames); - } - - /** - * Singleton method to get BrowserInfo object. - * - * @return instance of BrowserInfo object - */ - public static BrowserInfo get() { - if (instance == null) { - instance = new BrowserInfo(); - } - return instance; - } - - private VBrowserDetails browserDetails; - private boolean touchDevice; - - private BrowserInfo() { - browserDetails = new VBrowserDetails(getBrowserString()); - if (browserDetails.isIE()) { - // Use document mode instead user agent to accurately detect how we - // are rendering - int documentMode = getIEDocumentMode(); - if (documentMode != -1) { - browserDetails.setIEMode(documentMode); - } - } - - if (browserDetails.isChrome()) { - touchDevice = detectChromeTouchDevice(); - } else if (browserDetails.isIE()) { - touchDevice = detectIETouchDevice(); - } else { - // PhantomJS pretends to be a touch device which breaks some UI - // tests - touchDevice = !browserDetails.isPhantomJS() && detectTouchDevice(); - } - } - - private native boolean detectTouchDevice() - /*-{ - try { document.createEvent("TouchEvent");return true;} catch(e){return false;}; - }-*/; - - private native boolean detectChromeTouchDevice() - /*-{ - return ("ontouchstart" in window); - }-*/; - - private native boolean detectIETouchDevice() - /*-{ - return !!navigator.msMaxTouchPoints; - }-*/; - - private native int getIEDocumentMode() - /*-{ - var mode = $wnd.document.documentMode; - if (!mode) - return -1; - return mode; - }-*/; - - /** - * Returns a string representing the browser in use, for use in CSS - * classnames. The classnames will be space separated abbreviations, - * optionally with a version appended. - * - * Abbreviations: Firefox: ff Internet Explorer: ie Safari: sa Opera: op - * - * Browsers that CSS-wise behave like each other will get the same - * abbreviation (this usually depends on the rendering engine). - * - * This is quite simple at the moment, more heuristics will be added when - * needed. - * - * Examples: Internet Explorer 6: ".v-ie .v-ie6 .v-ie60", Firefox 3.0.4: - * ".v-ff .v-ff3 .v-ff30", Opera 9.60: ".v-op .v-op9 .v-op960", Opera 10.10: - * ".v-op .v-op10 .v-op1010" - * - * @return - */ - public String getCSSClass() { - String prefix = "v-"; - - if (cssClass == null) { - String browserIdentifier = ""; - String majorVersionClass = ""; - String minorVersionClass = ""; - String browserEngineClass = ""; - - if (browserDetails.isFirefox()) { - browserIdentifier = BROWSER_FIREFOX; - majorVersionClass = browserIdentifier - + getBrowserMajorVersion(); - minorVersionClass = majorVersionClass - + browserDetails.getBrowserMinorVersion(); - browserEngineClass = ENGINE_GECKO; - } else if (browserDetails.isChrome()) { - // TODO update when Chrome is more stable - browserIdentifier = BROWSER_SAFARI; - majorVersionClass = "ch"; - browserEngineClass = ENGINE_WEBKIT; - } else if (browserDetails.isSafari()) { - browserIdentifier = BROWSER_SAFARI; - majorVersionClass = browserIdentifier - + getBrowserMajorVersion(); - minorVersionClass = majorVersionClass - + browserDetails.getBrowserMinorVersion(); - browserEngineClass = ENGINE_WEBKIT; - } else if (browserDetails.isIE()) { - browserIdentifier = BROWSER_IE; - majorVersionClass = browserIdentifier - + getBrowserMajorVersion(); - minorVersionClass = majorVersionClass - + browserDetails.getBrowserMinorVersion(); - browserEngineClass = ENGINE_TRIDENT; - } else if (browserDetails.isEdge()) { - browserIdentifier = BROWSER_EDGE; - majorVersionClass = browserIdentifier - + getBrowserMajorVersion(); - minorVersionClass = majorVersionClass - + browserDetails.getBrowserMinorVersion(); - browserEngineClass = ""; - } else if (browserDetails.isOpera()) { - browserIdentifier = BROWSER_OPERA; - majorVersionClass = browserIdentifier - + getBrowserMajorVersion(); - minorVersionClass = majorVersionClass - + browserDetails.getBrowserMinorVersion(); - browserEngineClass = ENGINE_PRESTO; - } - - cssClass = prefix + browserIdentifier; - if (!"".equals(majorVersionClass)) { - cssClass = cssClass + " " + prefix + majorVersionClass; - } - if (!"".equals(minorVersionClass)) { - cssClass = cssClass + " " + prefix + minorVersionClass; - } - if (!"".equals(browserEngineClass)) { - cssClass = cssClass + " " + prefix + browserEngineClass; - } - String osClass = getOperatingSystemClass(); - if (osClass != null) { - cssClass = cssClass + " " + osClass; - } - if (isTouchDevice()) { - cssClass = cssClass + " " + prefix + UI_TOUCH; - } - } - - return cssClass; - } - - private String getOperatingSystemClass() { - String prefix = "v-"; - - if (browserDetails.isAndroid()) { - return prefix + OS_ANDROID; - } else if (browserDetails.isIOS()) { - String iosClass = prefix + OS_IOS; - return iosClass + " " + iosClass + getOperatingSystemMajorVersion(); - } else if (browserDetails.isWindows()) { - return prefix + OS_WINDOWS; - } else if (browserDetails.isLinux()) { - return prefix + OS_LINUX; - } else if (browserDetails.isMacOSX()) { - return prefix + OS_MACOSX; - } - // Unknown OS - return null; - } - - public boolean isIE() { - return browserDetails.isIE(); - } - - public boolean isEdge() { - return browserDetails.isEdge(); - } - - public boolean isFirefox() { - return browserDetails.isFirefox(); - } - - public boolean isSafari() { - return browserDetails.isSafari(); - } - - public boolean isIE8() { - return isIE() && getBrowserMajorVersion() == 8; - } - - public boolean isIE9() { - return isIE() && getBrowserMajorVersion() == 9; - } - - public boolean isIE10() { - return isIE() && getBrowserMajorVersion() == 10; - } - - public boolean isIE11() { - return isIE() && getBrowserMajorVersion() == 11; - } - - public boolean isChrome() { - return browserDetails.isChrome(); - } - - public boolean isGecko() { - return browserDetails.isGecko(); - } - - public boolean isWebkit() { - return browserDetails.isWebKit(); - } - - /** - * Returns the Gecko version if the browser is Gecko based. The Gecko - * version for Firefox 2 is 1.8 and 1.9 for Firefox 3. - * - * @return The Gecko version or -1 if the browser is not Gecko based - */ - public float getGeckoVersion() { - if (!browserDetails.isGecko()) { - return -1; - } - - return browserDetails.getBrowserEngineVersion(); - } - - /** - * Returns the WebKit version if the browser is WebKit based. The WebKit - * version returned is the major version e.g., 523. - * - * @return The WebKit version or -1 if the browser is not WebKit based - */ - public float getWebkitVersion() { - if (!browserDetails.isWebKit()) { - return -1; - } - - return browserDetails.getBrowserEngineVersion(); - } - - public float getIEVersion() { - if (!browserDetails.isIE()) { - return -1; - } - - return getBrowserMajorVersion(); - } - - public float getOperaVersion() { - if (!browserDetails.isOpera()) { - return -1; - } - - return getBrowserMajorVersion(); - } - - public boolean isOpera() { - return browserDetails.isOpera(); - } - - public boolean isOpera10() { - return browserDetails.isOpera() && getBrowserMajorVersion() == 10; - } - - public boolean isOpera11() { - return browserDetails.isOpera() && getBrowserMajorVersion() == 11; - } - - public native static String getBrowserString() - /*-{ - return $wnd.navigator.userAgent; - }-*/; - - public native int getScreenWidth() - /*-{ - return $wnd.screen.width; - }-*/; - - public native int getScreenHeight() - /*-{ - return $wnd.screen.height; - }-*/; - - /** - * @return true if the browser runs on a touch based device. - */ - public boolean isTouchDevice() { - return touchDevice; - } - - /** - * Indicates whether the browser might require juggling to properly update - * sizes inside elements with overflow: auto. - * - * @return true if the browser requires the workaround, - * otherwise false - */ - public boolean requiresOverflowAutoFix() { - return (getWebkitVersion() > 0 || getOperaVersion() >= 11 - || getIEVersion() >= 10 || isFirefox()) - && WidgetUtil.getNativeScrollbarSize() > 0; - } - - /** - * Indicates whether the browser might require juggling to properly update - * sizes inside elements with overflow: auto when adjusting absolutely - * positioned elements. - *

- * See https://bugs.webkit.org/show_bug.cgi?id=123958 and - * http://code.google.com/p/chromium/issues/detail?id=316549 - * - * @since 7.1.8 - * @return true if the browser requires the workaround, - * otherwise false - */ - public boolean requiresPositionAbsoluteOverflowAutoFix() { - return (getWebkitVersion() > 0) - && WidgetUtil.getNativeScrollbarSize() > 0; - } - - /** - * Checks if the browser is run on iOS - * - * @return true if the browser is run on iOS, false otherwise - */ - public boolean isIOS() { - return browserDetails.isIOS(); - } - - /** - * Checks if the browser is run on iOS 6. - * - * @since 7.1.1 - * @return true if the browser is run on iOS 6, false otherwise - */ - public boolean isIOS6() { - return isIOS() && getOperatingSystemMajorVersion() == 6; - } - - /** - * Checks if the browser is run on Android - * - * @return true if the browser is run on Android, false otherwise - */ - public boolean isAndroid() { - return browserDetails.isAndroid(); - } - - /** - * Checks if the browser is capable of handling scrolling natively or if a - * touch scroll helper is needed for scrolling. - * - * @return true if browser needs a touch scroll helper, false if the browser - * can handle scrolling natively - */ - public boolean requiresTouchScrollDelegate() { - if (!isTouchDevice()) { - return false; - } - // TODO Should test other Android browsers, especially Chrome - if (isAndroid() && isWebkit() && getWebkitVersion() >= 534) { - return false; - } - // iOS 6+ Safari supports native scrolling; iOS 5 suffers from #8792 - // TODO Should test other iOS browsers - if (isIOS() && isWebkit() && getOperatingSystemMajorVersion() >= 6) { - return false; - } - - if (isIE()) { - return false; - } - - return true; - } - - /** - * Tests if this is an Android devices with a broken scrollTop - * implementation - * - * @return true if scrollTop cannot be trusted on this device, false - * otherwise - */ - public boolean isAndroidWithBrokenScrollTop() { - return isAndroid() - && (getOperatingSystemMajorVersion() == 3 || getOperatingSystemMajorVersion() == 4); - } - - public boolean isAndroid23() { - return isAndroid() && getOperatingSystemMajorVersion() == 2 - && getOperatingSystemMinorVersion() == 3; - } - - private int getOperatingSystemMajorVersion() { - return browserDetails.getOperatingSystemMajorVersion(); - } - - private int getOperatingSystemMinorVersion() { - return browserDetails.getOperatingSystemMinorVersion(); - } - - /** - * Returns the browser major version e.g., 3 for Firefox 3.5, 4 for Chrome - * 4, 8 for Internet Explorer 8. - *

- * Note that Internet Explorer 8 and newer will return the document mode so - * IE8 rendering as IE7 will return 7. - *

- * - * @return The major version of the browser. - */ - public int getBrowserMajorVersion() { - return browserDetails.getBrowserMajorVersion(); - } - - /** - * Returns the browser minor version e.g., 5 for Firefox 3.5. - * - * @see #getBrowserMajorVersion() - * - * @return The minor version of the browser, or -1 if not known/parsed. - */ - public int getBrowserMinorVersion() { - return browserDetails.getBrowserMinorVersion(); - } - - /** - * Checks if the browser version is newer or equal to the given major+minor - * version. - * - * @param majorVersion - * The major version to check for - * @param minorVersion - * The minor version to check for - * @return true if the browser version is newer or equal to the given - * version - */ - public boolean isBrowserVersionNewerOrEqual(int majorVersion, - int minorVersion) { - if (getBrowserMajorVersion() == majorVersion) { - // Same major - return (getBrowserMinorVersion() >= minorVersion); - } - - // Older or newer major - return (getBrowserMajorVersion() > majorVersion); - } -} diff --git a/client/src/com/vaadin/client/CSSRule.java b/client/src/com/vaadin/client/CSSRule.java deleted file mode 100644 index a1ddce6d0a..0000000000 --- a/client/src/com/vaadin/client/CSSRule.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import com.google.gwt.core.client.JavaScriptObject; - -/** - * Utility class for fetching CSS properties from DOM StyleSheets JS object. - */ -public class CSSRule { - - private final String selector; - private JavaScriptObject rules = null; - - /** - * - * @param selector - * the CSS selector to search for in the stylesheets - * @param deep - * should the search follow any @import statements? - */ - public CSSRule(final String selector, final boolean deep) { - this.selector = selector; - fetchRule(selector, deep); - } - - // TODO how to find the right LINK-element? We should probably give the - // stylesheet a name. - private native void fetchRule(final String selector, final boolean deep) - /*-{ - var sheets = $doc.styleSheets; - for(var i = 0; i < sheets.length; i++) { - var sheet = sheets[i]; - if(sheet.href && sheet.href.indexOf("VAADIN/themes")>-1) { - // $entry not needed as function is not exported - this.@com.vaadin.client.CSSRule::rules = @com.vaadin.client.CSSRule::searchForRule(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;Z)(sheet, selector, deep); - return; - } - } - this.@com.vaadin.client.CSSRule::rules = []; - }-*/; - - /* - * Loops through all current style rules and collects all matching to - * 'rules' array. The array is reverse ordered (last one found is first). - */ - private static native JavaScriptObject searchForRule( - final JavaScriptObject sheet, final String selector, - final boolean deep) - /*-{ - if(!$doc.styleSheets) - return null; - - selector = selector.toLowerCase(); - - var allMatches = []; - - // IE handles imported sheet differently - if(deep && sheet.imports && sheet.imports.length > 0) { - for(var i=0; i < sheet.imports.length; i++) { - // $entry not needed as function is not exported - var imports = @com.vaadin.client.CSSRule::searchForRule(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;Z)(sheet.imports[i], selector, deep); - allMatches.concat(imports); - } - } - - var theRules = new Array(); - if (sheet.cssRules) - theRules = sheet.cssRules - else if (sheet.rules) - theRules = sheet.rules - - var j = theRules.length; - for(var i=0; itrue if the width of this paintable is currently - * undefined. If the width is undefined, the actual width of the paintable - * is defined by its contents. - * - * @return true if the width is undefined, else - * false - */ - public boolean isUndefinedWidth(); - - /** - * Returns true if the height of this paintable is currently - * undefined. If the height is undefined, the actual height of the paintable - * is defined by its contents. - * - * @return true if the height is undefined, else - * false - */ - public boolean isUndefinedHeight(); - - /** - * Returns true if the width of this paintable is currently - * relative. If the width is relative, the actual width of the paintable is - * a percentage of the size allocated to it by its parent. - * - * @return true if the width is undefined, else - * false - */ - public boolean isRelativeWidth(); - - /** - * Returns true if the height of this paintable is currently - * relative. If the height is relative, the actual height of the paintable - * is a percentage of the size allocated to it by its parent. - * - * @return true if the width is undefined, else - * false - */ - public boolean isRelativeHeight(); - - /** - * Checks if the connector is read only. - * - * @deprecated This belongs in AbstractFieldConnector, see #8514 - * @return true - */ - @Deprecated - public boolean isReadOnly(); - - /** - * Return true if parent handles caption, false if the paintable handles the - * caption itself. - * - *

- * This should always return true and all components should let the parent - * handle the caption and use other attributes for internal texts in the - * component - *

- * - * @return true if caption handling is delegated to the parent, false if - * parent should not be allowed to render caption - */ - public boolean delegateCaptionHandling(); - - /** - * Sets the enabled state of the widget associated to this connector. - * - * @param widgetEnabled - * true if the widget should be enabled, false otherwise - */ - public void setWidgetEnabled(boolean widgetEnabled); - - /** - * Gets the tooltip info for the given element. - *

- * When overriding this method, {@link #hasTooltip()} should also be - * overridden to return true in all situations where this - * method might return a non-empty result. - *

- * - * @param element - * The element to lookup a tooltip for - * @return The tooltip for the element or null if no tooltip is defined for - * this element. - */ - public TooltipInfo getTooltipInfo(Element element); - - /** - * Check whether there might be a tooltip for this component. The framework - * will only add event listeners for automatically handling tooltips (using - * {@link #getTooltipInfo(Element)}) if this method returns true. - *

- * This is only done to optimize performance, so in cases where the status - * is not known, it's safer to return true so that there will - * be a tooltip handler even though it might not be needed in all cases. - * - * @return true if some part of the component might have a - * tooltip, otherwise false - */ - public boolean hasTooltip(); - - /** - * Called for the active (focused) connector when a situation occurs that - * the focused connector might have buffered changes which need to be - * processed before other activity takes place. - *

- * This is currently called when the user changes the fragment using the - * back/forward button in the browser and allows the focused field to submit - * its value to the server before the fragment change event takes place. - *

- */ - public void flush(); - -} diff --git a/client/src/com/vaadin/client/ComponentDetail.java b/client/src/com/vaadin/client/ComponentDetail.java deleted file mode 100644 index 9e5e2a82a8..0000000000 --- a/client/src/com/vaadin/client/ComponentDetail.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import java.util.HashMap; - -class ComponentDetail { - - private TooltipInfo tooltipInfo = new TooltipInfo(); - - private ServerConnector connector; - - public ComponentDetail() { - - } - - /** - * Returns a TooltipInfo assosiated with Component. If element is given, - * returns an additional TooltipInfo. - * - * @param key - * @return the tooltipInfo - */ - public TooltipInfo getTooltipInfo(Object key) { - if (key == null) { - return tooltipInfo; - } else { - if (additionalTooltips != null) { - return additionalTooltips.get(key); - } else { - return null; - } - } - } - - /** - * @param tooltipInfo - * the tooltipInfo to set - */ - public void setTooltipInfo(TooltipInfo tooltipInfo) { - this.tooltipInfo = tooltipInfo; - } - - private HashMap additionalTooltips; - - public void putAdditionalTooltip(Object key, TooltipInfo tooltip) { - if (tooltip == null && additionalTooltips != null) { - additionalTooltips.remove(key); - } else { - if (additionalTooltips == null) { - additionalTooltips = new HashMap(); - } - additionalTooltips.put(key, tooltip); - } - } - - public ServerConnector getConnector() { - return connector; - } - - public void setConnector(ServerConnector connector) { - this.connector = connector; - } - -} diff --git a/client/src/com/vaadin/client/ComponentDetailMap.java b/client/src/com/vaadin/client/ComponentDetailMap.java deleted file mode 100644 index c99ebd2738..0000000000 --- a/client/src/com/vaadin/client/ComponentDetailMap.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import java.util.ArrayList; -import java.util.Collection; - -import com.google.gwt.core.client.JavaScriptObject; - -final class ComponentDetailMap extends JavaScriptObject { - - protected ComponentDetailMap() { - } - - static ComponentDetailMap create() { - return (ComponentDetailMap) JavaScriptObject.createObject(); - } - - boolean isEmpty() { - return size() == 0; - } - - final native boolean containsKey(String key) - /*-{ - return this.hasOwnProperty(key); - }-*/; - - final native ComponentDetail get(String key) - /*-{ - return this[key]; - }-*/; - - final native void put(String id, ComponentDetail value) - /*-{ - this[id] = value; - }-*/; - - final native void remove(String id) - /*-{ - delete this[id]; - }-*/; - - final native int size() - /*-{ - var count = 0; - for(var key in this) { - count++; - } - return count; - }-*/; - - final native void clear() - /*-{ - for(var key in this) { - if(this.hasOwnProperty(key)) { - delete this[key]; - } - } - }-*/; - - private final native void fillWithValues(Collection list) - /*-{ - for(var key in this) { - // $entry not needed as function is not exported - list.@java.util.Collection::add(Ljava/lang/Object;)(this[key]); - } - }-*/; - - final Collection values() { - ArrayList list = new ArrayList(); - fillWithValues(list); - return list; - } - - public native JsArrayObject valuesAsJsArray() - /*-{ - var result = []; - for(var key in this) { - if (this.hasOwnProperty(key)) { - result.push(this[key]); - } - } - return result; - }-*/; - -} diff --git a/client/src/com/vaadin/client/ComputedStyle.java b/client/src/com/vaadin/client/ComputedStyle.java deleted file mode 100644 index 65a6e69019..0000000000 --- a/client/src/com/vaadin/client/ComputedStyle.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.dom.client.Element; - -public class ComputedStyle { - - protected final JavaScriptObject computedStyle; - private final Element elem; - - /** - * Gets this element's computed style object which can be used to gather - * information about the current state of the rendered node. - *

- * Note that this method is expensive. Wherever possible, reuse the returned - * object. - * - * @param elem - * the element - * @return the computed style - */ - public ComputedStyle(Element elem) { - computedStyle = getComputedStyle(elem); - this.elem = elem; - } - - private static native JavaScriptObject getComputedStyle(Element elem) - /*-{ - if(elem.nodeType != 1) { - return {}; - } - - if($wnd.document.defaultView && $wnd.document.defaultView.getComputedStyle) { - return $wnd.document.defaultView.getComputedStyle(elem, null); - } - - if(elem.currentStyle) { - return elem.currentStyle; - } - }-*/; - - /** - * - * @param name - * name of the CSS property in camelCase - * @return the value of the property, normalized for across browsers (each - * browser returns pixel values whenever possible). - */ - public final native String getProperty(String name) - /*-{ - var cs = this.@com.vaadin.client.ComputedStyle::computedStyle; - var elem = this.@com.vaadin.client.ComputedStyle::elem; - - // Border values need to be checked separately. The width might have a - // meaningful value even if the border style is "none". In that case the - // value should be 0. - if(name.indexOf("border") > -1 && name.indexOf("Width") > -1) { - var borderStyleProp = name.substring(0,name.length-5) + "Style"; - if(cs.getPropertyValue) - var borderStyle = cs.getPropertyValue(borderStyleProp); - else // IE - var borderStyle = cs[borderStyleProp]; - if(borderStyle == "none") - return "0px"; - } - - if(cs.getPropertyValue) { - - // Convert name to dashed format - name = name.replace(/([A-Z])/g, "-$1").toLowerCase(); - var ret = cs.getPropertyValue(name); - - } else { - - var ret = cs[name]; - var style = elem.style; - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { - // Remember the original values - var left = style.left, rsLeft = elem.runtimeStyle.left; - - // Put in the new values to get a computed value out - elem.runtimeStyle.left = cs.left; - style.left = ret || 0; - ret = style.pixelLeft + "px"; - - // Revert the changed values - style.left = left; - elem.runtimeStyle.left = rsLeft; - } - - } - - // Normalize margin values. This is not totally valid, but in most cases - // it is what the user wants to know. - if(name.indexOf("margin") > -1 && ret == "auto") { - return "0px"; - } - - // Some browsers return undefined width and height values as "auto", so - // we need to retrieve those ourselves. - if (name == "width" && ret == "auto") { - ret = elem.clientWidth + "px"; - } else if (name == "height" && ret == "auto") { - ret = elem.clientHeight + "px"; - } - - return ret; - - }-*/; - - /** - * Retrieves the given computed property as an integer - * - * Returns 0 if the property cannot be converted to an integer - * - * @param name - * the property to retrieve - * @return the integer value of the property or 0 - */ - public final int getIntProperty(String name) { - Profiler.enter("ComputedStyle.getIntProperty"); - String value = getProperty(name); - int result = parseIntNative(value); - Profiler.leave("ComputedStyle.getIntProperty"); - return result; - } - - /** - * Retrieves the given computed property as a double - * - * Returns NaN if the property cannot be converted to a double - * - * @since 7.5.1 - * @param name - * the property to retrieve - * @return the double value of the property - */ - public final double getDoubleProperty(String name) { - Profiler.enter("ComputedStyle.getDoubleProperty"); - String value = getProperty(name); - double result = parseDoubleNative(value); - Profiler.leave("ComputedStyle.getDoubleProperty"); - return result; - } - - /** - * Get current margin values from the DOM. The array order is the default - * CSS order: top, right, bottom, left. - */ - public final int[] getMargin() { - int[] margin = { 0, 0, 0, 0 }; - margin[0] = getIntProperty("marginTop"); - margin[1] = getIntProperty("marginRight"); - margin[2] = getIntProperty("marginBottom"); - margin[3] = getIntProperty("marginLeft"); - return margin; - } - - /** - * Get current padding values from the DOM. The array order is the default - * CSS order: top, right, bottom, left. - */ - public final int[] getPadding() { - int[] padding = { 0, 0, 0, 0 }; - padding[0] = getIntProperty("paddingTop"); - padding[1] = getIntProperty("paddingRight"); - padding[2] = getIntProperty("paddingBottom"); - padding[3] = getIntProperty("paddingLeft"); - return padding; - } - - /** - * Get current border values from the DOM. The array order is the default - * CSS order: top, right, bottom, left. - */ - public final int[] getBorder() { - int[] border = { 0, 0, 0, 0 }; - border[0] = getIntProperty("borderTopWidth"); - border[1] = getIntProperty("borderRightWidth"); - border[2] = getIntProperty("borderBottomWidth"); - border[3] = getIntProperty("borderLeftWidth"); - return border; - } - - /** - * Returns the current width from the DOM. - * - * @since 7.5.1 - * @return the computed width - */ - public double getWidth() { - return getDoubleProperty("width"); - } - - /** - * Returns the current height from the DOM. - * - * @since 7.5.1 - * @return the computed height - */ - public double getHeight() { - return getDoubleProperty("height"); - } - - /** - * Takes a String value e.g. "12px" and parses that to Integer 12. - * - * @param String - * a value starting with a number - * @return Integer the value from the string before any non-numeric - * characters. If the value cannot be parsed to a number, returns - * null. - * - * @deprecated Since 7.1.4, the method {@link #parseIntNative(String)} is - * used internally and this method does not belong in the public - * API of {@link ComputedStyle}. {@link #parseInt(String)} might - * be removed or moved to a utility class in future versions. - */ - @Deprecated - public static native Integer parseInt(final String value) - /*-{ - var number = parseInt(value, 10); - if (isNaN(number)) - return null; - else - // $entry not needed as function is not exported - return @java.lang.Integer::valueOf(I)(number); - }-*/; - - /** - * Takes a String value e.g. "12px" and parses that to int 12. - * - *

- * This method returns 0 for NaN. - * - * @param String - * a value starting with a number - * @return int the value from the string before any non-numeric characters. - * If the value cannot be parsed to a number, returns 0. - */ - private static native int parseIntNative(final String value) - /*-{ - var number = parseInt(value, 10); - if (isNaN(number)) - return 0; - else - return number; - }-*/; - - /** - * Takes a String value e.g. "12.3px" and parses that to a double, 12.3. - * - * @param String - * a value starting with a number - * @return the value from the string before any non-numeric characters or - * NaN if the value cannot be parsed as a number - */ - private static native double parseDoubleNative(final String value) - /*-{ - return parseFloat(value); - }-*/; - - /** - * Returns the sum of the top and bottom border width - * - * @since 7.5.3 - * @return the sum of the top and bottom border - */ - public double getBorderHeight() { - double borderHeight = getDoubleProperty("borderTopWidth"); - borderHeight += getDoubleProperty("borderBottomWidth"); - - return borderHeight; - } - - /** - * Returns the sum of the left and right border width - * - * @since 7.5.3 - * @return the sum of the left and right border - */ - public double getBorderWidth() { - double borderWidth = getDoubleProperty("borderLeftWidth"); - borderWidth += getDoubleProperty("borderRightWidth"); - - return borderWidth; - } - - /** - * Returns the sum of the top and bottom padding - * - * @since 7.5.3 - * @return the sum of the top and bottom padding - */ - public double getPaddingHeight() { - double paddingHeight = getDoubleProperty("paddingTop"); - paddingHeight += getDoubleProperty("paddingBottom"); - - return paddingHeight; - } - - /** - * Returns the sum of the top and bottom padding - * - * @since 7.5.3 - * @return the sum of the left and right padding - */ - public double getPaddingWidth() { - double paddingWidth = getDoubleProperty("paddingLeft"); - paddingWidth += getDoubleProperty("paddingRight"); - - return paddingWidth; - } - - /** - * Returns the sum of the top and bottom margin - * - * @since 7.5.6 - * @return the sum of the top and bottom margin - */ - public double getMarginHeight() { - double marginHeight = getDoubleProperty("marginTop"); - marginHeight += getDoubleProperty("marginBottom"); - - return marginHeight; - } - - /** - * Returns the sum of the top and bottom margin - * - * @since 7.5.6 - * @return the sum of the left and right margin - */ - public double getMarginWidth() { - double marginWidth = getDoubleProperty("marginLeft"); - marginWidth += getDoubleProperty("marginRight"); - - return marginWidth; - } - -} diff --git a/client/src/com/vaadin/client/ConnectorHierarchyChangeEvent.java b/client/src/com/vaadin/client/ConnectorHierarchyChangeEvent.java deleted file mode 100644 index 267b3d3bfd..0000000000 --- a/client/src/com/vaadin/client/ConnectorHierarchyChangeEvent.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import java.io.Serializable; -import java.util.List; - -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.GwtEvent; -import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler; -import com.vaadin.client.communication.AbstractServerConnectorEvent; - -/** - * Event for containing data related to a change in the {@link ServerConnector} - * hierarchy. A {@link ConnectorHierarchyChangedEvent} is fired when an update - * from the server has been fully processed and all hierarchy updates have been - * completed. - * - * @author Vaadin Ltd - * @since 7.0.0 - * - */ -public class ConnectorHierarchyChangeEvent extends - AbstractServerConnectorEvent { - /** - * Type of this event, used by the event bus. - */ - public static final Type TYPE = new Type(); - - List oldChildren; - - public ConnectorHierarchyChangeEvent() { - } - - /** - * Returns a collection of the old children for the connector. This was the - * state before the update was received from the server. - * - * @return A collection of old child connectors. Never returns null. - */ - public List getOldChildren() { - return oldChildren; - } - - /** - * Sets the collection of the old children for the connector. - * - * @param oldChildren - * The old child connectors. Must not be null. - */ - public void setOldChildren(List oldChildren) { - this.oldChildren = oldChildren; - } - - /** - * Returns the {@link HasComponentsConnector} for which this event occurred. - * - * @return The {@link HasComponentsConnector} whose child collection has - * changed. Never returns null. - */ - public HasComponentsConnector getParent() { - return (HasComponentsConnector) getConnector(); - } - - @Override - public void setConnector(ServerConnector connector) { - assert connector instanceof HasComponentsConnector : "A ConnectorHierarchyChangeEvent " - + "can only occur for connectors implementing HasComponentsConnector. " - + connector.getClass().getName() + " does not"; - - super.setConnector(connector); - } - - public interface ConnectorHierarchyChangeHandler extends Serializable, - EventHandler { - public void onConnectorHierarchyChange( - ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent); - } - - @Override - public void dispatch(ConnectorHierarchyChangeHandler handler) { - handler.onConnectorHierarchyChange(this); - } - - @Override - public GwtEvent.Type getAssociatedType() { - return TYPE; - } - -} diff --git a/client/src/com/vaadin/client/ConnectorMap.java b/client/src/com/vaadin/client/ConnectorMap.java deleted file mode 100644 index 3200dd6ab4..0000000000 --- a/client/src/com/vaadin/client/ConnectorMap.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.logging.Logger; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.dom.client.Element; -import com.google.gwt.user.client.ui.Widget; - -public class ConnectorMap { - - public static ConnectorMap get(ApplicationConnection applicationConnection) { - return applicationConnection.getConnectorMap(); - } - - @Deprecated - private final ComponentDetailMap idToComponentDetail = ComponentDetailMap - .create(); - - /** - * Returns a {@link ServerConnector} by its id - * - * @param id - * The connector id - * @return A connector or null if a connector with the given id has not been - * registered - */ - public ServerConnector getConnector(String connectorId) { - ComponentDetail componentDetail = idToComponentDetail.get(connectorId); - if (componentDetail == null) { - return null; - } else { - return componentDetail.getConnector(); - } - } - - /** - * Returns a {@link ComponentConnector} element by its root element. - * - * @param element - * Root element of the {@link ComponentConnector} - * @return A connector or null if a connector with the given id has not been - * registered - */ - public ComponentConnector getConnector(Element element) { - ServerConnector connector = getConnector(getConnectorId(element)); - if (!(connector instanceof ComponentConnector)) { - // This can happen at least if element is not part of this - // application but is part of another application and the connector - // id happens to map to e.g. an extension in this application - return null; - } - - // Ensure this connector is really connected to the element. We cannot - // be sure of this otherwise as the id comes from the DOM and could be - // part of another application. - ComponentConnector cc = (ComponentConnector) connector; - if (cc.getWidget() == null || cc.getWidget().getElement() != element) { - return null; - } - - return cc; - } - - /** - * FIXME: What does this even do and why? - * - * @param pid - * @return - */ - public boolean isDragAndDropPaintable(String pid) { - return (pid.startsWith("DD")); - } - - /** - * Checks if a connector with the given id has been registered. - * - * @param connectorId - * The id to check for - * @return true if a connector has been registered with the given id, false - * otherwise - */ - public boolean hasConnector(String connectorId) { - return idToComponentDetail.containsKey(connectorId); - } - - /** - * Removes all registered connectors - */ - public void clear() { - idToComponentDetail.clear(); - } - - /** - * Retrieves the connector whose widget matches the parameter. - * - * @param widget - * The widget - * @return A connector with {@literal widget} as its root widget or null if - * no connector was found - */ - public ComponentConnector getConnector(Widget widget) { - return widget == null ? null : getConnector(widget.getElement()); - } - - public void registerConnector(String id, ServerConnector connector) { - Profiler.enter("ConnectorMap.registerConnector"); - ComponentDetail componentDetail = GWT.create(ComponentDetail.class); - idToComponentDetail.put(id, componentDetail); - componentDetail.setConnector(connector); - if (connector instanceof ComponentConnector) { - ComponentConnector pw = (ComponentConnector) connector; - Widget widget = pw.getWidget(); - Profiler.enter("ConnectorMap.setConnectorId"); - setConnectorId(widget.getElement(), id); - Profiler.leave("ConnectorMap.setConnectorId"); - } - Profiler.leave("ConnectorMap.registerConnector"); - } - - private static native void setConnectorId(Element el, String id) - /*-{ - el.tkPid = id; - }-*/; - - /** - * Gets the connector id using a DOM element - the element should be the - * root element for a connector, otherwise no id will be found. Use - * {@link #getConnectorId(ServerConnector)} instead whenever possible. - * - * @see #getConnectorId(ServerConnector) - * @param el - * element of the connector whose id is desired - * @return the id of the element's connector, if it's a connector - */ - native static final String getConnectorId(Element el) - /*-{ - return el.tkPid; - }-*/; - - /** - * Gets the main element for the connector with the given id. The reverse of - * {@link #getConnectorId(Element)}. - * - * @param connectorId - * the id of the widget whose element is desired - * @return the element for the connector corresponding to the id - */ - public Element getElement(String connectorId) { - ServerConnector p = getConnector(connectorId); - if (p instanceof ComponentConnector) { - return ((ComponentConnector) p).getWidget().getElement(); - } - - return null; - } - - /** - * Unregisters the given connector; always use after removing a connector. - * This method does not remove the connector from the DOM, but marks the - * connector so that ApplicationConnection may clean up its references to - * it. Removing the widget from DOM is component containers responsibility. - * - * @param connector - * the connector to remove - */ - public void unregisterConnector(ServerConnector connector) { - if (connector == null) { - getLogger().severe("Trying to unregister null connector"); - return; - } - - String connectorId = connector.getConnectorId(); - - idToComponentDetail.remove(connectorId); - connector.onUnregister(); - - for (ServerConnector child : connector.getChildren()) { - if (child.getParent() == connector) { - /* - * Only unregister children that are actually connected to this - * parent. For instance when moving connectors from one layout - * to another and removing the first layout it will still - * contain references to its old children, which are now - * attached to another connector. - */ - unregisterConnector(child); - } - } - } - - /** - * Gets all registered {@link ComponentConnector} instances - * - * @return An array of all registered {@link ComponentConnector} instances - * - * @deprecated As of 7.0.1, use {@link #getComponentConnectorsAsJsArray()} - * for better performance. - */ - @Deprecated - public ComponentConnector[] getComponentConnectors() { - ArrayList result = new ArrayList(); - - JsArrayObject connectors = getConnectorsAsJsArray(); - int size = connectors.size(); - - for (int i = 0; i < size; i++) { - ServerConnector connector = connectors.get(i); - if (connector instanceof ComponentConnector) { - result.add((ComponentConnector) connector); - } - } - - return result.toArray(new ComponentConnector[result.size()]); - } - - public JsArrayObject getComponentConnectorsAsJsArray() { - JsArrayObject result = JavaScriptObject - .createArray().cast(); - - JsArrayObject connectors = getConnectorsAsJsArray(); - int size = connectors.size(); - for (int i = 0; i < size; i++) { - ServerConnector connector = connectors.get(i); - if (connector instanceof ComponentConnector) { - result.add((ComponentConnector) connector); - } - } - - return result; - } - - @Deprecated - private ComponentDetail getComponentDetail( - ComponentConnector componentConnector) { - return idToComponentDetail.get(componentConnector.getConnectorId()); - } - - public int size() { - return idToComponentDetail.size(); - } - - /** - * @return - * - * @deprecated As of 7.0.1, use {@link #getConnectorsAsJsArray()} for - * improved performance. - */ - @Deprecated - public Collection getConnectors() { - Collection values = idToComponentDetail.values(); - ArrayList arrayList = new ArrayList( - values.size()); - for (ComponentDetail componentDetail : values) { - arrayList.add(componentDetail.getConnector()); - } - return arrayList; - } - - public JsArrayObject getConnectorsAsJsArray() { - JsArrayObject componentDetails = idToComponentDetail - .valuesAsJsArray(); - JsArrayObject connectors = JavaScriptObject - .createArray().cast(); - - int size = componentDetails.size(); - for (int i = 0; i < size; i++) { - connectors.add(componentDetails.get(i).getConnector()); - } - - return connectors; - } - - /** - * Tests if the widget is the root widget of a {@link ComponentConnector}. - * - * @param widget - * The widget to test - * @return true if the widget is the root widget of a - * {@link ComponentConnector}, false otherwise - */ - public boolean isConnector(Widget w) { - return getConnectorId(w.getElement()) != null; - } - - private static Logger getLogger() { - return Logger.getLogger(ConnectorMap.class.getName()); - } -} diff --git a/client/src/com/vaadin/client/ContainerResizedListener.java b/client/src/com/vaadin/client/ContainerResizedListener.java deleted file mode 100644 index 702542f0ac..0000000000 --- a/client/src/com/vaadin/client/ContainerResizedListener.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client; - -/** - * ContainerResizedListener interface is useful for Widgets that support - * relative sizes and who need some additional sizing logic. - * - * @deprecated As of 7.0, serves no purpose. Use {@link LayoutManager} and its - * methods instead. - */ -@Deprecated -public interface ContainerResizedListener { - /** - * This function is run when container box has been resized. Object - * implementing ContainerResizedListener is responsible to call the same - * function on its ancestors that implement NeedsLayout in case their - * container has resized. runAnchestorsLayout(HasWidgets parent) function - * from Util class may be a good helper for this. - * - * @deprecated As of 7.0, this method is never called by the framework. - * - */ - @Deprecated - public void iLayout(); -} diff --git a/client/src/com/vaadin/client/DateTimeService.java b/client/src/com/vaadin/client/DateTimeService.java deleted file mode 100644 index 04006d85fb..0000000000 --- a/client/src/com/vaadin/client/DateTimeService.java +++ /dev/null @@ -1,504 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client; - -import java.util.Date; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.google.gwt.i18n.client.DateTimeFormat; -import com.google.gwt.i18n.client.LocaleInfo; -import com.vaadin.shared.ui.datefield.Resolution; - -/** - * This class provides date/time parsing services to all components on the - * client side. - * - * @author Vaadin Ltd. - * - */ -@SuppressWarnings("deprecation") -public class DateTimeService { - - private String currentLocale; - - private static int[] maxDaysInMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30, - 31, 30, 31 }; - - /** - * Creates a new date time service with the application default locale. - */ - public DateTimeService() { - currentLocale = LocaleService.getDefaultLocale(); - } - - /** - * Creates a new date time service with a given locale. - * - * @param locale - * e.g. fi, en etc. - * @throws LocaleNotLoadedException - */ - public DateTimeService(String locale) throws LocaleNotLoadedException { - setLocale(locale); - } - - public void setLocale(String locale) throws LocaleNotLoadedException { - if (LocaleService.getAvailableLocales().contains(locale)) { - currentLocale = locale; - } else { - throw new LocaleNotLoadedException(locale); - } - } - - public String getLocale() { - return currentLocale; - } - - public String getMonth(int month) { - try { - return LocaleService.getMonthNames(currentLocale)[month]; - } catch (final LocaleNotLoadedException e) { - getLogger().log(Level.SEVERE, "Error in getMonth", e); - return null; - } - } - - public String getShortMonth(int month) { - try { - return LocaleService.getShortMonthNames(currentLocale)[month]; - } catch (final LocaleNotLoadedException e) { - getLogger().log(Level.SEVERE, "Error in getShortMonth", e); - return null; - } - } - - public String getDay(int day) { - try { - return LocaleService.getDayNames(currentLocale)[day]; - } catch (final LocaleNotLoadedException e) { - getLogger().log(Level.SEVERE, "Error in getDay", e); - return null; - } - } - - public String getShortDay(int day) { - try { - return LocaleService.getShortDayNames(currentLocale)[day]; - } catch (final LocaleNotLoadedException e) { - getLogger().log(Level.SEVERE, "Error in getShortDay", e); - return null; - } - } - - public int getFirstDayOfWeek() { - try { - return LocaleService.getFirstDayOfWeek(currentLocale); - } catch (final LocaleNotLoadedException e) { - getLogger().log(Level.SEVERE, "Error in getFirstDayOfWeek", e); - return 0; - } - } - - public boolean isTwelveHourClock() { - try { - return LocaleService.isTwelveHourClock(currentLocale); - } catch (final LocaleNotLoadedException e) { - getLogger().log(Level.SEVERE, "Error in isTwelveHourClock", e); - return false; - } - } - - public String getClockDelimeter() { - try { - return LocaleService.getClockDelimiter(currentLocale); - } catch (final LocaleNotLoadedException e) { - getLogger().log(Level.SEVERE, "Error in getClockDelimiter", e); - return ":"; - } - } - - private static final String[] DEFAULT_AMPM_STRINGS = { "AM", "PM" }; - - public String[] getAmPmStrings() { - try { - return LocaleService.getAmPmStrings(currentLocale); - } catch (final LocaleNotLoadedException e) { - // TODO can this practically even happen? Should die instead? - getLogger().log(Level.SEVERE, - "Locale not loaded, using fallback : AM/PM", e); - return DEFAULT_AMPM_STRINGS; - } - } - - public int getStartWeekDay(Date date) { - final Date dateForFirstOfThisMonth = new Date(date.getYear(), - date.getMonth(), 1); - int firstDay; - try { - firstDay = LocaleService.getFirstDayOfWeek(currentLocale); - } catch (final LocaleNotLoadedException e) { - getLogger().log(Level.SEVERE, - "Locale not loaded, using fallback 0", e); - firstDay = 0; - } - int start = dateForFirstOfThisMonth.getDay() - firstDay; - if (start < 0) { - start = 6; - } - return start; - } - - public static void setMilliseconds(Date date, int ms) { - date.setTime(date.getTime() / 1000 * 1000 + ms); - } - - public static int getMilliseconds(Date date) { - if (date == null) { - return 0; - } - - return (int) (date.getTime() - date.getTime() / 1000 * 1000); - } - - public static int getNumberOfDaysInMonth(Date date) { - final int month = date.getMonth(); - if (month == 1 && true == isLeapYear(date)) { - return 29; - } - return maxDaysInMonth[month]; - } - - public static boolean isLeapYear(Date date) { - // Instantiate the date for 1st March of that year - final Date firstMarch = new Date(date.getYear(), 2, 1); - - // Go back 1 day - final long firstMarchTime = firstMarch.getTime(); - final long lastDayTimeFeb = firstMarchTime - (24 * 60 * 60 * 1000); // NUM_MILLISECS_A_DAY - - // Instantiate new Date with this time - final Date febLastDay = new Date(lastDayTimeFeb); - - // Check for date in this new instance - return (29 == febLastDay.getDate()) ? true : false; - } - - public static boolean isSameDay(Date d1, Date d2) { - return (getDayInt(d1) == getDayInt(d2)); - } - - public static boolean isInRange(Date date, Date rangeStart, Date rangeEnd, - Resolution resolution) { - Date s; - Date e; - if (rangeStart.after(rangeEnd)) { - s = rangeEnd; - e = rangeStart; - } else { - e = rangeEnd; - s = rangeStart; - } - long start = s.getYear() * 10000000000l; - long end = e.getYear() * 10000000000l; - long target = date.getYear() * 10000000000l; - - if (resolution == Resolution.YEAR) { - return (start <= target && end >= target); - } - start += s.getMonth() * 100000000l; - end += e.getMonth() * 100000000l; - target += date.getMonth() * 100000000l; - if (resolution == Resolution.MONTH) { - return (start <= target && end >= target); - } - start += s.getDate() * 1000000l; - end += e.getDate() * 1000000l; - target += date.getDate() * 1000000l; - if (resolution == Resolution.DAY) { - return (start <= target && end >= target); - } - start += s.getHours() * 10000l; - end += e.getHours() * 10000l; - target += date.getHours() * 10000l; - if (resolution == Resolution.HOUR) { - return (start <= target && end >= target); - } - start += s.getMinutes() * 100l; - end += e.getMinutes() * 100l; - target += date.getMinutes() * 100l; - if (resolution == Resolution.MINUTE) { - return (start <= target && end >= target); - } - start += s.getSeconds(); - end += e.getSeconds(); - target += date.getSeconds(); - return (start <= target && end >= target); - - } - - private static int getDayInt(Date date) { - final int y = date.getYear(); - final int m = date.getMonth(); - final int d = date.getDate(); - - return ((y + 1900) * 10000 + m * 100 + d) * 1000000000; - } - - /** - * Returns the ISO-8601 week number of the given date. - * - * @param date - * The date for which the week number should be resolved - * @return The ISO-8601 week number for {@literal date} - */ - public static int getISOWeekNumber(Date date) { - final long MILLISECONDS_PER_DAY = 24 * 3600 * 1000; - int dayOfWeek = date.getDay(); // 0 == sunday - - // ISO 8601 use weeks that start on monday so we use - // mon=1,tue=2,...sun=7; - if (dayOfWeek == 0) { - dayOfWeek = 7; - } - // Find nearest thursday (defines the week in ISO 8601). The week number - // for the nearest thursday is the same as for the target date. - int nearestThursdayDiff = 4 - dayOfWeek; // 4 is thursday - Date nearestThursday = new Date(date.getTime() + nearestThursdayDiff - * MILLISECONDS_PER_DAY); - - Date firstOfJanuary = new Date(nearestThursday.getYear(), 0, 1); - long timeDiff = nearestThursday.getTime() - firstOfJanuary.getTime(); - - // Rounding the result, as the division doesn't result in an integer - // when the given date is inside daylight saving time period. - int daysSinceFirstOfJanuary = (int) Math.round((double) timeDiff - / MILLISECONDS_PER_DAY); - - int weekNumber = (daysSinceFirstOfJanuary) / 7 + 1; - - return weekNumber; - } - - /** - * Check if format contains the month name. If it does we manually convert - * it to the month name since DateTimeFormat.format always uses the current - * locale and will replace the month name wrong if current locale is - * different from the locale set for the DateField. - * - * MMMM is converted into long month name, MMM is converted into short month - * name. '' are added around the name to avoid that DateTimeFormat parses - * the month name as a pattern. - * - * @param date - * The date to convert - * @param formatStr - * The format string that might contain MMM or MMMM - * @param dateTimeService - * Reference to the Vaadin DateTimeService - * @return - */ - public String formatDate(Date date, String formatStr) { - /* - * Format month and day names separately when locale for the - * DateTimeService is not the same as the browser locale - */ - formatStr = formatMonthNames(date, formatStr); - formatStr = formatDayNames(date, formatStr); - - // Format uses the browser locale - DateTimeFormat format = DateTimeFormat.getFormat(formatStr); - - String result = format.format(date); - - return result; - } - - private String formatDayNames(Date date, String formatStr) { - if (formatStr.contains("EEEE")) { - String dayName = getDay(date.getDay()); - - if (dayName != null) { - /* - * Replace 4 or more E:s with the quoted day name. Also - * concatenate generated string with any other string prepending - * or following the EEEE pattern, i.e. 'EEEE'ta ' becomes 'DAYta - * ' and not 'DAY''ta ', 'ab'EEEE becomes 'abDAY', 'x'EEEE'y' - * becomes 'xDAYy'. - */ - formatStr = formatStr.replaceAll("'([E]{4,})'", dayName); - formatStr = formatStr.replaceAll("([E]{4,})'", "'" + dayName); - formatStr = formatStr.replaceAll("'([E]{4,})", dayName + "'"); - formatStr = formatStr - .replaceAll("[E]{4,}", "'" + dayName + "'"); - } - } - - if (formatStr.contains("EEE")) { - - String dayName = getShortDay(date.getDay()); - - if (dayName != null) { - /* - * Replace 3 or more E:s with the quoted month name. Also - * concatenate generated string with any other string prepending - * or following the EEE pattern, i.e. 'EEE'ta ' becomes 'DAYta ' - * and not 'DAY''ta ', 'ab'EEE becomes 'abDAY', 'x'EEE'y' - * becomes 'xDAYy'. - */ - formatStr = formatStr.replaceAll("'([E]{3,})'", dayName); - formatStr = formatStr.replaceAll("([E]{3,})'", "'" + dayName); - formatStr = formatStr.replaceAll("'([E]{3,})", dayName + "'"); - formatStr = formatStr - .replaceAll("[E]{3,}", "'" + dayName + "'"); - } - } - - return formatStr; - } - - private String formatMonthNames(Date date, String formatStr) { - if (formatStr.contains("MMMM")) { - String monthName = getMonth(date.getMonth()); - - if (monthName != null) { - /* - * Replace 4 or more M:s with the quoted month name. Also - * concatenate generated string with any other string prepending - * or following the MMMM pattern, i.e. 'MMMM'ta ' becomes - * 'MONTHta ' and not 'MONTH''ta ', 'ab'MMMM becomes 'abMONTH', - * 'x'MMMM'y' becomes 'xMONTHy'. - */ - formatStr = formatStr.replaceAll("'([M]{4,})'", monthName); - formatStr = formatStr.replaceAll("([M]{4,})'", "'" + monthName); - formatStr = formatStr.replaceAll("'([M]{4,})", monthName + "'"); - formatStr = formatStr.replaceAll("[M]{4,}", "'" + monthName - + "'"); - } - } - - if (formatStr.contains("MMM")) { - - String monthName = getShortMonth(date.getMonth()); - - if (monthName != null) { - /* - * Replace 3 or more M:s with the quoted month name. Also - * concatenate generated string with any other string prepending - * or following the MMM pattern, i.e. 'MMM'ta ' becomes 'MONTHta - * ' and not 'MONTH''ta ', 'ab'MMM becomes 'abMONTH', 'x'MMM'y' - * becomes 'xMONTHy'. - */ - formatStr = formatStr.replaceAll("'([M]{3,})'", monthName); - formatStr = formatStr.replaceAll("([M]{3,})'", "'" + monthName); - formatStr = formatStr.replaceAll("'([M]{3,})", monthName + "'"); - formatStr = formatStr.replaceAll("[M]{3,}", "'" + monthName - + "'"); - } - } - - return formatStr; - } - - /** - * Replaces month names in the entered date with the name in the current - * browser locale. - * - * @param enteredDate - * Date string e.g. "5 May 2010" - * @param formatString - * Format string e.g. "d M yyyy" - * @return The date string where the month names have been replaced by the - * browser locale version - */ - private String parseMonthName(String enteredDate, String formatString) { - LocaleInfo browserLocale = LocaleInfo.getCurrentLocale(); - if (browserLocale.getLocaleName().equals(getLocale())) { - // No conversion needs to be done when locales match - return enteredDate; - } - String[] browserMonthNames = browserLocale.getDateTimeConstants() - .months(); - String[] browserShortMonthNames = browserLocale.getDateTimeConstants() - .shortMonths(); - - if (formatString.contains("MMMM")) { - // Full month name - for (int i = 0; i < 12; i++) { - enteredDate = enteredDate.replaceAll(getMonth(i), - browserMonthNames[i]); - } - } - if (formatString.contains("MMM")) { - // Short month name - for (int i = 0; i < 12; i++) { - enteredDate = enteredDate.replaceAll(getShortMonth(i), - browserShortMonthNames[i]); - } - } - - return enteredDate; - } - - /** - * Parses the given date string using the given format string and the locale - * set in this DateTimeService instance. - * - * @param dateString - * Date string e.g. "1 February 2010" - * @param formatString - * Format string e.g. "d MMMM yyyy" - * @param lenient - * true to use lenient parsing, false to use strict parsing - * @return A Date object representing the dateString. Never returns null. - * @throws IllegalArgumentException - * if the parsing fails - * - */ - public Date parseDate(String dateString, String formatString, - boolean lenient) throws IllegalArgumentException { - /* DateTimeFormat uses the browser's locale */ - DateTimeFormat format = DateTimeFormat.getFormat(formatString); - - /* - * Parse month names separately when locale for the DateTimeService is - * not the same as the browser locale - */ - dateString = parseMonthName(dateString, formatString); - - Date date; - - if (lenient) { - date = format.parse(dateString); - } else { - date = format.parseStrict(dateString); - } - - // Some version of Firefox sets the timestamp to 0 if parsing fails. - if (date != null && date.getTime() == 0) { - throw new IllegalArgumentException("Parsing of '" + dateString - + "' failed"); - } - - return date; - - } - - private static Logger getLogger() { - return Logger.getLogger(DateTimeService.class.getName()); - } -} diff --git a/client/src/com/vaadin/client/DeferredWorker.java b/client/src/com/vaadin/client/DeferredWorker.java deleted file mode 100644 index cc22cda2a2..0000000000 --- a/client/src/com/vaadin/client/DeferredWorker.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -/** - * Give widgets and connectors the possibility to indicate to the framework that - * there is work scheduled to be executed in the near future and that the - * framework should wait for this work to complete before assuming the UI has - * reached a steady state. - * - * @since 7.3 - * @author Vaadin Ltd - */ -public interface DeferredWorker { - /** - * Checks whether there are operations pending for this widget or connector - * that must be executed before reaching a steady state. - * - * @returns true iff there are operations pending which must be - * executed before reaching a steady state - */ - public boolean isWorkPending(); -} diff --git a/client/src/com/vaadin/client/DirectionalManagedLayout.java b/client/src/com/vaadin/client/DirectionalManagedLayout.java deleted file mode 100644 index eab9811082..0000000000 --- a/client/src/com/vaadin/client/DirectionalManagedLayout.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import com.vaadin.client.ui.ManagedLayout; - -public interface DirectionalManagedLayout extends ManagedLayout { - public void layoutVertically(); - - public void layoutHorizontally(); -} diff --git a/client/src/com/vaadin/client/EventHelper.java b/client/src/com/vaadin/client/EventHelper.java deleted file mode 100644 index 1ee252af0f..0000000000 --- a/client/src/com/vaadin/client/EventHelper.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import static com.vaadin.shared.EventId.BLUR; -import static com.vaadin.shared.EventId.FOCUS; - -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.DomEvent.Type; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.ui.Widget; - -/** - * Helper class for attaching/detaching handlers for Vaadin client side - * components, based on identifiers in UIDL. Helpers expect Paintables to be - * both listeners and sources for events. This helper cannot be used for more - * complex widgets. - *

- * Possible current registration is given as parameter. The returned - * registration (possibly the same as given, should be store for next update. - *

- * Pseudocode what helpers do: - * - *

- * 
- * if paintable has event listener in UIDL
- *      if registration is null
- *              register paintable as as handler for event
- *      return the registration
- * else 
- *      if registration is not null
- *              remove the handler from paintable
- *      return null
- * 
- * 
- * 
- */ -public class EventHelper { - - /** - * Adds or removes a focus handler depending on if the connector has focus - * listeners on the server side or not. - * - * @param connector - * The connector to update. Must implement focusHandler. - * @param handlerRegistration - * The old registration reference or null if no handler has been - * registered previously - * @return a new registration handler that can be used to unregister the - * handler later - */ - public static HandlerRegistration updateFocusHandler( - T connector, HandlerRegistration handlerRegistration) { - return updateHandler(connector, connector, FOCUS, handlerRegistration, - FocusEvent.getType(), connector.getWidget()); - } - - /** - * Adds or removes a focus handler depending on if the connector has focus - * listeners on the server side or not. - * - * @param connector - * The connector to update. Must implement focusHandler. - * @param handlerRegistration - * The old registration reference or null if no handler has been - * registered previously - * @param widget - * The widget which emits focus events - * @return a new registration handler that can be used to unregister the - * handler later - */ - public static HandlerRegistration updateFocusHandler( - T connector, HandlerRegistration handlerRegistration, Widget widget) { - return updateHandler(connector, connector, FOCUS, handlerRegistration, - FocusEvent.getType(), widget); - } - - /** - * Adds or removes a blur handler depending on if the connector has blur - * listeners on the server side or not. - * - * @param connector - * The connector to update. Must implement BlurHandler. - * @param handlerRegistration - * The old registration reference or null if no handler has been - * registered previously - * @return a new registration handler that can be used to unregister the - * handler later - */ - public static HandlerRegistration updateBlurHandler( - T connector, HandlerRegistration handlerRegistration) { - return updateHandler(connector, connector, BLUR, handlerRegistration, - BlurEvent.getType(), connector.getWidget()); - } - - /** - * Adds or removes a blur handler depending on if the connector has blur - * listeners on the server side or not. - * - * @param connector - * The connector to update. Must implement BlurHandler. - * @param handlerRegistration - * The old registration reference or null if no handler has been - * registered previously - * @param widget - * The widget which emits blur events - * - * @return a new registration handler that can be used to unregister the - * handler later - */ - public static HandlerRegistration updateBlurHandler( - T connector, HandlerRegistration handlerRegistration, Widget widget) { - return updateHandler(connector, connector, BLUR, handlerRegistration, - BlurEvent.getType(), widget); - } - - public static HandlerRegistration updateHandler( - ComponentConnector connector, H handler, String eventIdentifier, - HandlerRegistration handlerRegistration, Type type, Widget widget) { - if (connector.hasEventListener(eventIdentifier)) { - if (handlerRegistration == null) { - handlerRegistration = widget.addDomHandler(handler, type); - } - } else if (handlerRegistration != null) { - handlerRegistration.removeHandler(); - handlerRegistration = null; - } - return handlerRegistration; - } -} diff --git a/client/src/com/vaadin/client/FastStringMap.java b/client/src/com/vaadin/client/FastStringMap.java deleted file mode 100644 index ba3d07025b..0000000000 --- a/client/src/com/vaadin/client/FastStringMap.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client; - -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.JsArrayString; - -public final class FastStringMap extends JavaScriptObject { - - protected FastStringMap() { - // JSO constructor - } - - public native void put(String key, T value) - /*-{ - this[key] = value; - }-*/; - - public native T get(String key) - /*-{ - return this[key]; - }-*/; - - public native boolean containsKey(String key) - /*-{ - //Can't use this.hasOwnProperty in case that key is used - return Object.hasOwnProperty.call(this, key); - }-*/; - - public native void remove(String key) - /*-{ - delete this[key]; - }-*/; - - public native JsArrayString getKeys() - /*-{ - var keys = []; - for(var key in this) { - if (Object.hasOwnProperty.call(this, key)) { - keys.push(key); - } - } - return keys; - }-*/; - - public native int size() - /*-{ - var size = 0; - for(var key in this) { - if (Object.hasOwnProperty.call(this, key)) { - size++; - } - } - return size; - }-*/; - - public static FastStringMap create() { - return JavaScriptObject.createObject().cast(); - } -} diff --git a/client/src/com/vaadin/client/FastStringSet.java b/client/src/com/vaadin/client/FastStringSet.java deleted file mode 100644 index 756bd374dc..0000000000 --- a/client/src/com/vaadin/client/FastStringSet.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import java.util.Collection; - -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.JsArrayString; - -public final class FastStringSet extends JavaScriptObject { - protected FastStringSet() { - // JSO constructor - } - - public native boolean contains(String string) - /*-{ - return this.hasOwnProperty(string); - }-*/; - - public native void add(String string) - /*-{ - this[string] = true; - }-*/; - - public native void addAll(JsArrayString array) - /*-{ - for(var i = 0; i < array.length; i++) { - this[array[i]] = true; - } - }-*/; - - public native void addAll(FastStringSet set) - /*-{ - for(var string in set) { - if (Object.hasOwnProperty.call(set, string)) { - this[string] = true; - } - } - }-*/; - - public native JsArrayString dump() - /*-{ - var array = []; - for(var string in this) { - if (this.hasOwnProperty(string)) { - array.push(string); - } - } - return array; - }-*/; - - public native void remove(String string) - /*-{ - delete this[string]; - }-*/; - - public native boolean isEmpty() - /*-{ - for(var string in this) { - if (this.hasOwnProperty(string)) { - return false; - } - } - return true; - }-*/; - - public static FastStringSet create() { - return JavaScriptObject.createObject().cast(); - } - - public native void addAllTo(Collection target) - /*-{ - for(var string in this) { - if (Object.hasOwnProperty.call(this, string)) { - target.@java.util.Collection::add(Ljava/lang/Object;)(string); - } - } - }-*/; - - public native void removeAll(FastStringSet valuesToRemove) - /*-{ - for(var string in valuesToRemove) { - if (Object.hasOwnProperty.call(valuesToRemove, string)) { - delete this[string]; - } - } - }-*/; -} diff --git a/client/src/com/vaadin/client/Focusable.java b/client/src/com/vaadin/client/Focusable.java deleted file mode 100644 index 05b32a7b05..0000000000 --- a/client/src/com/vaadin/client/Focusable.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -/** - * GWT's HasFocus is way too overkill for just receiving focus in simple - * components. Vaadin uses this interface in addition to GWT's HasFocus to pass - * focus requests from server to actual ui widgets in browsers. - * - * So in to make your server side focusable component receive focus on client - * side it must either implement this or HasFocus interface. - */ -public interface Focusable { - /** - * Sets focus to this widget. - */ - public void focus(); -} diff --git a/client/src/com/vaadin/client/HasChildMeasurementHintConnector.java b/client/src/com/vaadin/client/HasChildMeasurementHintConnector.java deleted file mode 100644 index bb76377cd1..0000000000 --- a/client/src/com/vaadin/client/HasChildMeasurementHintConnector.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import com.vaadin.client.ui.ManagedLayout; -import com.vaadin.client.ui.layout.ElementResizeListener; - -/** - * Connector with layout measuring hint. Used to improve granularity of control - * over child component measurements. - * - * @since 7.6 - * @author Vaadin Ltd - */ -public interface HasChildMeasurementHintConnector extends - HasComponentsConnector { - - /** - * Specifies how you would like child components measurements to be handled. - * Since this is a hint, it can be ignored when deemed necessary. - */ - public enum ChildMeasurementHint { - - /** - * Always measure all child components (default). - */ - MEASURE_ALWAYS, - - /** - * Measure child component only if child component is a {@link Layout} - * or implements either {@link ManagedLayout} or - * {@link ElementResizeListener}. - */ - MEASURE_IF_NEEDED, - - /** - * Never measure child components. This can improve rendering speed of - * components with lots of children (e.g. Table), but can cause some - * child components to be rendered incorrectly (e.g. ComboBox). - */ - MEASURE_NEVER - } - - /** - * Sets the child measurement hint for this component. - * - * @param hint - * the value to set - */ - void setChildMeasurementHint(ChildMeasurementHint hint); - - /** - * Returns the current child measurement hint value. - * - * @return a ChildLayoutMeasureMode value - */ - ChildMeasurementHint getChildMeasurementHint(); - -} diff --git a/client/src/com/vaadin/client/HasComponentsConnector.java b/client/src/com/vaadin/client/HasComponentsConnector.java deleted file mode 100644 index 19b93edd61..0000000000 --- a/client/src/com/vaadin/client/HasComponentsConnector.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client; - -import java.util.List; - -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.ui.HasWidgets; -import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler; -import com.vaadin.ui.HasComponents; - -/** - * An interface used by client-side connectors whose widget is a component - * container (implements {@link HasWidgets}). - */ -public interface HasComponentsConnector extends ServerConnector { - - /** - * Update child components caption, description and error message. - * - *

- * Each component is responsible for maintaining its caption, description - * and error message. In most cases components doesn't want to do that and - * those elements reside outside of the component. Because of this layouts - * must provide service for it's childen to show those elements for them. - *

- * - * @param connector - * Child component for which service is requested. - */ - void updateCaption(ComponentConnector connector); - - /** - * Returns the child components for this connector. - *

- * The children for this connector are defined as all {@link HasComponents}s - * whose parent is this {@link HasComponentsConnector}. - *

- * Note that the method {@link ServerConnector#getChildren()} can return a - * larger list of children including both the child components and any - * extensions registered for the connector. - * - * @return A collection of child components for this connector. An empty - * collection if there are no children. Never returns null. - */ - public List getChildComponents(); - - /** - * Sets the children for this connector. This method should only be called - * by the framework to ensure that the connector hierarchy on the client - * side and the server side are in sync. - *

- * Note that calling this method does not call - * {@link ConnectorHierarchyChangeHandler#onConnectorHierarchyChange(ConnectorHierarchyChangeEvent)} - * . The event method is called only when the hierarchy has been updated for - * all connectors. - *

- * Note that this method is separate from - * {@link ServerConnector#setChildren(List)} and contains only child - * components. Both methods are called separately by the framework if the - * connector implements {@link HasComponentsConnector}. - * - * @param children - * The new child connectors (components only) - */ - public void setChildComponents(List children); - - /** - * Adds a handler that is called whenever the child hierarchy of this - * connector has been updated by the server. - * - * @param handler - * The handler that should be added. - * @return A handler registration reference that can be used to unregister - * the handler - */ - public HandlerRegistration addConnectorHierarchyChangeHandler( - ConnectorHierarchyChangeHandler handler); - -} diff --git a/client/src/com/vaadin/client/JavaScriptConnectorHelper.java b/client/src/com/vaadin/client/JavaScriptConnectorHelper.java deleted file mode 100644 index 1833b370e5..0000000000 --- a/client/src/com/vaadin/client/JavaScriptConnectorHelper.java +++ /dev/null @@ -1,511 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.logging.Logger; - -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.JsArray; -import com.google.gwt.dom.client.Element; -import com.vaadin.client.communication.JavaScriptMethodInvocation; -import com.vaadin.client.communication.ServerRpcQueue; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; -import com.vaadin.client.ui.layout.ElementResizeEvent; -import com.vaadin.client.ui.layout.ElementResizeListener; -import com.vaadin.shared.JavaScriptConnectorState; -import com.vaadin.shared.communication.MethodInvocation; - -import elemental.json.JsonArray; - -public class JavaScriptConnectorHelper { - - private final ServerConnector connector; - private final JavaScriptObject nativeState = JavaScriptObject - .createObject(); - private final JavaScriptObject rpcMap = JavaScriptObject.createObject(); - - private final Map rpcObjects = new HashMap(); - private final Map> rpcMethods = new HashMap>(); - private final Map> resizeListeners = new HashMap>(); - - private JavaScriptObject connectorWrapper; - private int tag; - - private String initFunctionName; - - public JavaScriptConnectorHelper(ServerConnector connector) { - this.connector = connector; - - // Wildcard rpc object - rpcObjects.put("", JavaScriptObject.createObject()); - } - - /** - * The id of the previous response for which state changes have been - * processed. If this is the same as the - * {@link ApplicationConnection#getLastSeenServerSyncId()}, it means that the - * state change has already been handled and should not be done again. - */ - private int processedResponseId = -1; - - public void init() { - connector.addStateChangeHandler(new StateChangeHandler() { - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - processStateChanges(); - } - }); - } - - /** - * Makes sure the javascript part of the connector has been initialized. The - * javascript is usually initalized the first time a state change event is - * received, but it might in some cases be necessary to make this happen - * earlier. - * - * @since 7.4.0 - */ - public void ensureJavascriptInited() { - if (initFunctionName == null) { - processStateChanges(); - } - } - - private void processStateChanges() { - int lastResponseId = connector.getConnection().getLastSeenServerSyncId(); - if (processedResponseId == lastResponseId) { - return; - } - processedResponseId = lastResponseId; - - JavaScriptObject wrapper = getConnectorWrapper(); - JavaScriptConnectorState state = getConnectorState(); - - for (String callback : state.getCallbackNames()) { - ensureCallback(JavaScriptConnectorHelper.this, wrapper, callback); - } - - for (Entry> entry : state.getRpcInterfaces() - .entrySet()) { - String rpcName = entry.getKey(); - String jsName = getJsInterfaceName(rpcName); - if (!rpcObjects.containsKey(jsName)) { - Set methods = entry.getValue(); - rpcObjects.put(jsName, createRpcObject(rpcName, methods)); - - // Init all methods for wildcard rpc - for (String method : methods) { - JavaScriptObject wildcardRpcObject = rpcObjects.get(""); - Set interfaces = rpcMethods.get(method); - if (interfaces == null) { - interfaces = new HashSet(); - rpcMethods.put(method, interfaces); - attachRpcMethod(wildcardRpcObject, null, method); - } - interfaces.add(rpcName); - } - } - } - - // Init after setting up callbacks & rpc - if (initFunctionName == null) { - initJavaScript(); - } - - invokeIfPresent(wrapper, "onStateChange"); - } - - private static String getJsInterfaceName(String rpcName) { - return rpcName.replace('$', '.'); - } - - protected JavaScriptObject createRpcObject(String iface, Set methods) { - JavaScriptObject object = JavaScriptObject.createObject(); - - for (String method : methods) { - attachRpcMethod(object, iface, method); - } - - return object; - } - - protected boolean initJavaScript() { - ApplicationConfiguration conf = connector.getConnection() - .getConfiguration(); - ArrayList attemptedNames = new ArrayList(); - Integer tag = Integer.valueOf(this.tag); - while (tag != null) { - String serverSideClassName = conf.getServerSideClassNameForTag(tag); - String initFunctionName = serverSideClassName - .replaceAll("\\.", "_"); - if (tryInitJs(initFunctionName, getConnectorWrapper())) { - getLogger().info( - "JavaScript connector initialized using " - + initFunctionName); - this.initFunctionName = initFunctionName; - return true; - } else { - getLogger() - .warning( - "No JavaScript function " + initFunctionName - + " found"); - attemptedNames.add(initFunctionName); - tag = conf.getParentTag(tag.intValue()); - } - } - getLogger().info("No JavaScript init for connector found"); - showInitProblem(attemptedNames); - return false; - } - - protected void showInitProblem(ArrayList attemptedNames) { - // Default does nothing - } - - private static native boolean tryInitJs(String initFunctionName, - JavaScriptObject connectorWrapper) - /*-{ - if (typeof $wnd[initFunctionName] == 'function') { - $wnd[initFunctionName].apply(connectorWrapper); - return true; - } else { - return false; - } - }-*/; - - public JavaScriptObject getConnectorWrapper() { - if (connectorWrapper == null) { - connectorWrapper = createConnectorWrapper(this, - connector.getConnection(), nativeState, rpcMap, - connector.getConnectorId(), rpcObjects); - } - - return connectorWrapper; - } - - private static native JavaScriptObject createConnectorWrapper( - JavaScriptConnectorHelper h, ApplicationConnection c, - JavaScriptObject nativeState, JavaScriptObject registeredRpc, - String connectorId, Map rpcObjects) - /*-{ - return { - 'getConnectorId': function() { - return connectorId; - }, - 'getParentId': $entry(function(connectorId) { - return h.@com.vaadin.client.JavaScriptConnectorHelper::getParentId(Ljava/lang/String;)(connectorId); - }), - 'getState': function() { - return nativeState; - }, - 'getRpcProxy': $entry(function(iface) { - if (!iface) { - iface = ''; - } - return rpcObjects.@java.util.Map::get(Ljava/lang/Object;)(iface); - }), - 'getElement': $entry(function(connectorId) { - return h.@com.vaadin.client.JavaScriptConnectorHelper::getWidgetElement(Ljava/lang/String;)(connectorId); - }), - 'registerRpc': function(iface, rpcHandler) { - //registerRpc(handler) -> registerRpc('', handler); - if (!rpcHandler) { - rpcHandler = iface; - iface = ''; - } - if (!registeredRpc[iface]) { - registeredRpc[iface] = []; - } - registeredRpc[iface].push(rpcHandler); - }, - 'translateVaadinUri': $entry(function(uri) { - return c.@com.vaadin.client.ApplicationConnection::translateVaadinUri(Ljava/lang/String;)(uri); - }), - 'addResizeListener': function(element, resizeListener) { - if (!element || element.nodeType != 1) throw "element must be defined"; - if (typeof resizeListener != "function") throw "resizeListener must be defined"; - $entry(h.@com.vaadin.client.JavaScriptConnectorHelper::addResizeListener(*)).call(h, element, resizeListener); - }, - 'removeResizeListener': function(element, resizeListener) { - if (!element || element.nodeType != 1) throw "element must be defined"; - if (typeof resizeListener != "function") throw "resizeListener must be defined"; - $entry(h.@com.vaadin.client.JavaScriptConnectorHelper::removeResizeListener(*)).call(h, element, resizeListener); - } - }; - }-*/; - - // Called from JSNI to add a listener - private void addResizeListener(Element element, - final JavaScriptObject callbackFunction) { - Map elementListeners = resizeListeners - .get(element); - if (elementListeners == null) { - elementListeners = new HashMap(); - resizeListeners.put(element, elementListeners); - } - - ElementResizeListener listener = elementListeners.get(callbackFunction); - if (listener == null) { - LayoutManager layoutManager = LayoutManager.get(connector - .getConnection()); - listener = new ElementResizeListener() { - @Override - public void onElementResize(ElementResizeEvent e) { - invokeElementResizeCallback(e.getElement(), - callbackFunction); - } - }; - layoutManager.addElementResizeListener(element, listener); - elementListeners.put(callbackFunction, listener); - } - } - - private static native void invokeElementResizeCallback(Element element, - JavaScriptObject callbackFunction) - /*-{ - // Call with a simple event object and 'this' pointing to the global scope - callbackFunction.call($wnd, {'element': element}); - }-*/; - - // Called from JSNI to remove a listener - private void removeResizeListener(Element element, - JavaScriptObject callbackFunction) { - Map listenerMap = resizeListeners - .get(element); - if (listenerMap == null) { - return; - } - - ElementResizeListener listener = listenerMap.remove(callbackFunction); - if (listener != null) { - LayoutManager.get(connector.getConnection()) - .removeElementResizeListener(element, listener); - if (listenerMap.isEmpty()) { - resizeListeners.remove(element); - } - } - } - - private native void attachRpcMethod(JavaScriptObject rpc, String iface, - String method) - /*-{ - var self = this; - rpc[method] = $entry(function() { - self.@com.vaadin.client.JavaScriptConnectorHelper::fireRpc(Ljava/lang/String;Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(iface, method, arguments); - }); - }-*/; - - private String getParentId(String connectorId) { - ServerConnector target = getConnector(connectorId); - if (target == null) { - return null; - } - ServerConnector parent = target.getParent(); - if (parent == null) { - return null; - } else { - return parent.getConnectorId(); - } - } - - private Element getWidgetElement(String connectorId) { - ServerConnector target = getConnector(connectorId); - if (target instanceof ComponentConnector) { - return ((ComponentConnector) target).getWidget().getElement(); - } else { - return null; - } - } - - private ServerConnector getConnector(String connectorId) { - if (connectorId == null || connectorId.length() == 0) { - return connector; - } - - return ConnectorMap.get(connector.getConnection()).getConnector( - connectorId); - } - - private void fireRpc(String iface, String method, - JsArray arguments) { - if (iface == null) { - iface = findWildcardInterface(method); - } - - JsonArray argumentsArray = Util.jso2json(arguments); - Object[] parameters = new Object[arguments.length()]; - for (int i = 0; i < parameters.length; i++) { - parameters[i] = argumentsArray.get(i); - } - ServerRpcQueue rpcQueue = ServerRpcQueue.get(connector.getConnection()); - rpcQueue.add(new JavaScriptMethodInvocation(connector.getConnectorId(), - iface, method, parameters), false); - rpcQueue.flush(); - } - - private String findWildcardInterface(String method) { - Set interfaces = rpcMethods.get(method); - if (interfaces.size() == 1) { - return interfaces.iterator().next(); - } else { - // TODO Resolve conflicts using argument count and types - String interfaceList = ""; - for (String iface : interfaces) { - if (interfaceList.length() != 0) { - interfaceList += ", "; - } - interfaceList += getJsInterfaceName(iface); - } - - throw new IllegalStateException( - "Can not call method " - + method - + " for wildcard rpc proxy because the function is defined for multiple rpc interfaces: " - + interfaceList - + ". Retrieve a rpc proxy for a specific interface using getRpcProxy(interfaceName) to use the function."); - } - } - - private void fireCallback(String name, JsArray arguments) { - MethodInvocation invocation = new JavaScriptMethodInvocation( - connector.getConnectorId(), - "com.vaadin.ui.JavaScript$JavaScriptCallbackRpc", "call", - new Object[] { name, arguments }); - ServerRpcQueue rpcQueue = ServerRpcQueue.get(connector.getConnection()); - rpcQueue.add(invocation, false); - rpcQueue.flush(); - } - - public void setNativeState(JavaScriptObject state) { - updateNativeState(nativeState, state); - } - - private static native void updateNativeState(JavaScriptObject state, - JavaScriptObject input) - /*-{ - // Copy all fields to existing state object - for(var key in state) { - if (state.hasOwnProperty(key)) { - delete state[key]; - } - } - - for(var key in input) { - if (input.hasOwnProperty(key)) { - state[key] = input[key]; - } - } - }-*/; - - public Object[] decodeRpcParameters(JsonArray parametersJson) { - return new Object[] { Util.json2jso(parametersJson) }; - } - - public void setTag(int tag) { - this.tag = tag; - } - - public void invokeJsRpc(MethodInvocation invocation, - JsonArray parametersJson) { - String iface = invocation.getInterfaceName(); - String method = invocation.getMethodName(); - if ("com.vaadin.ui.JavaScript$JavaScriptCallbackRpc".equals(iface) - && "call".equals(method)) { - String callbackName = parametersJson.getString(0); - JavaScriptObject arguments = Util.json2jso(parametersJson.get(1)); - invokeCallback(getConnectorWrapper(), callbackName, arguments); - } else { - JavaScriptObject arguments = Util.json2jso(parametersJson); - invokeJsRpc(rpcMap, iface, method, arguments); - // Also invoke wildcard interface - invokeJsRpc(rpcMap, "", method, arguments); - } - } - - private static native void invokeCallback(JavaScriptObject connector, - String name, JavaScriptObject arguments) - /*-{ - connector[name].apply(connector, arguments); - }-*/; - - private static native void invokeJsRpc(JavaScriptObject rpcMap, - String interfaceName, String methodName, JavaScriptObject parameters) - /*-{ - var targets = rpcMap[interfaceName]; - if (!targets) { - return; - } - for(var i = 0; i < targets.length; i++) { - var target = targets[i]; - target[methodName].apply(target, parameters); - } - }-*/; - - private static native void ensureCallback(JavaScriptConnectorHelper h, - JavaScriptObject connector, String name) - /*-{ - connector[name] = $entry(function() { - var args = Array.prototype.slice.call(arguments, 0); - h.@com.vaadin.client.JavaScriptConnectorHelper::fireCallback(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(name, args); - }); - }-*/; - - private JavaScriptConnectorState getConnectorState() { - return (JavaScriptConnectorState) connector.getState(); - } - - public void onUnregister() { - invokeIfPresent(connectorWrapper, "onUnregister"); - - if (!resizeListeners.isEmpty()) { - LayoutManager layoutManager = LayoutManager.get(connector - .getConnection()); - for (Entry> entry : resizeListeners - .entrySet()) { - Element element = entry.getKey(); - for (ElementResizeListener listener : entry.getValue().values()) { - layoutManager - .removeElementResizeListener(element, listener); - } - } - resizeListeners.clear(); - } - } - - private static native void invokeIfPresent( - JavaScriptObject connectorWrapper, String functionName) - /*-{ - if (typeof connectorWrapper[functionName] == 'function') { - connectorWrapper[functionName].apply(connectorWrapper, arguments); - } - }-*/; - - public String getInitFunctionName() { - return initFunctionName; - } - - private static Logger getLogger() { - return Logger.getLogger(JavaScriptConnectorHelper.class.getName()); - } -} diff --git a/client/src/com/vaadin/client/JavaScriptExtension.java b/client/src/com/vaadin/client/JavaScriptExtension.java deleted file mode 100644 index 9c60d38a5a..0000000000 --- a/client/src/com/vaadin/client/JavaScriptExtension.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client; - -import com.vaadin.client.communication.HasJavaScriptConnectorHelper; -import com.vaadin.client.extensions.AbstractExtensionConnector; -import com.vaadin.server.AbstractJavaScriptExtension; -import com.vaadin.shared.JavaScriptExtensionState; -import com.vaadin.shared.ui.Connect; - -@Connect(AbstractJavaScriptExtension.class) -public final class JavaScriptExtension extends AbstractExtensionConnector - implements HasJavaScriptConnectorHelper { - private final JavaScriptConnectorHelper helper = new JavaScriptConnectorHelper( - this); - - @Override - protected void init() { - super.init(); - helper.init(); - } - - @Override - public JavaScriptConnectorHelper getJavascriptConnectorHelper() { - return helper; - } - - @Override - public JavaScriptExtensionState getState() { - return (JavaScriptExtensionState) super.getState(); - } - - @Override - public void onUnregister() { - super.onUnregister(); - helper.onUnregister(); - } - - @Override - protected void extend(ServerConnector target) { - // Nothing to do for JavaScriptExtension here. Everything is done in - // javascript. - } -} diff --git a/client/src/com/vaadin/client/JsArrayObject.java b/client/src/com/vaadin/client/JsArrayObject.java deleted file mode 100644 index b6dcf3789d..0000000000 --- a/client/src/com/vaadin/client/JsArrayObject.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client; - -import com.google.gwt.core.client.JavaScriptObject; - -public final class JsArrayObject extends JavaScriptObject { - - protected JsArrayObject() { - // JSO constructor - } - - public native void add(T value) - /*-{ - this.push(value); - }-*/; - - public native int size() - /*-{ - return this.length; - }-*/; - - public native T get(int i) - /*-{ - return this[i]; - }-*/; - -} diff --git a/client/src/com/vaadin/client/LayoutManager.java b/client/src/com/vaadin/client/LayoutManager.java deleted file mode 100644 index ed83e195d7..0000000000 --- a/client/src/com/vaadin/client/LayoutManager.java +++ /dev/null @@ -1,1836 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.google.gwt.core.client.Duration; -import com.google.gwt.core.client.JsArrayString; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.user.client.Timer; -import com.vaadin.client.MeasuredSize.MeasureResult; -import com.vaadin.client.ui.ManagedLayout; -import com.vaadin.client.ui.PostLayoutListener; -import com.vaadin.client.ui.SimpleManagedLayout; -import com.vaadin.client.ui.VNotification; -import com.vaadin.client.ui.layout.ElementResizeEvent; -import com.vaadin.client.ui.layout.ElementResizeListener; -import com.vaadin.client.ui.layout.LayoutDependencyTree; - -public class LayoutManager { - private static final String STATE_CHANGE_MESSAGE = "Cannot run layout while processing state change from the server."; - - private static final String LOOP_ABORT_MESSAGE = "Aborting layout after 100 passes. This would probably be an infinite loop."; - - private static final boolean debugLogging = false; - - private ApplicationConnection connection; - private final Set measuredNonConnectorElements = new HashSet(); - private final MeasuredSize nullSize = new MeasuredSize(); - - private LayoutDependencyTree currentDependencyTree; - - private FastStringSet needsHorizontalLayout = FastStringSet.create(); - private FastStringSet needsVerticalLayout = FastStringSet.create(); - - private FastStringSet needsMeasure = FastStringSet.create(); - - private FastStringSet pendingOverflowFixes = FastStringSet.create(); - - private final Map> elementResizeListeners = new HashMap>(); - private final Set listenersToFire = new HashSet(); - - private boolean layoutPending = false; - private Timer layoutTimer = new Timer() { - @Override - public void run() { - layoutNow(); - } - }; - private boolean everythingNeedsMeasure = false; - - /** - * Sets the application connection this instance is connected to. Called - * internally by the framework. - * - * @param connection - * the application connection this instance is connected to - */ - public void setConnection(ApplicationConnection connection) { - if (this.connection != null) { - throw new RuntimeException( - "LayoutManager connection can never be changed"); - } - this.connection = connection; - } - - /** - * Returns the application connection for this layout manager. - * - * @return connection - */ - protected ApplicationConnection getConnection() { - return connection; - } - - /** - * Gets the layout manager associated with the given - * {@link ApplicationConnection}. - * - * @param connection - * the application connection to get a layout manager for - * @return the layout manager associated with the provided application - * connection - */ - public static LayoutManager get(ApplicationConnection connection) { - return connection.getLayoutManager(); - } - - /** - * Registers that a ManagedLayout is depending on the size of an Element. - * This causes this layout manager to measure the element in the beginning - * of every layout phase and call the appropriate layout method of the - * managed layout if the size of the element has changed. - * - * @param owner - * the ManagedLayout that depends on an element - * @param element - * the Element that should be measured - */ - public void registerDependency(ManagedLayout owner, Element element) { - MeasuredSize measuredSize = ensureMeasured(element); - setNeedsLayout(owner); - measuredSize.addDependent(owner.getConnectorId()); - } - - private MeasuredSize ensureMeasured(Element element) { - MeasuredSize measuredSize = getMeasuredSize(element, null); - if (measuredSize == null) { - measuredSize = new MeasuredSize(); - - if (ConnectorMap.get(connection).getConnector(element) == null) { - measuredNonConnectorElements.add(element); - } - setMeasuredSize(element, measuredSize); - } - return measuredSize; - } - - private boolean needsMeasure(Element e) { - ComponentConnector connector = connection.getConnectorMap() - .getConnector(e); - if (connector != null && needsMeasureForManagedLayout(connector)) { - return true; - } else if (elementResizeListeners.containsKey(e)) { - return true; - } else if (getMeasuredSize(e, nullSize).hasDependents()) { - return true; - } else { - return false; - } - } - - private boolean needsMeasureForManagedLayout(ComponentConnector connector) { - if (connector instanceof ManagedLayout) { - return true; - } else if (connector.getParent() instanceof ManagedLayout) { - return true; - } else { - return false; - } - } - - /** - * Assigns a measured size to an element. Method defined as protected to - * allow separate implementation for IE8. - * - * @param element - * the dom element to attach the measured size to - * @param measuredSize - * the measured size to attach to the element. If - * null, any previous measured size is removed. - */ - protected native void setMeasuredSize(Element element, - MeasuredSize measuredSize) - /*-{ - if (measuredSize) { - element.vMeasuredSize = measuredSize; - } else { - delete element.vMeasuredSize; - } - }-*/; - - /** - * Gets the measured size for an element. Method defined as protected to - * allow separate implementation for IE8. - * - * @param element - * The element to get measured size for - * @param defaultSize - * The size to return if no measured size could be found - * @return The measured size for the element or {@literal defaultSize} - */ - protected native MeasuredSize getMeasuredSize(Element element, - MeasuredSize defaultSize) - /*-{ - return element.vMeasuredSize || defaultSize; - }-*/; - - private final MeasuredSize getMeasuredSize(Element element) { - MeasuredSize measuredSize = getMeasuredSize(element, null); - if (measuredSize == null) { - measuredSize = new MeasuredSize(); - setMeasuredSize(element, measuredSize); - } - return measuredSize; - } - - /** - * Registers that a ManagedLayout is no longer depending on the size of an - * Element. - * - * @see #registerDependency(ManagedLayout, Element) - * - * @param owner - * the ManagedLayout no longer depends on an element - * @param element - * the Element that that no longer needs to be measured - */ - public void unregisterDependency(ManagedLayout owner, Element element) { - MeasuredSize measuredSize = getMeasuredSize(element, null); - if (measuredSize == null) { - return; - } - measuredSize.removeDependent(owner.getConnectorId()); - stopMeasuringIfUnecessary(element); - } - - public boolean isLayoutRunning() { - return currentDependencyTree != null; - } - - private void countLayout(FastStringMap layoutCounts, - ManagedLayout layout) { - Integer count = layoutCounts.get(layout.getConnectorId()); - if (count == null) { - count = Integer.valueOf(0); - } else { - count = Integer.valueOf(count.intValue() + 1); - } - layoutCounts.put(layout.getConnectorId(), count); - if (count.intValue() > 2) { - getLogger().severe( - Util.getConnectorString(layout) + " has been layouted " - + count.intValue() + " times"); - } - } - - public void layoutLater() { - if (!layoutPending) { - layoutPending = true; - layoutTimer.schedule(100); - } - } - - public void layoutNow() { - if (isLayoutRunning()) { - throw new IllegalStateException( - "Can't start a new layout phase before the previous layout phase ends."); - } - - if (connection.getMessageHandler().isUpdatingState()) { - // If assertions are enabled, throw an exception - assert false : STATE_CHANGE_MESSAGE; - - // Else just log a warning and postpone the layout - getLogger().warning(STATE_CHANGE_MESSAGE); - - // Framework will call layoutNow when the state update is completed - return; - } - - layoutPending = false; - layoutTimer.cancel(); - try { - currentDependencyTree = new LayoutDependencyTree(connection); - doLayout(); - } finally { - currentDependencyTree = null; - } - } - - /** - * Called once per iteration in the layout loop before size calculations so - * different browsers quirks can be handled. Mainly this is currently for - * the IE8 permutation. - */ - protected void performBrowserLayoutHacks() { - // Permutations implement this - } - - private void doLayout() { - getLogger().info("Starting layout phase"); - Profiler.enter("LayoutManager phase init"); - - FastStringMap layoutCounts = FastStringMap.create(); - - int passes = 0; - Duration totalDuration = new Duration(); - - ConnectorMap connectorMap = ConnectorMap.get(connection); - - JsArrayString dump = needsHorizontalLayout.dump(); - int dumpLength = dump.length(); - for (int i = 0; i < dumpLength; i++) { - String layoutId = dump.get(i); - currentDependencyTree.setNeedsHorizontalLayout(layoutId, true); - } - - dump = needsVerticalLayout.dump(); - dumpLength = dump.length(); - for (int i = 0; i < dumpLength; i++) { - String layoutId = dump.get(i); - currentDependencyTree.setNeedsVerticalLayout(layoutId, true); - } - needsHorizontalLayout = FastStringSet.create(); - needsVerticalLayout = FastStringSet.create(); - - dump = needsMeasure.dump(); - dumpLength = dump.length(); - for (int i = 0; i < dumpLength; i++) { - ServerConnector connector = connectorMap.getConnector(dump.get(i)); - if (connector != null) { - currentDependencyTree.setNeedsMeasure( - (ComponentConnector) connector, true); - } - } - needsMeasure = FastStringSet.create(); - - measureNonConnectors(); - - Profiler.leave("LayoutManager phase init"); - - while (true) { - Profiler.enter("Layout pass"); - passes++; - - performBrowserLayoutHacks(); - - Profiler.enter("Layout measure connectors"); - int measuredConnectorCount = measureConnectors( - currentDependencyTree, everythingNeedsMeasure); - Profiler.leave("Layout measure connectors"); - - everythingNeedsMeasure = false; - if (measuredConnectorCount == 0) { - getLogger().info("No more changes in pass " + passes); - Profiler.leave("Layout pass"); - break; - } - - int firedListeners = 0; - if (!listenersToFire.isEmpty()) { - firedListeners = listenersToFire.size(); - Profiler.enter("Layout fire resize events"); - for (Element element : listenersToFire) { - Collection listeners = elementResizeListeners - .get(element); - if (listeners != null) { - Profiler.enter("Layout fire resize events - listeners not null"); - Profiler.enter("ElementResizeListener.onElementResize copy list"); - ElementResizeListener[] array = listeners - .toArray(new ElementResizeListener[listeners - .size()]); - Profiler.leave("ElementResizeListener.onElementResize copy list"); - ElementResizeEvent event = new ElementResizeEvent(this, - element); - for (ElementResizeListener listener : array) { - try { - String key = null; - if (Profiler.isEnabled()) { - Profiler.enter("ElementResizeListener.onElementResize construct profiler key"); - key = "ElementResizeListener.onElementResize for " - + listener.getClass() - .getSimpleName(); - Profiler.leave("ElementResizeListener.onElementResize construct profiler key"); - Profiler.enter(key); - } - - listener.onElementResize(event); - if (Profiler.isEnabled()) { - Profiler.leave(key); - } - } catch (RuntimeException e) { - getLogger().log(Level.SEVERE, - "Error in resize listener", e); - } - } - Profiler.leave("Layout fire resize events - listeners not null"); - } - } - listenersToFire.clear(); - - Profiler.leave("Layout fire resize events"); - } - - Profiler.enter("LayoutManager handle ManagedLayout"); - - FastStringSet updatedSet = FastStringSet.create(); - - int layoutCount = 0; - while (currentDependencyTree.hasHorizontalConnectorToLayout() - || currentDependencyTree.hasVerticaConnectorToLayout()) { - - JsArrayString layoutTargets = currentDependencyTree - .getHorizontalLayoutTargetsJsArray(); - int length = layoutTargets.length(); - for (int i = 0; i < length; i++) { - ManagedLayout layout = (ManagedLayout) connectorMap - .getConnector(layoutTargets.get(i)); - if (layout instanceof DirectionalManagedLayout) { - currentDependencyTree - .markAsHorizontallyLayouted(layout); - DirectionalManagedLayout cl = (DirectionalManagedLayout) layout; - try { - String key = null; - if (Profiler.isEnabled()) { - key = "layoutHorizontally() for " - + cl.getClass().getSimpleName(); - Profiler.enter(key); - } - - cl.layoutHorizontally(); - layoutCount++; - - if (Profiler.isEnabled()) { - Profiler.leave(key); - } - } catch (RuntimeException e) { - getLogger().log(Level.SEVERE, - "Error in ManagedLayout handling", e); - } - countLayout(layoutCounts, cl); - } else { - currentDependencyTree - .markAsHorizontallyLayouted(layout); - currentDependencyTree.markAsVerticallyLayouted(layout); - SimpleManagedLayout rr = (SimpleManagedLayout) layout; - try { - String key = null; - if (Profiler.isEnabled()) { - key = "layout() for " - + rr.getClass().getSimpleName(); - Profiler.enter(key); - } - - rr.layout(); - layoutCount++; - - if (Profiler.isEnabled()) { - Profiler.leave(key); - } - } catch (RuntimeException e) { - getLogger() - .log(Level.SEVERE, - "Error in SimpleManagedLayout (horizontal) handling", - e); - - } - countLayout(layoutCounts, rr); - } - if (debugLogging) { - updatedSet.add(layout.getConnectorId()); - } - } - - layoutTargets = currentDependencyTree - .getVerticalLayoutTargetsJsArray(); - length = layoutTargets.length(); - for (int i = 0; i < length; i++) { - ManagedLayout layout = (ManagedLayout) connectorMap - .getConnector(layoutTargets.get(i)); - if (layout instanceof DirectionalManagedLayout) { - currentDependencyTree.markAsVerticallyLayouted(layout); - DirectionalManagedLayout cl = (DirectionalManagedLayout) layout; - try { - String key = null; - if (Profiler.isEnabled()) { - key = "layoutVertically() for " - + cl.getClass().getSimpleName(); - Profiler.enter(key); - } - - cl.layoutVertically(); - layoutCount++; - - if (Profiler.isEnabled()) { - Profiler.leave(key); - } - } catch (RuntimeException e) { - getLogger() - .log(Level.SEVERE, - "Error in DirectionalManagedLayout handling", - e); - } - countLayout(layoutCounts, cl); - } else { - currentDependencyTree - .markAsHorizontallyLayouted(layout); - currentDependencyTree.markAsVerticallyLayouted(layout); - SimpleManagedLayout rr = (SimpleManagedLayout) layout; - try { - String key = null; - if (Profiler.isEnabled()) { - key = "layout() for " - + rr.getClass().getSimpleName(); - Profiler.enter(key); - } - - rr.layout(); - layoutCount++; - - if (Profiler.isEnabled()) { - Profiler.leave(key); - } - } catch (RuntimeException e) { - getLogger() - .log(Level.SEVERE, - "Error in SimpleManagedLayout (vertical) handling", - e); - } - countLayout(layoutCounts, rr); - } - if (debugLogging) { - updatedSet.add(layout.getConnectorId()); - } - } - } - - Profiler.leave("LayoutManager handle ManagedLayout"); - - if (debugLogging) { - JsArrayString changedCids = updatedSet.dump(); - - StringBuilder b = new StringBuilder(" "); - b.append(changedCids.length()); - b.append(" requestLayout invocations "); - if (changedCids.length() < 30) { - for (int i = 0; i < changedCids.length(); i++) { - if (i != 0) { - b.append(", "); - } else { - b.append(": "); - } - String connectorString = changedCids.get(i); - if (changedCids.length() < 10) { - ServerConnector connector = ConnectorMap.get( - connection).getConnector(connectorString); - connectorString = Util - .getConnectorString(connector); - } - b.append(connectorString); - } - } - getLogger().info(b.toString()); - } - - Profiler.leave("Layout pass"); - - getLogger() - .info("Pass " + passes + " measured " - + measuredConnectorCount + " elements, fired " - + firedListeners + " listeners and did " - + layoutCount + " layouts."); - - if (passes > 100) { - getLogger().severe(LOOP_ABORT_MESSAGE); - if (ApplicationConfiguration.isDebugMode()) { - VNotification.createNotification( - VNotification.DELAY_FOREVER, - connection.getUIConnector().getWidget()) - .show(LOOP_ABORT_MESSAGE, VNotification.CENTERED, - "error"); - } - break; - } - } - - Profiler.enter("layout PostLayoutListener"); - JsArrayObject componentConnectors = connectorMap - .getComponentConnectorsAsJsArray(); - int size = componentConnectors.size(); - for (int i = 0; i < size; i++) { - ComponentConnector connector = componentConnectors.get(i); - if (connector instanceof PostLayoutListener) { - String key = null; - if (Profiler.isEnabled()) { - key = "layout PostLayoutListener for " - + connector.getClass().getSimpleName(); - Profiler.enter(key); - } - - ((PostLayoutListener) connector).postLayout(); - - if (Profiler.isEnabled()) { - Profiler.leave(key); - } - } - } - Profiler.leave("layout PostLayoutListener"); - - cleanMeasuredSizes(); - - getLogger().info( - "Total layout phase time: " + totalDuration.elapsedMillis() - + "ms"); - } - - private void logConnectorStatus(int connectorId) { - currentDependencyTree - .logDependencyStatus((ComponentConnector) ConnectorMap.get( - connection).getConnector(Integer.toString(connectorId))); - } - - private int measureConnectors(LayoutDependencyTree layoutDependencyTree, - boolean measureAll) { - Profiler.enter("Layout overflow fix handling"); - JsArrayString pendingOverflowConnectorsIds = pendingOverflowFixes - .dump(); - int pendingOverflowCount = pendingOverflowConnectorsIds.length(); - ConnectorMap connectorMap = ConnectorMap.get(connection); - if (pendingOverflowCount > 0) { - HashMap originalOverflows = new HashMap(); - - FastStringSet delayedOverflowFixes = FastStringSet.create(); - - // First set overflow to hidden (and save previous value so it can - // be restored later) - for (int i = 0; i < pendingOverflowCount; i++) { - String connectorId = pendingOverflowConnectorsIds.get(i); - ComponentConnector componentConnector = (ComponentConnector) connectorMap - .getConnector(connectorId); - - if (delayOverflowFix(componentConnector)) { - delayedOverflowFixes.add(connectorId); - continue; - } - - if (debugLogging) { - getLogger() - .info("Doing overflow fix for " - + Util.getConnectorString(componentConnector) - + " in " - + Util.getConnectorString(componentConnector - .getParent())); - } - Profiler.enter("Overflow fix apply"); - - Element parentElement = componentConnector.getWidget() - .getElement().getParentElement(); - Style style = parentElement.getStyle(); - String originalOverflow = style.getOverflow(); - - if (originalOverflow != null - && !originalOverflows.containsKey(parentElement)) { - // Store original value for restore, but only the first time - // the value is changed - originalOverflows.put(parentElement, originalOverflow); - } - - style.setOverflow(Overflow.HIDDEN); - Profiler.leave("Overflow fix apply"); - } - - pendingOverflowFixes.removeAll(delayedOverflowFixes); - - JsArrayString remainingOverflowFixIds = pendingOverflowFixes.dump(); - int remainingCount = remainingOverflowFixIds.length(); - - Profiler.enter("Overflow fix reflow"); - // Then ensure all scrolling elements are reflowed by measuring - for (int i = 0; i < remainingCount; i++) { - ComponentConnector componentConnector = (ComponentConnector) connectorMap - .getConnector(remainingOverflowFixIds.get(i)); - componentConnector.getWidget().getElement().getParentElement() - .getOffsetHeight(); - } - Profiler.leave("Overflow fix reflow"); - - Profiler.enter("Overflow fix restore"); - // Finally restore old overflow value and update bookkeeping - for (int i = 0; i < remainingCount; i++) { - String connectorId = remainingOverflowFixIds.get(i); - ComponentConnector componentConnector = (ComponentConnector) connectorMap - .getConnector(connectorId); - Element parentElement = componentConnector.getWidget() - .getElement().getParentElement(); - parentElement.getStyle().setProperty("overflow", - originalOverflows.get(parentElement)); - - layoutDependencyTree.setNeedsMeasure(componentConnector, true); - } - Profiler.leave("Overflow fix restore"); - - if (!pendingOverflowFixes.isEmpty()) { - getLogger().info( - "Did overflow fix for " + remainingCount + " elements"); - } - pendingOverflowFixes = delayedOverflowFixes; - } - Profiler.leave("Layout overflow fix handling"); - - int measureCount = 0; - if (measureAll) { - Profiler.enter("Layout measureAll"); - JsArrayObject allConnectors = connectorMap - .getComponentConnectorsAsJsArray(); - int size = allConnectors.size(); - - // Find connectors that should actually be measured - JsArrayObject connectors = JsArrayObject - .createArray().cast(); - for (int i = 0; i < size; i++) { - ComponentConnector candidate = allConnectors.get(i); - if (!Util.shouldSkipMeasurementOfConnector(candidate) - && needsMeasure(candidate.getWidget().getElement())) { - connectors.add(candidate); - } - } - - int connectorCount = connectors.size(); - for (int i = 0; i < connectorCount; i++) { - measureConnector(connectors.get(i)); - } - for (int i = 0; i < connectorCount; i++) { - layoutDependencyTree.setNeedsMeasure(connectors.get(i), false); - } - measureCount += connectorCount; - - Profiler.leave("Layout measureAll"); - } - - Profiler.enter("Layout measure from tree"); - while (layoutDependencyTree.hasConnectorsToMeasure()) { - JsArrayString measureTargets = layoutDependencyTree - .getMeasureTargetsJsArray(); - int length = measureTargets.length(); - for (int i = 0; i < length; i++) { - ComponentConnector connector = (ComponentConnector) connectorMap - .getConnector(measureTargets.get(i)); - measureConnector(connector); - measureCount++; - } - for (int i = 0; i < length; i++) { - ComponentConnector connector = (ComponentConnector) connectorMap - .getConnector(measureTargets.get(i)); - layoutDependencyTree.setNeedsMeasure(connector, false); - } - } - Profiler.leave("Layout measure from tree"); - - return measureCount; - } - - /* - * Delay the overflow fix if the involved connectors might still change - */ - private boolean delayOverflowFix(ComponentConnector componentConnector) { - if (!currentDependencyTree.noMoreChangesExpected(componentConnector)) { - return true; - } - ServerConnector parent = componentConnector.getParent(); - if (parent instanceof ComponentConnector - && !currentDependencyTree - .noMoreChangesExpected((ComponentConnector) parent)) { - return true; - } - - return false; - } - - private void measureConnector(ComponentConnector connector) { - Profiler.enter("LayoutManager.measureConnector"); - Element element = connector.getWidget().getElement(); - MeasuredSize measuredSize = getMeasuredSize(element); - MeasureResult measureResult = measuredAndUpdate(element, measuredSize); - - if (measureResult.isChanged()) { - onConnectorChange(connector, measureResult.isWidthChanged(), - measureResult.isHeightChanged()); - } - Profiler.leave("LayoutManager.measureConnector"); - } - - private void onConnectorChange(ComponentConnector connector, - boolean widthChanged, boolean heightChanged) { - Profiler.enter("LayoutManager.onConnectorChange"); - Profiler.enter("LayoutManager.onConnectorChange setNeedsOverflowFix"); - setNeedsOverflowFix(connector); - Profiler.leave("LayoutManager.onConnectorChange setNeedsOverflowFix"); - Profiler.enter("LayoutManager.onConnectorChange heightChanged"); - if (heightChanged) { - currentDependencyTree.markHeightAsChanged(connector); - } - Profiler.leave("LayoutManager.onConnectorChange heightChanged"); - Profiler.enter("LayoutManager.onConnectorChange widthChanged"); - if (widthChanged) { - currentDependencyTree.markWidthAsChanged(connector); - } - Profiler.leave("LayoutManager.onConnectorChange widthChanged"); - Profiler.leave("LayoutManager.onConnectorChange"); - } - - private void setNeedsOverflowFix(ComponentConnector connector) { - // IE9 doesn't need the original fix, but for some reason it needs this - if (BrowserInfo.get().requiresOverflowAutoFix() - || BrowserInfo.get().isIE9()) { - ComponentConnector scrollingBoundary = currentDependencyTree - .getScrollingBoundary(connector); - if (scrollingBoundary != null) { - pendingOverflowFixes.add(scrollingBoundary.getConnectorId()); - } - } - } - - private void measureNonConnectors() { - Profiler.enter("LayoutManager.measureNonConenctors"); - for (Element element : measuredNonConnectorElements) { - measuredAndUpdate(element, getMeasuredSize(element, null)); - } - Profiler.leave("LayoutManager.measureNonConenctors"); - getLogger().info( - "Measured " + measuredNonConnectorElements.size() - + " non connector elements"); - } - - private MeasureResult measuredAndUpdate(Element element, - MeasuredSize measuredSize) { - MeasureResult measureResult = measuredSize.measure(element); - if (measureResult.isChanged()) { - notifyListenersAndDepdendents(element, - measureResult.isWidthChanged(), - measureResult.isHeightChanged()); - } - return measureResult; - } - - private void notifyListenersAndDepdendents(Element element, - boolean widthChanged, boolean heightChanged) { - assert widthChanged || heightChanged; - - Profiler.enter("LayoutManager.notifyListenersAndDepdendents"); - - MeasuredSize measuredSize = getMeasuredSize(element, nullSize); - JsArrayString dependents = measuredSize.getDependents(); - for (int i = 0; i < dependents.length(); i++) { - String pid = dependents.get(i); - if (pid != null) { - if (heightChanged) { - currentDependencyTree.setNeedsVerticalLayout(pid, true); - } - if (widthChanged) { - currentDependencyTree.setNeedsHorizontalLayout(pid, true); - } - } - } - if (elementResizeListeners.containsKey(element)) { - listenersToFire.add(element); - } - Profiler.leave("LayoutManager.notifyListenersAndDepdendents"); - } - - private static boolean isManagedLayout(ComponentConnector connector) { - return connector instanceof ManagedLayout; - } - - public void forceLayout() { - ConnectorMap connectorMap = connection.getConnectorMap(); - JsArrayObject componentConnectors = connectorMap - .getComponentConnectorsAsJsArray(); - int size = componentConnectors.size(); - for (int i = 0; i < size; i++) { - ComponentConnector connector = componentConnectors.get(i); - if (connector instanceof ManagedLayout) { - setNeedsLayout((ManagedLayout) connector); - } - } - setEverythingNeedsMeasure(); - layoutNow(); - } - - /** - * Marks that a ManagedLayout should be layouted in the next layout phase - * even if none of the elements managed by the layout have been resized. - *

- * This method should not be invoked during a layout phase since it only - * controls what will happen in the beginning of the next phase. If you want - * to explicitly cause some layout to be considered in an ongoing layout - * phase, you should use {@link #setNeedsMeasure(ComponentConnector)} - * instead. - * - * @param layout - * the managed layout that should be layouted - */ - public final void setNeedsLayout(ManagedLayout layout) { - setNeedsHorizontalLayout(layout); - setNeedsVerticalLayout(layout); - } - - /** - * Marks that a ManagedLayout should be layouted horizontally in the next - * layout phase even if none of the elements managed by the layout have been - * resized horizontally. - *

- * For SimpleManagedLayout which is always layouted in both directions, this - * has the same effect as {@link #setNeedsLayout(ManagedLayout)}. - *

- * This method should not be invoked during a layout phase since it only - * controls what will happen in the beginning of the next phase. If you want - * to explicitly cause some layout to be considered in an ongoing layout - * phase, you should use {@link #setNeedsMeasure(ComponentConnector)} - * instead. - * - * @param layout - * the managed layout that should be layouted - */ - public final void setNeedsHorizontalLayout(ManagedLayout layout) { - if (isLayoutRunning()) { - getLogger() - .warning( - "setNeedsHorizontalLayout should not be run while a layout phase is in progress."); - } - needsHorizontalLayout.add(layout.getConnectorId()); - } - - /** - * Marks that a ManagedLayout should be layouted vertically in the next - * layout phase even if none of the elements managed by the layout have been - * resized vertically. - *

- * For SimpleManagedLayout which is always layouted in both directions, this - * has the same effect as {@link #setNeedsLayout(ManagedLayout)}. - *

- * This method should not be invoked during a layout phase since it only - * controls what will happen in the beginning of the next phase. If you want - * to explicitly cause some layout to be considered in an ongoing layout - * phase, you should use {@link #setNeedsMeasure(ComponentConnector)} - * instead. - * - * @param layout - * the managed layout that should be layouted - */ - public final void setNeedsVerticalLayout(ManagedLayout layout) { - if (isLayoutRunning()) { - getLogger() - .warning( - "setNeedsVerticalLayout should not be run while a layout phase is in progress."); - } - needsVerticalLayout.add(layout.getConnectorId()); - } - - /** - * Gets the outer height (including margins, paddings and borders) of the - * given element, provided that it has been measured. These elements are - * guaranteed to be measured: - *

    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * -1 is returned if the element has not been measured. If 0 is returned, it - * might indicate that the element is not attached to the DOM. - *

- * The value returned by this method is always rounded up. To get the exact - * outer width, use {@link #getOuterHeightDouble(Element)} - * - * @param element - * the element to get the measured size for - * @return the measured outer height (including margins, paddings and - * borders) of the element in pixels. - */ - public final int getOuterHeight(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return (int) Math.ceil(getMeasuredSize(element, nullSize) - .getOuterHeight()); - } - - /** - * Gets the outer height (including margins, paddings and borders) of the - * given element, provided that it has been measured. These elements are - * guaranteed to be measured: - *

    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * -1 is returned if the element has not been measured. If 0 is returned, it - * might indicate that the element is not attached to the DOM. - * - * @since 7.5.1 - * @param element - * the element to get the measured size for - * @return the measured outer height (including margins, paddings and - * borders) of the element in pixels. - */ - public final double getOuterHeightDouble(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getOuterHeight(); - } - - /** - * Gets the outer width (including margins, paddings and borders) of the - * given element, provided that it has been measured. These elements are - * guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * -1 is returned if the element has not been measured. If 0 is returned, it - * might indicate that the element is not attached to the DOM. - *

- * The value returned by this method is always rounded up. To get the exact - * outer width, use {@link #getOuterWidthDouble(Element)} - * - * @since 7.5.1 - * @param element - * the element to get the measured size for - * @return the measured outer width (including margins, paddings and - * borders) of the element in pixels. - */ - public final int getOuterWidth(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return (int) Math.ceil(getMeasuredSize(element, nullSize) - .getOuterWidth()); - } - - /** - * Gets the outer width (including margins, paddings and borders) of the - * given element, provided that it has been measured. These elements are - * guaranteed to be measured: - *

    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * -1 is returned if the element has not been measured. If 0 is returned, it - * might indicate that the element is not attached to the DOM. - * - * @param element - * the element to get the measured size for - * @return the measured outer width (including margins, paddings and - * borders) of the element in pixels. - */ - public final double getOuterWidthDouble(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getOuterWidth(); - } - - /** - * Gets the inner height (excluding margins, paddings and borders) of the - * given element, provided that it has been measured. These elements are - * guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * -1 is returned if the element has not been measured. If 0 is returned, it - * might indicate that the element is not attached to the DOM. - *

- * The value returned by this method is always rounded up. To get the exact - * outer width, use {@link #getInnerHeightDouble(Element)} - * - * @param element - * the element to get the measured size for - * @return the measured inner height (excluding margins, paddings and - * borders) of the element in pixels. - */ - public final int getInnerHeight(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return (int) Math.ceil(getMeasuredSize(element, nullSize) - .getInnerHeight()); - } - - /** - * Gets the inner height (excluding margins, paddings and borders) of the - * given element, provided that it has been measured. These elements are - * guaranteed to be measured: - *

    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * -1 is returned if the element has not been measured. If 0 is returned, it - * might indicate that the element is not attached to the DOM. - * - * @since 7.5.1 - * @param element - * the element to get the measured size for - * @return the measured inner height (excluding margins, paddings and - * borders) of the element in pixels. - */ - public final double getInnerHeightDouble(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getInnerHeight(); - } - - /** - * Gets the inner width (excluding margins, paddings and borders) of the - * given element, provided that it has been measured. These elements are - * guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * -1 is returned if the element has not been measured. If 0 is returned, it - * might indicate that the element is not attached to the DOM. - *

- * The value returned by this method is always rounded up. To get the exact - * outer width, use {@link #getOuterHeightDouble(Element)} - * - * @param element - * the element to get the measured size for - * @return the measured inner width (excluding margins, paddings and - * borders) of the element in pixels. - */ - public final int getInnerWidth(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return (int) Math.ceil(getMeasuredSize(element, nullSize) - .getInnerWidth()); - } - - /** - * Gets the inner width (excluding margins, paddings and borders) of the - * given element, provided that it has been measured. These elements are - * guaranteed to be measured: - *

    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * -1 is returned if the element has not been measured. If 0 is returned, it - * might indicate that the element is not attached to the DOM. - * - * @since 7.5.1 - * @param element - * the element to get the measured size for - * @return the measured inner width (excluding margins, paddings and - * borders) of the element in pixels. - */ - public final double getInnerWidthDouble(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getInnerWidth(); - } - - /** - * Gets the border height (top border + bottom border) of the given element, - * provided that it has been measured. These elements are guaranteed to be - * measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured border height (top border + bottom border) of the - * element in pixels. - */ - public final int getBorderHeight(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getBorderHeight(); - } - - /** - * Gets the padding height (top padding + bottom padding) of the given - * element, provided that it has been measured. These elements are - * guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured padding height (top padding + bottom padding) of the - * element in pixels. - */ - public int getPaddingHeight(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getPaddingHeight(); - } - - /** - * Gets the border width (left border + right border) of the given element, - * provided that it has been measured. These elements are guaranteed to be - * measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured border width (left border + right border) of the - * element in pixels. - */ - public int getBorderWidth(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getBorderWidth(); - } - - /** - * Gets the top border of the given element, provided that it has been - * measured. These elements are guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured top border of the element in pixels. - */ - public int getBorderTop(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getBorderTop(); - } - - /** - * Gets the left border of the given element, provided that it has been - * measured. These elements are guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured left border of the element in pixels. - */ - public int getBorderLeft(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getBorderLeft(); - } - - /** - * Gets the bottom border of the given element, provided that it has been - * measured. These elements are guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured bottom border of the element in pixels. - */ - public int getBorderBottom(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getBorderBottom(); - } - - /** - * Gets the right border of the given element, provided that it has been - * measured. These elements are guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured right border of the element in pixels. - */ - public int getBorderRight(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getBorderRight(); - } - - /** - * Gets the padding width (left padding + right padding) of the given - * element, provided that it has been measured. These elements are - * guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured padding width (left padding + right padding) of the - * element in pixels. - */ - public int getPaddingWidth(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getPaddingWidth(); - } - - /** - * Gets the top padding of the given element, provided that it has been - * measured. These elements are guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured top padding of the element in pixels. - */ - public int getPaddingTop(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getPaddingTop(); - } - - /** - * Gets the left padding of the given element, provided that it has been - * measured. These elements are guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured left padding of the element in pixels. - */ - public int getPaddingLeft(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getPaddingLeft(); - } - - /** - * Gets the bottom padding of the given element, provided that it has been - * measured. These elements are guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured bottom padding of the element in pixels. - */ - public int getPaddingBottom(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getPaddingBottom(); - } - - /** - * Gets the right padding of the given element, provided that it has been - * measured. These elements are guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured right padding of the element in pixels. - */ - public int getPaddingRight(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getPaddingRight(); - } - - /** - * Gets the top margin of the given element, provided that it has been - * measured. These elements are guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured top margin of the element in pixels. - */ - public int getMarginTop(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getMarginTop(); - } - - /** - * Gets the right margin of the given element, provided that it has been - * measured. These elements are guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured right margin of the element in pixels. - */ - public int getMarginRight(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getMarginRight(); - } - - /** - * Gets the bottom margin of the given element, provided that it has been - * measured. These elements are guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured bottom margin of the element in pixels. - */ - public int getMarginBottom(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getMarginBottom(); - } - - /** - * Gets the left margin of the given element, provided that it has been - * measured. These elements are guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured size for - * @return the measured left margin of the element in pixels. - */ - public int getMarginLeft(Element element) { - assert needsMeasure(element) : "Getting measurement for element that is not measured"; - return getMeasuredSize(element, nullSize).getMarginLeft(); - } - - /** - * Gets the combined top & bottom margin of the given element, provided that - * they have been measured. These elements are guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured margin for - * @return the measured top+bottom margin of the element in pixels. - */ - public int getMarginHeight(Element element) { - return getMarginTop(element) + getMarginBottom(element); - } - - /** - * Gets the combined left & right margin of the given element, provided that - * they have been measured. These elements are guaranteed to be measured: - *
    - *
  • ManagedLayouts and their child Connectors - *
  • Elements for which there is at least one ElementResizeListener - *
  • Elements for which at least one ManagedLayout has registered a - * dependency - *
- * - * A negative number is returned if the element has not been measured. If 0 - * is returned, it might indicate that the element is not attached to the - * DOM. - * - * @param element - * the element to get the measured margin for - * @return the measured left+right margin of the element in pixels. - */ - public int getMarginWidth(Element element) { - return getMarginLeft(element) + getMarginRight(element); - } - - /** - * Registers the outer height (including margins, borders and paddings) of a - * component. This can be used as an optimization by ManagedLayouts; by - * informing the LayoutManager about what size a component will have, the - * layout propagation can continue directly without first measuring the - * potentially resized elements. - * - * @param component - * the component for which the size is reported - * @param outerHeight - * the new outer height (including margins, borders and paddings) - * of the component in pixels - */ - public void reportOuterHeight(ComponentConnector component, int outerHeight) { - Element element = component.getWidget().getElement(); - MeasuredSize measuredSize = getMeasuredSize(element); - if (isLayoutRunning()) { - boolean heightChanged = measuredSize.setOuterHeight(outerHeight); - - if (heightChanged) { - onConnectorChange(component, false, true); - notifyListenersAndDepdendents(element, false, true); - } - currentDependencyTree.setNeedsVerticalMeasure(component, false); - } else if (measuredSize.getOuterHeight() != outerHeight) { - setNeedsMeasure(component); - } - } - - /** - * Registers the height reserved for a relatively sized component. This can - * be used as an optimization by ManagedLayouts; by informing the - * LayoutManager about what size a component will have, the layout - * propagation can continue directly without first measuring the potentially - * resized elements. - * - * @param component - * the relatively sized component for which the size is reported - * @param assignedHeight - * the inner height of the relatively sized component's parent - * element in pixels - */ - public void reportHeightAssignedToRelative(ComponentConnector component, - int assignedHeight) { - assert component.isRelativeHeight(); - - float percentSize = parsePercent(component.getState().height == null ? "" - : component.getState().height); - int effectiveHeight = Math.round(assignedHeight * (percentSize / 100)); - - reportOuterHeight(component, effectiveHeight); - } - - /** - * Registers the width reserved for a relatively sized component. This can - * be used as an optimization by ManagedLayouts; by informing the - * LayoutManager about what size a component will have, the layout - * propagation can continue directly without first measuring the potentially - * resized elements. - * - * @param component - * the relatively sized component for which the size is reported - * @param assignedWidth - * the inner width of the relatively sized component's parent - * element in pixels - */ - public void reportWidthAssignedToRelative(ComponentConnector component, - int assignedWidth) { - assert component.isRelativeWidth(); - - float percentSize = parsePercent(component.getState().width == null ? "" - : component.getState().width); - int effectiveWidth = Math.round(assignedWidth * (percentSize / 100)); - - reportOuterWidth(component, effectiveWidth); - } - - private static float parsePercent(String size) { - return Float.parseFloat(size.substring(0, size.length() - 1)); - } - - /** - * Registers the outer width (including margins, borders and paddings) of a - * component. This can be used as an optimization by ManagedLayouts; by - * informing the LayoutManager about what size a component will have, the - * layout propagation can continue directly without first measuring the - * potentially resized elements. - * - * @param component - * the component for which the size is reported - * @param outerWidth - * the new outer width (including margins, borders and paddings) - * of the component in pixels - */ - public void reportOuterWidth(ComponentConnector component, int outerWidth) { - Element element = component.getWidget().getElement(); - MeasuredSize measuredSize = getMeasuredSize(element); - if (isLayoutRunning()) { - boolean widthChanged = measuredSize.setOuterWidth(outerWidth); - - if (widthChanged) { - onConnectorChange(component, true, false); - notifyListenersAndDepdendents(element, true, false); - } - currentDependencyTree.setNeedsHorizontalMeasure(component, false); - } else if (measuredSize.getOuterWidth() != outerWidth) { - setNeedsMeasure(component); - } - } - - /** - * Adds a listener that will be notified whenever the size of a specific - * element changes. Adding a listener to an element also ensures that all - * sizes for that element will be available starting from the next layout - * phase. - * - * @param element - * the element that should be checked for size changes - * @param listener - * an ElementResizeListener that will be informed whenever the - * size of the target element has changed - */ - public void addElementResizeListener(Element element, - ElementResizeListener listener) { - Collection listeners = elementResizeListeners - .get(element); - if (listeners == null) { - listeners = new HashSet(); - elementResizeListeners.put(element, listeners); - ensureMeasured(element); - } - listeners.add(listener); - } - - /** - * Removes an element resize listener from the provided element. This might - * cause this LayoutManager to stop tracking the size of the element if no - * other sources are interested in the size. - * - * @param element - * the element to which the element resize listener was - * previously added - * @param listener - * the ElementResizeListener that should no longer get informed - * about size changes to the target element. - */ - public void removeElementResizeListener(Element element, - ElementResizeListener listener) { - Collection listeners = elementResizeListeners - .get(element); - if (listeners != null) { - listeners.remove(listener); - if (listeners.isEmpty()) { - elementResizeListeners.remove(element); - stopMeasuringIfUnecessary(element); - } - } - } - - private void stopMeasuringIfUnecessary(Element element) { - if (!needsMeasure(element)) { - measuredNonConnectorElements.remove(element); - setMeasuredSize(element, null); - } - } - - /** - * Informs this LayoutManager that the size of a component might have - * changed. This method should be used whenever the size of an individual - * component might have changed from outside of Vaadin's normal update - * phase, e.g. when an icon has been loaded or when the user resizes some - * part of the UI using the mouse. - *

- * To set an entire component hierarchy to be measured, use - * {@link #setNeedsMeasureRecursively(ComponentConnector)} instead. - *

- * If there is no upcoming layout phase, a new layout phase is scheduled. - * - * @param component - * the component whose size might have changed. - */ - public void setNeedsMeasure(ComponentConnector component) { - if (isLayoutRunning()) { - currentDependencyTree.setNeedsMeasure(component, true); - } else { - needsMeasure.add(component.getConnectorId()); - layoutLater(); - } - } - - /** - * Informs this LayoutManager that some sizes in a component hierarchy might - * have changed. This method should be used whenever the size of any child - * component might have changed from outside of Vaadin's normal update - * phase, e.g. when a CSS class name related to sizing has been changed. - *

- * To set a single component to be measured, use - * {@link #setNeedsMeasure(ComponentConnector)} instead. - *

- * If there is no upcoming layout phase, a new layout phase is scheduled. - * - * @since 7.2 - * @param component - * the component at the root of the component hierarchy to - * measure - */ - public void setNeedsMeasureRecursively(ComponentConnector component) { - setNeedsMeasure(component); - - if (component instanceof HasComponentsConnector) { - HasComponentsConnector hasComponents = (HasComponentsConnector) component; - for (ComponentConnector child : hasComponents.getChildComponents()) { - setNeedsMeasureRecursively(child); - } - } - } - - public void setEverythingNeedsMeasure() { - everythingNeedsMeasure = true; - } - - /** - * Clean measured sizes which are no longer needed. Only for IE8. - */ - public void cleanMeasuredSizes() { - } - - private static Logger getLogger() { - return Logger.getLogger(LayoutManager.class.getName()); - } - - /** - * Checks if there is something waiting for a layout to take place. - * - * @since 7.5.6 - * @return true if there are connectors waiting for measurement or layout, - * false otherwise - */ - public boolean isLayoutNeeded() { - if (!needsHorizontalLayout.isEmpty() || !needsVerticalLayout.isEmpty()) { - return true; - } - if (!needsMeasure.isEmpty()) { - return true; - } - - if (everythingNeedsMeasure) { - return true; - } - - return false; - } -} diff --git a/client/src/com/vaadin/client/LayoutManagerIE8.java b/client/src/com/vaadin/client/LayoutManagerIE8.java deleted file mode 100644 index 479155d0e6..0000000000 --- a/client/src/com/vaadin/client/LayoutManagerIE8.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Node; -import com.google.gwt.user.client.ui.RootPanel; - -/** - * Alternative MeasuredSize storage for IE8. Storing any information in a DOM - * element in IE8 seems to make the browser think the element has changed in a - * way that requires a reflow. To work around that, the MeasureData is instead - * stored in Map for IE8. - * - * This implementation is injected for IE8 by a replace-with definition in the - * GWT module. - * - * @author Vaadin Ltd - * @since 7.0.0 - */ -public class LayoutManagerIE8 extends LayoutManager { - - private Map measuredSizes = new HashMap(); - - // this method is needed to test for memory leaks (see - // LayoutMemoryUsageIE8ExtensionConnector) but can be private - private int getMeasuredSizesMapSize() { - return measuredSizes.size(); - } - - @Override - protected void setMeasuredSize(Element element, MeasuredSize measuredSize) { - if (measuredSize != null) { - measuredSizes.put(element, measuredSize); - } else { - measuredSizes.remove(element); - } - // clear any values that are saved to the element - if (super.getMeasuredSize(element, null) != null) { - super.setMeasuredSize(element, null); - } - } - - @Override - protected MeasuredSize getMeasuredSize(Element element, - MeasuredSize defaultSize) { - MeasuredSize measured = measuredSizes.get(element); - if (measured != null) { - return measured; - } else { - // check if saved to the element instead - MeasuredSize measuredSize = super.getMeasuredSize(element, null); - if (measuredSize != null) { - // move the value back to the map - setMeasuredSize(element, measuredSize); - return measuredSize; - } - return defaultSize; - } - } - - @Override - public void cleanMeasuredSizes() { - Profiler.enter("LayoutManager.cleanMeasuredSizes"); - - // #12688: IE8 was leaking memory when adding&removing components. - // Uses IE specific logic to figure if an element has been removed from - // DOM or not. For removed elements the measured size is stored within - // the element in case the element gets re-attached. - Node rootNode = Document.get().getBody(); - - Iterator i = measuredSizes.keySet().iterator(); - while (i.hasNext()) { - Element e = i.next(); - if (!rootNode.isOrHasChild(e)) { - // Store in element in case is still needed. - // Not attached, so reflow isn't a problem. - super.setMeasuredSize(e, measuredSizes.get(e)); - i.remove(); - } - } - - Profiler.leave("LayoutManager.cleanMeasuredSizes"); - } - - @Override - protected void performBrowserLayoutHacks() { - Profiler.enter("LayoutManagerIE8.performBrowserLayoutHacks"); - /* - * Fixes IE8 issues where IE8 sometimes forgets to update the size of - * the containing element. To force a reflow by modifying the magical - * zoom property. - */ - WidgetUtil.forceIE8Redraw(RootPanel.get().getElement()); - Profiler.leave("LayoutManagerIE8.performBrowserLayoutHacks"); - } -} diff --git a/client/src/com/vaadin/client/LocaleNotLoadedException.java b/client/src/com/vaadin/client/LocaleNotLoadedException.java deleted file mode 100644 index 6f59e786e4..0000000000 --- a/client/src/com/vaadin/client/LocaleNotLoadedException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client; - -@SuppressWarnings("serial") -public class LocaleNotLoadedException extends Exception { - - public LocaleNotLoadedException(String locale) { - super(locale); - } -} diff --git a/client/src/com/vaadin/client/LocaleService.java b/client/src/com/vaadin/client/LocaleService.java deleted file mode 100644 index dcd1c9ea4e..0000000000 --- a/client/src/com/vaadin/client/LocaleService.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Logger; - -import com.vaadin.shared.ui.ui.UIState.LocaleData; - -/** - * Date / time etc. localisation service for all widgets. Caches all loaded - * locales as JSONObjects. - * - * @author Vaadin Ltd. - * - */ -public class LocaleService { - - private static Map cache = new HashMap(); - - private static String defaultLocale; - - public static void addLocale(LocaleData localeData) { - final String key = localeData.name; - if (cache.containsKey(key)) { - cache.remove(key); - } - getLogger().fine("Received locale data for " + localeData.name); - cache.put(key, localeData); - if (cache.size() == 1) { - setDefaultLocale(key); - } - } - - public static void setDefaultLocale(String locale) { - defaultLocale = locale; - } - - public static String getDefaultLocale() { - return defaultLocale; - } - - public static Set getAvailableLocales() { - return cache.keySet(); - } - - public static String[] getMonthNames(String locale) - throws LocaleNotLoadedException { - if (cache.containsKey(locale)) { - return cache.get(locale).monthNames; - } else { - throw new LocaleNotLoadedException(locale); - } - } - - public static String[] getShortMonthNames(String locale) - throws LocaleNotLoadedException { - if (cache.containsKey(locale)) { - return cache.get(locale).shortMonthNames; - } else { - throw new LocaleNotLoadedException(locale); - } - } - - public static String[] getDayNames(String locale) - throws LocaleNotLoadedException { - if (cache.containsKey(locale)) { - return cache.get(locale).dayNames; - } else { - throw new LocaleNotLoadedException(locale); - } - } - - public static String[] getShortDayNames(String locale) - throws LocaleNotLoadedException { - if (cache.containsKey(locale)) { - return cache.get(locale).shortDayNames; - } else { - throw new LocaleNotLoadedException(locale); - } - } - - public static int getFirstDayOfWeek(String locale) - throws LocaleNotLoadedException { - if (cache.containsKey(locale)) { - return cache.get(locale).firstDayOfWeek; - } else { - throw new LocaleNotLoadedException(locale); - } - } - - public static String getDateFormat(String locale) - throws LocaleNotLoadedException { - if (cache.containsKey(locale)) { - return cache.get(locale).dateFormat; - } else { - throw new LocaleNotLoadedException(locale); - } - } - - public static boolean isTwelveHourClock(String locale) - throws LocaleNotLoadedException { - if (cache.containsKey(locale)) { - return cache.get(locale).twelveHourClock; - } else { - throw new LocaleNotLoadedException(locale); - } - } - - public static String getClockDelimiter(String locale) - throws LocaleNotLoadedException { - if (cache.containsKey(locale)) { - return cache.get(locale).hourMinuteDelimiter; - } else { - throw new LocaleNotLoadedException(locale); - } - } - - public static String[] getAmPmStrings(String locale) - throws LocaleNotLoadedException { - if (cache.containsKey(locale)) { - return new String[] { cache.get(locale).am, cache.get(locale).pm }; - } else { - throw new LocaleNotLoadedException(locale); - } - } - - public static void addLocales(List localeDatas) { - for (LocaleData localeData : localeDatas) { - addLocale(localeData); - } - } - - private static Logger getLogger() { - return Logger.getLogger(LocaleService.class.getName()); - } -} diff --git a/client/src/com/vaadin/client/MeasuredSize.java b/client/src/com/vaadin/client/MeasuredSize.java deleted file mode 100644 index 2099008350..0000000000 --- a/client/src/com/vaadin/client/MeasuredSize.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import java.util.logging.Logger; - -import com.google.gwt.core.client.JsArrayString; -import com.google.gwt.dom.client.Element; - -public class MeasuredSize { - private final static boolean debugSizeChanges = false; - - public static class MeasureResult { - private final boolean widthChanged; - private final boolean heightChanged; - - private MeasureResult(boolean widthChanged, boolean heightChanged) { - this.widthChanged = widthChanged; - this.heightChanged = heightChanged; - } - - public boolean isHeightChanged() { - return heightChanged; - } - - public boolean isWidthChanged() { - return widthChanged; - } - - public boolean isChanged() { - return heightChanged || widthChanged; - } - } - - private double width = -1; - private double height = -1; - - private int[] paddings = new int[4]; - private int[] borders = new int[4]; - private int[] margins = new int[4]; - - private FastStringSet dependents = FastStringSet.create(); - - public double getOuterHeight() { - return height; - } - - public double getOuterWidth() { - return width; - } - - public void addDependent(String pid) { - dependents.add(pid); - } - - public void removeDependent(String pid) { - dependents.remove(pid); - } - - public boolean hasDependents() { - return !dependents.isEmpty(); - } - - public JsArrayString getDependents() { - return dependents.dump(); - } - - private static int sumWidths(int[] sizes) { - return sizes[1] + sizes[3]; - } - - private static int sumHeights(int[] sizes) { - return sizes[0] + sizes[2]; - } - - public double getInnerHeight() { - return height - sumHeights(margins) - sumHeights(borders) - - sumHeights(paddings); - } - - public double getInnerWidth() { - return width - sumWidths(margins) - sumWidths(borders) - - sumWidths(paddings); - } - - public boolean setOuterHeight(double height) { - if (this.height != height) { - this.height = height; - return true; - } else { - return false; - } - } - - public boolean setOuterWidth(double width) { - if (this.width != width) { - this.width = width; - return true; - } else { - return false; - } - } - - public int getBorderHeight() { - return sumHeights(borders); - } - - public int getBorderWidth() { - return sumWidths(borders); - } - - public int getPaddingHeight() { - return sumHeights(paddings); - } - - public int getPaddingWidth() { - return sumWidths(paddings); - } - - public int getMarginHeight() { - return sumHeights(margins); - } - - public int getMarginWidth() { - return sumWidths(margins); - } - - public int getMarginTop() { - return margins[0]; - } - - public int getMarginRight() { - return margins[1]; - } - - public int getMarginBottom() { - return margins[2]; - } - - public int getMarginLeft() { - return margins[3]; - } - - public int getBorderTop() { - return borders[0]; - } - - public int getBorderRight() { - return borders[1]; - } - - public int getBorderBottom() { - return borders[2]; - } - - public int getBorderLeft() { - return borders[3]; - } - - public int getPaddingTop() { - return paddings[0]; - } - - public int getPaddingRight() { - return paddings[1]; - } - - public int getPaddingBottom() { - return paddings[2]; - } - - public int getPaddingLeft() { - return paddings[3]; - } - - public MeasureResult measure(Element element) { - Profiler.enter("MeasuredSize.measure"); - boolean heightChanged = false; - boolean widthChanged = false; - - Profiler.enter("new ComputedStyle"); - ComputedStyle computedStyle = new ComputedStyle(element); - int[] paddings = computedStyle.getPadding(); - // Some browsers do not reflow until accessing data from the computed - // style object - Profiler.leave("new ComputedStyle"); - - Profiler.enter("Measure paddings"); - if (!heightChanged && hasHeightChanged(this.paddings, paddings)) { - debugSizeChange(element, "Height (padding)", this.paddings, - paddings); - heightChanged = true; - } - if (!widthChanged && hasWidthChanged(this.paddings, paddings)) { - debugSizeChange(element, "Width (padding)", this.paddings, paddings); - widthChanged = true; - } - this.paddings = paddings; - Profiler.leave("Measure paddings"); - - Profiler.enter("Measure margins"); - int[] margins = computedStyle.getMargin(); - if (!heightChanged && hasHeightChanged(this.margins, margins)) { - debugSizeChange(element, "Height (margins)", this.margins, margins); - heightChanged = true; - } - if (!widthChanged && hasWidthChanged(this.margins, margins)) { - debugSizeChange(element, "Width (margins)", this.margins, margins); - widthChanged = true; - } - this.margins = margins; - Profiler.leave("Measure margins"); - - Profiler.enter("Measure borders"); - int[] borders = computedStyle.getBorder(); - if (!heightChanged && hasHeightChanged(this.borders, borders)) { - debugSizeChange(element, "Height (borders)", this.borders, borders); - heightChanged = true; - } - if (!widthChanged && hasWidthChanged(this.borders, borders)) { - debugSizeChange(element, "Width (borders)", this.borders, borders); - widthChanged = true; - } - this.borders = borders; - Profiler.leave("Measure borders"); - - Profiler.enter("Measure height"); - double requiredHeight = WidgetUtil.getRequiredHeightDouble(element); - double outerHeight = requiredHeight + sumHeights(margins); - double oldHeight = height; - if (setOuterHeight(outerHeight)) { - debugSizeChange(element, "Height (outer)", oldHeight, height); - heightChanged = true; - } - Profiler.leave("Measure height"); - - Profiler.enter("Measure width"); - double requiredWidth = WidgetUtil.getRequiredWidthDouble(element); - double outerWidth = requiredWidth + sumWidths(margins); - double oldWidth = width; - if (setOuterWidth(outerWidth)) { - debugSizeChange(element, "Width (outer)", oldWidth, width); - widthChanged = true; - } - Profiler.leave("Measure width"); - - Profiler.leave("MeasuredSize.measure"); - - return new MeasureResult(widthChanged, heightChanged); - } - - private void debugSizeChange(Element element, String sizeChangeType, - int[] changedFrom, int[] changedTo) { - debugSizeChange(element, sizeChangeType, - java.util.Arrays.asList(changedFrom).toString(), - java.util.Arrays.asList(changedTo).toString()); - } - - private void debugSizeChange(Element element, String sizeChangeType, - double changedFrom, double changedTo) { - debugSizeChange(element, sizeChangeType, String.valueOf(changedFrom), - String.valueOf(changedTo)); - } - - private void debugSizeChange(Element element, String sizeChangeType, - String changedFrom, String changedTo) { - if (debugSizeChanges) { - getLogger() - .info(sizeChangeType + " has changed from " + changedFrom - + " to " + changedTo + " for " + element.toString()); - } - } - - private static boolean hasWidthChanged(int[] sizes1, int[] sizes2) { - return sizes1[1] != sizes2[1] || sizes1[3] != sizes2[3]; - } - - private static boolean hasHeightChanged(int[] sizes1, int[] sizes2) { - return sizes1[0] != sizes2[0] || sizes1[2] != sizes2[2]; - } - - private static Logger getLogger() { - return Logger.getLogger(MeasuredSize.class.getName()); - } - -} diff --git a/client/src/com/vaadin/client/MouseEventDetailsBuilder.java b/client/src/com/vaadin/client/MouseEventDetailsBuilder.java deleted file mode 100644 index 11ebe3925c..0000000000 --- a/client/src/com/vaadin/client/MouseEventDetailsBuilder.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.user.client.Event; -import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.MouseEventDetails.MouseButton; - -/** - * Helper class for constructing a MouseEventDetails object from a - * {@link NativeEvent}. - * - * @author Vaadin Ltd - * @since 7.0.0 - * - */ -public class MouseEventDetailsBuilder { - - /** - * Construct a {@link MouseEventDetails} object from the given event - * - * @param evt - * The event to use as a source for the details - * @return a MouseEventDetails containing information from the event - */ - public static MouseEventDetails buildMouseEventDetails(NativeEvent evt) { - return buildMouseEventDetails(evt, null); - } - - /** - * Construct a {@link MouseEventDetails} object from the given event - * - * @param evt - * The event to use as a source for the details - * @param relativeToElement - * The element whose position - * {@link MouseEventDetails#getRelativeX()} and - * {@link MouseEventDetails#getRelativeY()} are relative to. - * @return a MouseEventDetails containing information from the event - */ - public static MouseEventDetails buildMouseEventDetails(NativeEvent evt, - Element relativeToElement) { - MouseEventDetails mouseEventDetails = new MouseEventDetails(); - mouseEventDetails.setType(Event.getTypeInt(evt.getType())); - mouseEventDetails.setClientX(WidgetUtil.getTouchOrMouseClientX(evt)); - mouseEventDetails.setClientY(WidgetUtil.getTouchOrMouseClientY(evt)); - if (evt.getButton() == NativeEvent.BUTTON_LEFT) { - mouseEventDetails.setButton(MouseButton.LEFT); - } else if (evt.getButton() == NativeEvent.BUTTON_RIGHT) { - mouseEventDetails.setButton(MouseButton.RIGHT); - } else if (evt.getButton() == NativeEvent.BUTTON_MIDDLE) { - mouseEventDetails.setButton(MouseButton.MIDDLE); - } else { - // IE8 does not always report a button. Assume left. - mouseEventDetails.setButton(MouseButton.LEFT); - } - mouseEventDetails.setAltKey(evt.getAltKey()); - mouseEventDetails.setCtrlKey(evt.getCtrlKey()); - mouseEventDetails.setMetaKey(evt.getMetaKey()); - mouseEventDetails.setShiftKey(evt.getShiftKey()); - if (relativeToElement != null) { - mouseEventDetails.setRelativeX(getRelativeX( - mouseEventDetails.getClientX(), relativeToElement)); - mouseEventDetails.setRelativeY(getRelativeY( - mouseEventDetails.getClientY(), relativeToElement)); - } - return mouseEventDetails; - - } - - private static int getRelativeX(int clientX, Element target) { - return clientX - target.getAbsoluteLeft() + target.getScrollLeft() - + target.getOwnerDocument().getScrollLeft(); - } - - private static int getRelativeY(int clientY, Element target) { - return clientY - target.getAbsoluteTop() + target.getScrollTop() - + target.getOwnerDocument().getScrollTop(); - } - -} diff --git a/client/src/com/vaadin/client/Paintable.java b/client/src/com/vaadin/client/Paintable.java deleted file mode 100644 index 34f2d0714f..0000000000 --- a/client/src/com/vaadin/client/Paintable.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client; - -/** - * An interface used by client-side widgets or paintable parts to receive - * updates from the corresponding server-side components in the form of - * {@link UIDL}. - * - * Updates can be sent back to the server using the - * {@link ApplicationConnection#updateVariable()} methods. - */ -@Deprecated -public interface Paintable { - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client); -} diff --git a/client/src/com/vaadin/client/Profiler.java b/client/src/com/vaadin/client/Profiler.java deleted file mode 100644 index 3923b66218..0000000000 --- a/client/src/com/vaadin/client/Profiler.java +++ /dev/null @@ -1,719 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Logger; - -import com.google.gwt.core.client.Duration; -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.JsArray; - -/** - * Lightweight profiling tool that can be used to collect profiling data with - * zero overhead unless enabled. To enable profiling, add - * <set-property name="vaadin.profiler" value="true" /> to - * your .gwt.xml file. - * - * @author Vaadin Ltd - * @since 7.0.0 - */ -public class Profiler { - - private static RelativeTimeSupplier RELATIVE_TIME_SUPPLIER; - - private static final String evtGroup = "VaadinProfiler"; - - private static ProfilerResultConsumer consumer; - - /** - * Class to include using deferred binding to enable the profiling. - * - * @author Vaadin Ltd - * @since 7.0.0 - */ - public static class EnabledProfiler extends Profiler { - - @Override - protected boolean isImplEnabled() { - return true; - } - } - - /** - * Interface for getting data from the {@link Profiler}. - *

- * Warning! This interface is most likely to change in the future - * - * @since 7.1 - * @author Vaadin Ltd - */ - public interface ProfilerResultConsumer { - public void addProfilerData(Node rootNode, List totals); - - public void addBootstrapData(LinkedHashMap timings); - } - - /** - * A hierarchical representation of the time spent running a named block of - * code. - *

- * Warning! This class is most likely to change in the future and is - * therefore defined in this class in an internal package instead of - * Profiler where it might seem more logical. - */ - public static class Node { - private final String name; - private final LinkedHashMap children = new LinkedHashMap(); - private double time = 0; - private int count = 0; - private double enterTime = 0; - private double minTime = 1000000000; - private double maxTime = 0; - - /** - * Create a new node with the given name. - * - * @param name - */ - public Node(String name) { - this.name = name; - } - - /** - * Gets the name of the node - * - * @return the name of the node - */ - public String getName() { - return name; - } - - /** - * Creates a new child node or retrieves and existing child and updates - * its total time and hit count. - * - * @param name - * the name of the child - * @param timestamp - * the timestamp for when the node is entered - * @return the child node object - */ - public Node enterChild(String name, double timestamp) { - Node child = children.get(name); - if (child == null) { - child = new Node(name); - children.put(name, child); - } - child.enterTime = timestamp; - child.count++; - return child; - } - - /** - * Gets the total time spent in this node, including time spent in sub - * nodes - * - * @return the total time spent, in milliseconds - */ - public double getTimeSpent() { - return time; - } - - /** - * Gets the minimum time spent for one invocation of this node, - * including time spent in sub nodes - * - * @return the time spent for the fastest invocation, in milliseconds - */ - public double getMinTimeSpent() { - return minTime; - } - - /** - * Gets the maximum time spent for one invocation of this node, - * including time spent in sub nodes - * - * @return the time spent for the slowest invocation, in milliseconds - */ - public double getMaxTimeSpent() { - return maxTime; - } - - /** - * Gets the number of times this node has been entered - * - * @return the number of times the node has been entered - */ - public int getCount() { - return count; - } - - /** - * Gets the total time spent in this node, excluding time spent in sub - * nodes - * - * @return the total time spent, in milliseconds - */ - public double getOwnTime() { - double time = getTimeSpent(); - for (Node node : children.values()) { - time -= node.getTimeSpent(); - } - return time; - } - - /** - * Gets the child nodes of this node - * - * @return a collection of child nodes - */ - public Collection getChildren() { - return Collections.unmodifiableCollection(children.values()); - } - - private void buildRecursiveString(StringBuilder builder, String prefix) { - if (getName() != null) { - String msg = getStringRepresentation(prefix); - builder.append(msg + '\n'); - } - String childPrefix = prefix + "*"; - for (Node node : children.values()) { - node.buildRecursiveString(builder, childPrefix); - } - } - - @Override - public String toString() { - return getStringRepresentation(""); - } - - public String getStringRepresentation(String prefix) { - if (getName() == null) { - return ""; - } - String msg = prefix + " " + getName() + " in " - + roundToSignificantFigures(getTimeSpent()) + " ms."; - if (getCount() > 1) { - msg += " Invoked " - + getCount() - + " times (" - + roundToSignificantFigures(getTimeSpent() / getCount()) - + " ms per time, min " - + roundToSignificantFigures(getMinTimeSpent()) - + " ms, max " - + roundToSignificantFigures(getMaxTimeSpent()) - + " ms)."; - } - if (!children.isEmpty()) { - double ownTime = getOwnTime(); - msg += " " + roundToSignificantFigures(ownTime) - + " ms spent in own code"; - if (getCount() > 1) { - msg += " (" - + roundToSignificantFigures(ownTime / getCount()) - + " ms per time)"; - } - msg += '.'; - } - return msg; - } - - private static double roundToSignificantFigures(double num) { - // Number of significant digits - int n = 3; - if (num == 0) { - return 0; - } - - final double d = Math.ceil(Math.log10(num < 0 ? -num : num)); - final int power = n - (int) d; - - final double magnitude = Math.pow(10, power); - final long shifted = Math.round(num * magnitude); - return shifted / magnitude; - } - - public void sumUpTotals(Map totals) { - String name = getName(); - if (name != null) { - Node totalNode = totals.get(name); - if (totalNode == null) { - totalNode = new Node(name); - totals.put(name, totalNode); - } - - totalNode.time += getOwnTime(); - totalNode.count += getCount(); - totalNode.minTime = roundToSignificantFigures(Math.min( - totalNode.minTime, getMinTimeSpent())); - totalNode.maxTime = roundToSignificantFigures(Math.max( - totalNode.maxTime, getMaxTimeSpent())); - } - for (Node node : children.values()) { - node.sumUpTotals(totals); - } - } - - /** - * @param timestamp - */ - public void leave(double timestamp) { - double elapsed = (timestamp - enterTime); - time += elapsed; - enterTime = 0; - if (elapsed < minTime) { - minTime = elapsed; - } - if (elapsed > maxTime) { - maxTime = elapsed; - } - } - } - - private static final class GwtStatsEvent extends JavaScriptObject { - protected GwtStatsEvent() { - // JSO constructor - } - - private native String getEvtGroup() - /*-{ - return this.evtGroup; - }-*/; - - private native double getMillis() - /*-{ - return this.millis; - }-*/; - - private native String getSubSystem() - /*-{ - return this.subSystem; - }-*/; - - private native String getType() - /*-{ - return this.type; - }-*/; - - private native String getModuleName() - /*-{ - return this.moduleName; - }-*/; - - private native double getRelativeMillis() - /*-{ - return this.relativeMillis; - }-*/; - - private native boolean isExtendedEvent() - /*-{ - return 'relativeMillis' in this; - }-*/; - - public final String getEventName() { - String group = getEvtGroup(); - if (evtGroup.equals(group)) { - return getSubSystem(); - } else { - return group + "." + getSubSystem(); - } - } - } - - /** - * Checks whether the profiling gathering is enabled. - * - * @return true if the profiling is enabled, else - * false - */ - public static boolean isEnabled() { - // This will be fully inlined by the compiler - Profiler create = GWT.create(Profiler.class); - return create.isImplEnabled(); - } - - /** - * Enters a named block. There should always be a matching invocation of - * {@link #leave(String)} when leaving the block. Calls to this method will - * be removed by the compiler unless profiling is enabled. - * - * @param name - * the name of the entered block - */ - public static void enter(String name) { - if (isEnabled()) { - logGwtEvent(name, "begin"); - } - } - - /** - * Leaves a named block. There should always be a matching invocation of - * {@link #enter(String)} when entering the block. Calls to this method will - * be removed by the compiler unless profiling is enabled. - * - * @param name - * the name of the left block - */ - public static void leave(String name) { - if (isEnabled()) { - logGwtEvent(name, "end"); - } - } - - /** - * Returns time relative to the particular page load time. The value should - * not be used directly but rather difference between two values returned by - * this method should be used to compare measurements. - * - * @since 7.6 - */ - public static double getRelativeTimeMillis() { - return RELATIVE_TIME_SUPPLIER.getRelativeTime(); - } - - private static native final void logGwtEvent(String name, String type) - /*-{ - $wnd.__gwtStatsEvent({ - evtGroup: @com.vaadin.client.Profiler::evtGroup, - moduleName: @com.google.gwt.core.client.GWT::getModuleName()(), - millis: (new Date).getTime(), - sessionId: undefined, - subSystem: name, - type: type, - relativeMillis: @com.vaadin.client.Profiler::getRelativeTimeMillis()() - }); - }-*/; - - /** - * Resets the collected profiler data. Calls to this method will be removed - * by the compiler unless profiling is enabled. - */ - public static void reset() { - if (isEnabled()) { - /* - * Old implementations might call reset for initialization, so - * ensure it is initialized here as well. Initialization has no side - * effects if already done. - */ - initialize(); - - clearEventsList(); - } - } - - /** - * Initializes the profiler. This should be done before calling any other - * function in this class. Failing to do so might cause undesired behavior. - * This method has no side effects if the initialization has already been - * done. - *

- * Please note that this method should be called even if the profiler is not - * enabled because it will then remove a logger function that might have - * been included in the HTML page and that would leak memory unless removed. - *

- * - * @since 7.0.2 - */ - public static void initialize() { - if (hasHighPrecisionTime()) { - RELATIVE_TIME_SUPPLIER = new HighResolutionTimeSupplier(); - } else { - RELATIVE_TIME_SUPPLIER = new DefaultRelativeTimeSupplier(); - } - if (isEnabled()) { - ensureLogger(); - } else { - ensureNoLogger(); - } - } - - /** - * Outputs the gathered profiling data to the debug console. - */ - public static void logTimings() { - if (!isEnabled()) { - getLogger().warning( - "Profiler is not enabled, no data has been collected."); - return; - } - - LinkedList stack = new LinkedList(); - Node rootNode = new Node(null); - stack.add(rootNode); - JsArray gwtStatsEvents = getGwtStatsEvents(); - if (gwtStatsEvents.length() == 0) { - getLogger() - .warning( - "No profiling events recorded, this might happen if another __gwtStatsEvent handler is installed."); - return; - } - - Set extendedTimeNodes = new HashSet(); - for (int i = 0; i < gwtStatsEvents.length(); i++) { - GwtStatsEvent gwtStatsEvent = gwtStatsEvents.get(i); - String eventName = gwtStatsEvent.getEventName(); - String type = gwtStatsEvent.getType(); - boolean isExtendedEvent = gwtStatsEvent.isExtendedEvent(); - boolean isBeginEvent = "begin".equals(type); - - Node stackTop = stack.getLast(); - boolean inEvent = eventName.equals(stackTop.getName()) - && !isBeginEvent; - - if (!inEvent && stack.size() >= 2 - && eventName.equals(stack.get(stack.size() - 2).getName()) - && !isBeginEvent) { - // back out of sub event - if (extendedTimeNodes.contains(stackTop) && isExtendedEvent) { - stackTop.leave(gwtStatsEvent.getRelativeMillis()); - } else { - stackTop.leave(gwtStatsEvent.getMillis()); - } - stack.removeLast(); - stackTop = stack.getLast(); - - inEvent = true; - } - - if (type.equals("end")) { - if (!inEvent) { - getLogger().severe( - "Got end event for " + eventName - + " but is currently in " - + stackTop.getName()); - return; - } - Node previousStackTop = stack.removeLast(); - if (extendedTimeNodes.contains(previousStackTop)) { - previousStackTop.leave(gwtStatsEvent.getRelativeMillis()); - } else { - previousStackTop.leave(gwtStatsEvent.getMillis()); - } - } else { - double millis = isExtendedEvent ? gwtStatsEvent - .getRelativeMillis() : gwtStatsEvent.getMillis(); - if (!inEvent) { - stackTop = stackTop.enterChild(eventName, millis); - stack.add(stackTop); - if (isExtendedEvent) { - extendedTimeNodes.add(stackTop); - } - } - if (!isBeginEvent) { - // Create sub event - Node subNode = stackTop.enterChild(eventName + "." + type, - millis); - if (isExtendedEvent) { - extendedTimeNodes.add(subNode); - } - stack.add(subNode); - } - } - } - - if (stack.size() != 1) { - getLogger().warning( - "Not all nodes are left, the last node is " - + stack.getLast().getName()); - return; - } - - Map totals = new HashMap(); - rootNode.sumUpTotals(totals); - - ArrayList totalList = new ArrayList(totals.values()); - Collections.sort(totalList, new Comparator() { - @Override - public int compare(Node o1, Node o2) { - return (int) (o2.getTimeSpent() - o1.getTimeSpent()); - } - }); - - if (getConsumer() != null) { - getConsumer().addProfilerData(stack.getFirst(), totalList); - } - } - - /** - * Overridden in {@link EnabledProfiler} to make {@link #isEnabled()} return - * true if GWT.create returns that class. - * - * @return true if the profiling is enabled, else - * false - */ - protected boolean isImplEnabled() { - return false; - } - - /** - * Outputs the time passed since various events recored in - * performance.timing if supported by the browser. - */ - public static void logBootstrapTimings() { - if (isEnabled()) { - double now = Duration.currentTimeMillis(); - - String[] keys = new String[] { "navigationStart", - "unloadEventStart", "unloadEventEnd", "redirectStart", - "redirectEnd", "fetchStart", "domainLookupStart", - "domainLookupEnd", "connectStart", "connectEnd", - "requestStart", "responseStart", "responseEnd", - "domLoading", "domInteractive", - "domContentLoadedEventStart", "domContentLoadedEventEnd", - "domComplete", "loadEventStart", "loadEventEnd" }; - - LinkedHashMap timings = new LinkedHashMap(); - - for (String key : keys) { - double value = getPerformanceTiming(key); - if (value == 0) { - // Ignore missing value - continue; - } - timings.put(key, Double.valueOf(now - value)); - } - - if (timings.isEmpty()) { - getLogger() - .info("Bootstrap timings not supported, please ensure your browser supports performance.timing"); - return; - } - - if (getConsumer() != null) { - getConsumer().addBootstrapData(timings); - } - } - } - - private static final native double getPerformanceTiming(String name) - /*-{ - if ($wnd.performance && $wnd.performance.timing && $wnd.performance.timing[name]) { - return $wnd.performance.timing[name]; - } else { - return 0; - } - }-*/; - - private static native JsArray getGwtStatsEvents() - /*-{ - return $wnd.vaadin.gwtStatsEvents || []; - }-*/; - - /** - * Add logger if it's not already there, also initializing the event array - * if needed. - */ - private static native void ensureLogger() - /*-{ - if (typeof $wnd.__gwtStatsEvent != 'function') { - if (typeof $wnd.vaadin.gwtStatsEvents != 'object') { - $wnd.vaadin.gwtStatsEvents = []; - } - $wnd.__gwtStatsEvent = function(event) { - $wnd.vaadin.gwtStatsEvents.push(event); - return true; - } - } - }-*/; - - /** - * Remove logger function and event array if it seems like the function has - * been added by us. - */ - private static native void ensureNoLogger() - /*-{ - if (typeof $wnd.vaadin.gwtStatsEvents == 'object') { - delete $wnd.vaadin.gwtStatsEvents; - if (typeof $wnd.__gwtStatsEvent == 'function') { - $wnd.__gwtStatsEvent = function() { return true; }; - } - } - }-*/; - - private static native JsArray clearEventsList() - /*-{ - $wnd.vaadin.gwtStatsEvents = []; - }-*/; - - /** - * Sets the profiler result consumer that is used to output the profiler - * data to the user. - *

- * Warning! This is internal API and should not be used by - * applications or add-ons. - * - * @since 7.1.4 - * @param profilerResultConsumer - * the consumer that gets profiler data - */ - public static void setProfilerResultConsumer( - ProfilerResultConsumer profilerResultConsumer) { - if (consumer != null) { - throw new IllegalStateException("The consumer has already been set"); - } - consumer = profilerResultConsumer; - } - - private static ProfilerResultConsumer getConsumer() { - return consumer; - } - - private static Logger getLogger() { - return Logger.getLogger(Profiler.class.getName()); - } - - private static native boolean hasHighPrecisionTime() - /*-{ - return $wnd.performance && (typeof $wnd.performance.now == 'function'); - }-*/; - - private interface RelativeTimeSupplier { - double getRelativeTime(); - } - - private static class DefaultRelativeTimeSupplier implements - RelativeTimeSupplier { - - @Override - public native double getRelativeTime() - /*-{ - return (new Date).getTime(); - }-*/; - } - - private static class HighResolutionTimeSupplier implements - RelativeTimeSupplier { - - @Override - public native double getRelativeTime() - /*-{ - return $wnd.performance.now(); - }-*/; - } -} diff --git a/client/src/com/vaadin/client/RenderInformation.java b/client/src/com/vaadin/client/RenderInformation.java deleted file mode 100644 index 8fd3fc7e0b..0000000000 --- a/client/src/com/vaadin/client/RenderInformation.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.user.client.DOM; - -/** - * Contains size information about a rendered container and its content area. - * - * @author Artur Signell - * - */ -public class RenderInformation { - - private RenderSpace contentArea = new RenderSpace(); - private Size renderedSize = new Size(-1, -1); - - public void setContentAreaWidth(int w) { - contentArea.setWidth(w); - } - - public void setContentAreaHeight(int h) { - contentArea.setHeight(h); - } - - public RenderSpace getContentAreaSize() { - return contentArea; - - } - - public Size getRenderedSize() { - return renderedSize; - } - - /** - * Update the size of the widget. - * - * @param widget - * - * @return true if the size has changed since last update - * @deprecated As of 7.2, call and override {@link #updateSize(Element)} - * instead - */ - @Deprecated - public boolean updateSize(com.google.gwt.user.client.Element element) { - Size newSize = new Size(element.getOffsetWidth(), - element.getOffsetHeight()); - if (newSize.equals(renderedSize)) { - return false; - } else { - renderedSize = newSize; - return true; - } - } - - /** - * Update the size of the widget. - * - * @param widget - * - * @return true if the size has changed since last update - * - * @since 7.2 - */ - public boolean updateSize(Element element) { - return updateSize(DOM.asOld(element)); - } - - @Override - public String toString() { - return "RenderInformation [contentArea=" + contentArea - + ",renderedSize=" + renderedSize + "]"; - - } - - public static class FloatSize { - - private float width, height; - - public FloatSize(float width, float height) { - this.width = width; - this.height = height; - } - - public float getWidth() { - return width; - } - - public void setWidth(float width) { - this.width = width; - } - - public float getHeight() { - return height; - } - - public void setHeight(float height) { - this.height = height; - } - - } - - public static class Size { - - private int width, height; - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof Size)) { - return false; - } - Size other = (Size) obj; - return other.width == width && other.height == height; - } - - @Override - public int hashCode() { - return (width << 8) | height; - } - - public Size() { - } - - public Size(int width, int height) { - this.height = height; - this.width = width; - } - - public int getWidth() { - return width; - } - - public void setWidth(int width) { - this.width = width; - } - - public int getHeight() { - return height; - } - - public void setHeight(int height) { - this.height = height; - } - - @Override - public String toString() { - return "Size [width=" + width + ",height=" + height + "]"; - } - } - -} diff --git a/client/src/com/vaadin/client/RenderSpace.java b/client/src/com/vaadin/client/RenderSpace.java deleted file mode 100644 index dff774aa6f..0000000000 --- a/client/src/com/vaadin/client/RenderSpace.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import com.vaadin.client.RenderInformation.Size; - -/** - * Contains information about render area. - */ -public class RenderSpace extends Size { - - private int scrollBarSize = 0; - - public RenderSpace(int width, int height) { - super(width, height); - } - - public RenderSpace() { - } - - public RenderSpace(int width, int height, boolean useNativeScrollbarSize) { - super(width, height); - if (useNativeScrollbarSize) { - scrollBarSize = WidgetUtil.getNativeScrollbarSize(); - } - } - - /** - * Returns pixels available vertically for contained widget, including - * possible scrollbars. - */ - @Override - public int getHeight() { - return super.getHeight(); - } - - /** - * Returns pixels available horizontally for contained widget, including - * possible scrollbars. - */ - @Override - public int getWidth() { - return super.getWidth(); - } - - /** - * In case containing block has oveflow: auto, this method must return - * number of pixels used by scrollbar. Returning zero means either that no - * scrollbar will be visible. - */ - public int getScrollbarSize() { - return scrollBarSize; - } - -} diff --git a/client/src/com/vaadin/client/ResourceLoader.java b/client/src/com/vaadin/client/ResourceLoader.java deleted file mode 100644 index 559768d09c..0000000000 --- a/client/src/com/vaadin/client/ResourceLoader.java +++ /dev/null @@ -1,612 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import com.google.gwt.core.client.Duration; -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.RepeatingCommand; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.LinkElement; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.ObjectElement; -import com.google.gwt.dom.client.ScriptElement; -import com.google.gwt.user.client.Timer; - -/** - * ResourceLoader lets you dynamically include external scripts and styles on - * the page and lets you know when the resource has been loaded. - * - * You can also preload resources, allowing them to get cached by the browser - * without being evaluated. This enables downloading multiple resources at once - * while still controlling in which order e.g. scripts are executed. - * - * @author Vaadin Ltd - * @since 7.0.0 - */ -public class ResourceLoader { - /** - * Event fired when a resource has been loaded. - */ - public static class ResourceLoadEvent { - private final ResourceLoader loader; - private final String resourceUrl; - private final boolean preload; - - /** - * Creates a new event. - * - * @param loader - * the resource loader that has loaded the resource - * @param resourceUrl - * the url of the loaded resource - * @param preload - * true if the resource has only been preloaded, false if - * it's fully loaded - */ - public ResourceLoadEvent(ResourceLoader loader, String resourceUrl, - boolean preload) { - this.loader = loader; - this.resourceUrl = resourceUrl; - this.preload = preload; - } - - /** - * Gets the resource loader that has fired this event - * - * @return the resource loader - */ - public ResourceLoader getResourceLoader() { - return loader; - } - - /** - * Gets the absolute url of the loaded resource. - * - * @return the absolute url of the loaded resource - */ - public String getResourceUrl() { - return resourceUrl; - } - - /** - * Returns true if the resource has been preloaded, false if it's fully - * loaded - * - * @see ResourceLoader#preloadResource(String, ResourceLoadListener) - * - * @return true if the resource has been preloaded, false if it's fully - * loaded - */ - public boolean isPreload() { - return preload; - } - } - - /** - * Event listener that gets notified when a resource has been loaded - */ - public interface ResourceLoadListener { - /** - * Notifies this ResourceLoadListener that a resource has been loaded. - * Some browsers do not support any way of detecting load errors. In - * these cases, onLoad will be called regardless of the status. - * - * @see ResourceLoadEvent - * - * @param event - * a resource load event with information about the loaded - * resource - */ - public void onLoad(ResourceLoadEvent event); - - /** - * Notifies this ResourceLoadListener that a resource could not be - * loaded, e.g. because the file could not be found or because the - * server did not respond. Some browsers do not support any way of - * detecting load errors. In these cases, onLoad will be called - * regardless of the status. - * - * @see ResourceLoadEvent - * - * @param event - * a resource load event with information about the resource - * that could not be loaded. - */ - public void onError(ResourceLoadEvent event); - } - - private static final ResourceLoader INSTANCE = GWT - .create(ResourceLoader.class); - - private ApplicationConnection connection; - - private final Set loadedResources = new HashSet(); - private final Set preloadedResources = new HashSet(); - - private final Map> loadListeners = new HashMap>(); - private final Map> preloadListeners = new HashMap>(); - - private final Element head; - - /** - * Creates a new resource loader. You should generally not create you own - * resource loader, but instead use {@link ResourceLoader#get()} to get an - * instance. - */ - protected ResourceLoader() { - Document document = Document.get(); - head = document.getElementsByTagName("head").getItem(0); - - // detect already loaded scripts and stylesheets - NodeList scripts = document.getElementsByTagName("script"); - for (int i = 0; i < scripts.getLength(); i++) { - ScriptElement element = ScriptElement.as(scripts.getItem(i)); - String src = element.getSrc(); - if (src != null && src.length() != 0) { - loadedResources.add(src); - } - } - - NodeList links = document.getElementsByTagName("link"); - for (int i = 0; i < links.getLength(); i++) { - LinkElement linkElement = LinkElement.as(links.getItem(i)); - String rel = linkElement.getRel(); - String href = linkElement.getHref(); - if ("stylesheet".equalsIgnoreCase(rel) && href != null - && href.length() != 0) { - loadedResources.add(href); - } - } - } - - /** - * Returns the default ResourceLoader - * - * @return the default ResourceLoader - */ - public static ResourceLoader get() { - return INSTANCE; - } - - /** - * Load a script and notify a listener when the script is loaded. Calling - * this method when the script is currently loading or already loaded - * doesn't cause the script to be loaded again, but the listener will still - * be notified when appropriate. - * - * - * @param scriptUrl - * the url of the script to load - * @param resourceLoadListener - * the listener that will get notified when the script is loaded - */ - public void loadScript(final String scriptUrl, - final ResourceLoadListener resourceLoadListener) { - loadScript(scriptUrl, resourceLoadListener, - !supportsInOrderScriptExecution()); - } - - /** - * Load a script and notify a listener when the script is loaded. Calling - * this method when the script is currently loading or already loaded - * doesn't cause the script to be loaded again, but the listener will still - * be notified when appropriate. - * - * - * @param scriptUrl - * url of script to load - * @param resourceLoadListener - * listener to notify when script is loaded - * @param async - * What mode the script.async attribute should be set to - * @since 7.2.4 - */ - public void loadScript(final String scriptUrl, - final ResourceLoadListener resourceLoadListener, boolean async) { - final String url = WidgetUtil.getAbsoluteUrl(scriptUrl); - ResourceLoadEvent event = new ResourceLoadEvent(this, url, false); - if (loadedResources.contains(url)) { - if (resourceLoadListener != null) { - resourceLoadListener.onLoad(event); - } - return; - } - - if (preloadListeners.containsKey(url)) { - // Preload going on, continue when preloaded - preloadResource(url, new ResourceLoadListener() { - @Override - public void onLoad(ResourceLoadEvent event) { - loadScript(url, resourceLoadListener); - } - - @Override - public void onError(ResourceLoadEvent event) { - // Preload failed -> signal error to own listener - if (resourceLoadListener != null) { - resourceLoadListener.onError(event); - } - } - }); - return; - } - - if (addListener(url, resourceLoadListener, loadListeners)) { - ScriptElement scriptTag = Document.get().createScriptElement(); - scriptTag.setSrc(url); - scriptTag.setType("text/javascript"); - - scriptTag.setPropertyBoolean("async", async); - - addOnloadHandler(scriptTag, new ResourceLoadListener() { - @Override - public void onLoad(ResourceLoadEvent event) { - fireLoad(event); - } - - @Override - public void onError(ResourceLoadEvent event) { - fireError(event); - } - }, event); - head.appendChild(scriptTag); - } - } - - /** - * The current browser supports script.async='false' for maintaining - * execution order for dynamically-added scripts. - * - * @return Browser supports script.async='false' - * @since 7.2.4 - */ - public static boolean supportsInOrderScriptExecution() { - return BrowserInfo.get().isIE11() || BrowserInfo.get().isEdge(); - } - - /** - * Download a resource and notify a listener when the resource is loaded - * without attempting to interpret the resource. When a resource has been - * preloaded, it will be present in the browser's cache (provided the HTTP - * headers allow caching), making a subsequent load operation complete - * without having to wait for the resource to be downloaded again. - * - * Calling this method when the resource is currently loading, currently - * preloading, already preloaded or already loaded doesn't cause the - * resource to be preloaded again, but the listener will still be notified - * when appropriate. - * - * @param url - * the url of the resource to preload - * @param resourceLoadListener - * the listener that will get notified when the resource is - * preloaded - */ - public void preloadResource(String url, - ResourceLoadListener resourceLoadListener) { - url = WidgetUtil.getAbsoluteUrl(url); - ResourceLoadEvent event = new ResourceLoadEvent(this, url, true); - if (loadedResources.contains(url) || preloadedResources.contains(url)) { - // Already loaded or preloaded -> just fire listener - if (resourceLoadListener != null) { - resourceLoadListener.onLoad(event); - } - return; - } - - if (addListener(url, resourceLoadListener, preloadListeners) - && !loadListeners.containsKey(url)) { - // Inject loader element if this is the first time this is preloaded - // AND the resources isn't already being loaded in the normal way - - final Element element = getPreloadElement(url); - addOnloadHandler(element, new ResourceLoadListener() { - @Override - public void onLoad(ResourceLoadEvent event) { - fireLoad(event); - Document.get().getBody().removeChild(element); - } - - @Override - public void onError(ResourceLoadEvent event) { - fireError(event); - Document.get().getBody().removeChild(element); - } - }, event); - - Document.get().getBody().appendChild(element); - } - } - - private static Element getPreloadElement(String url) { - /*- - * TODO - * In Chrome, FF: - * does not fire event if resource is 404 -> eternal spinner. - * always fires onerror -> no way to know if it loaded -> eternal spinner - * ", scriptStart); - scripts += html.substring(scriptStart + 1, j) + ";"; - nextPosToCheck = endOfPrevScript = j + "".length(); - scriptStart = lc.indexOf("", startOfBody) + 1; - final int endOfBody = lc.indexOf("", startOfBody); - if (endOfBody > startOfBody) { - res = html.substring(startOfBody, endOfBody); - } else { - res = html.substring(startOfBody); - } - } - - return res; - } - - /** Update caption for given widget */ - public void updateCaption(ComponentConnector paintable) { - Widget widget = paintable.getWidget(); - if (widget.getParent() != this) { - // Widget has not been added because the location was not found - return; - } - VCaptionWrapper wrapper = childWidgetToCaptionWrapper.get(widget); - if (VCaption.isNeeded(paintable.getState())) { - if (wrapper == null) { - // Add a wrapper between the layout and the child widget - final String loc = getLocation(widget); - super.remove(widget); - wrapper = new VCaptionWrapper(paintable, client); - super.add(wrapper, locationToElement.get(loc)); - childWidgetToCaptionWrapper.put(widget, wrapper); - } - wrapper.updateCaption(); - } else { - if (wrapper != null) { - // Remove the wrapper and add the widget directly to the layout - final String loc = getLocation(widget); - super.remove(wrapper); - super.add(widget, locationToElement.get(loc)); - childWidgetToCaptionWrapper.remove(widget); - } - } - } - - /** Get the location of an widget */ - public String getLocation(Widget w) { - for (final Iterator i = locationToWidget.keySet().iterator(); i - .hasNext();) { - final String location = i.next(); - if (locationToWidget.get(location) == w) { - return location; - } - } - return null; - } - - /** Removes given widget from the layout */ - @Override - public boolean remove(Widget w) { - final String location = getLocation(w); - if (location != null) { - locationToWidget.remove(location); - } - final VCaptionWrapper cw = childWidgetToCaptionWrapper.get(w); - if (cw != null) { - childWidgetToCaptionWrapper.remove(w); - return super.remove(cw); - } else if (w != null) { - return super.remove(w); - } - return false; - } - - /** Adding widget without specifying location is not supported */ - @Override - public void add(Widget w) { - throw new UnsupportedOperationException(); - } - - /** Clear all widgets from the layout */ - @Override - public void clear() { - super.clear(); - locationToWidget.clear(); - childWidgetToCaptionWrapper.clear(); - } - - /** - * This method is published to JS side with the same name into first DOM - * node of custom layout. This way if one implements some resizeable - * containers in custom layout he/she can notify children after resize. - */ - public void notifyChildrenOfSizeChange() { - client.runDescendentsLayout(this); - } - - @Override - public void onDetach() { - super.onDetach(); - if (elementWithNativeResizeFunction != null) { - detachResizedFunction(elementWithNativeResizeFunction); - } - } - - private native void detachResizedFunction(Element element) - /*-{ - element.notifyChildrenOfSizeChange = null; - }-*/; - - private native void publishResizedFunction(Element element) - /*-{ - var self = this; - element.notifyChildrenOfSizeChange = $entry(function() { - self.@com.vaadin.client.ui.VCustomLayout::notifyChildrenOfSizeChange()(); - }); - }-*/; - - /** - * In custom layout one may want to run layout functions made with - * JavaScript. This function tests if one exists (with name "iLayoutJS" in - * layouts first DOM node) and runs et. Return value is used to determine if - * children needs to be notified of size changes. - *

- * Note! When implementing a JS layout function you most likely want to call - * notifyChildrenOfSizeChange() function on your custom layouts main - * element. That method is used to control whether child components layout - * functions are to be run. - *

- * For internal use only. May be removed or replaced in the future. - * - * @param el - * @return true if layout function exists and was run successfully, else - * false. - */ - public native boolean iLayoutJS(com.google.gwt.user.client.Element el) - /*-{ - if(el && el.iLayoutJS) { - try { - el.iLayoutJS(); - return true; - } catch (e) { - return false; - } - } else { - return false; - } - }-*/; - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (event.getTypeInt() == Event.ONLOAD) { - Util.notifyParentOfSizeChange(this, true); - event.cancelBubble(true); - } - } - -} diff --git a/client/src/com/vaadin/client/ui/VDateField.java b/client/src/com/vaadin/client/ui/VDateField.java deleted file mode 100644 index b4084847dd..0000000000 --- a/client/src/com/vaadin/client/ui/VDateField.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.Date; - -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HasEnabled; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.DateTimeService; -import com.vaadin.shared.ui.datefield.Resolution; - -public class VDateField extends FlowPanel implements Field, HasEnabled { - - public static final String CLASSNAME = "v-datefield"; - - /** For internal use only. May be removed or replaced in the future. */ - public String paintableId; - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean immediate; - - @Deprecated - public static final Resolution RESOLUTION_YEAR = Resolution.YEAR; - @Deprecated - public static final Resolution RESOLUTION_MONTH = Resolution.MONTH; - @Deprecated - public static final Resolution RESOLUTION_DAY = Resolution.DAY; - @Deprecated - public static final Resolution RESOLUTION_HOUR = Resolution.HOUR; - @Deprecated - public static final Resolution RESOLUTION_MIN = Resolution.MINUTE; - @Deprecated - public static final Resolution RESOLUTION_SEC = Resolution.SECOND; - - /** For internal use only. May be removed or replaced in the future. */ - public static String resolutionToString(Resolution res) { - if (res.getCalendarField() > Resolution.DAY.getCalendarField()) { - return "full"; - } - if (res == Resolution.DAY) { - return "day"; - } - if (res == Resolution.MONTH) { - return "month"; - } - return "year"; - } - - protected Resolution currentResolution = Resolution.YEAR; - - protected String currentLocale; - - protected boolean readonly; - - protected boolean enabled; - - /** - * The date that is selected in the date field. Null if an invalid date is - * specified. - */ - private Date date = null; - - /** For internal use only. May be removed or replaced in the future. */ - public DateTimeService dts; - - protected boolean showISOWeekNumbers = false; - - public VDateField() { - setStyleName(CLASSNAME); - dts = new DateTimeService(); - } - - /** - * We need this redundant native function because Java's Date object doesn't - * have a setMilliseconds method. - *

- * For internal use only. May be removed or replaced in the future. - */ - public static native double getTime(int y, int m, int d, int h, int mi, - int s, int ms) - /*-{ - try { - var date = new Date(2000,1,1,1); // don't use current date here - if(y && y >= 0) date.setFullYear(y); - if(m && m >= 1) date.setMonth(m-1); - if(d && d >= 0) date.setDate(d); - if(h >= 0) date.setHours(h); - if(mi >= 0) date.setMinutes(mi); - if(s >= 0) date.setSeconds(s); - if(ms >= 0) date.setMilliseconds(ms); - return date.getTime(); - } catch (e) { - // TODO print some error message on the console - //console.log(e); - return (new Date()).getTime(); - } - }-*/; - - public int getMilliseconds() { - return DateTimeService.getMilliseconds(date); - } - - public void setMilliseconds(int ms) { - DateTimeService.setMilliseconds(date, ms); - } - - public Resolution getCurrentResolution() { - return currentResolution; - } - - public void setCurrentResolution(Resolution currentResolution) { - this.currentResolution = currentResolution; - } - - public String getCurrentLocale() { - return currentLocale; - } - - public void setCurrentLocale(String currentLocale) { - this.currentLocale = currentLocale; - } - - public Date getCurrentDate() { - return date; - } - - public void setCurrentDate(Date date) { - this.date = date; - } - - public boolean isImmediate() { - return immediate; - } - - public void setImmediate(boolean immediate) { - this.immediate = immediate; - } - - public boolean isReadonly() { - return readonly; - } - - public void setReadonly(boolean readonly) { - this.readonly = readonly; - } - - @Override - public boolean isEnabled() { - return enabled; - } - - @Override - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public DateTimeService getDateTimeService() { - return dts; - } - - public String getId() { - return paintableId; - } - - public ApplicationConnection getClient() { - return client; - } - - /** - * Returns whether ISO 8601 week numbers should be shown in the date - * selector or not. ISO 8601 defines that a week always starts with a Monday - * so the week numbers are only shown if this is the case. - * - * @return true if week number should be shown, false otherwise - */ - public boolean isShowISOWeekNumbers() { - return showISOWeekNumbers; - } - - public void setShowISOWeekNumbers(boolean showISOWeekNumbers) { - this.showISOWeekNumbers = showISOWeekNumbers; - } - - /** - * Returns a copy of the current date. Modifying the returned date will not - * modify the value of this VDateField. Use {@link #setDate(Date)} to change - * the current date. - *

- * For internal use only. May be removed or replaced in the future. - * - * @return A copy of the current date - */ - public Date getDate() { - Date current = getCurrentDate(); - if (current == null) { - return null; - } else { - return (Date) getCurrentDate().clone(); - } - } - - /** - * Sets the current date for this VDateField. - * - * @param date - * The new date to use - */ - protected void setDate(Date date) { - this.date = date; - } -} diff --git a/client/src/com/vaadin/client/ui/VDateFieldCalendar.java b/client/src/com/vaadin/client/ui/VDateFieldCalendar.java deleted file mode 100644 index 759ebef102..0000000000 --- a/client/src/com/vaadin/client/ui/VDateFieldCalendar.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.Date; - -import com.google.gwt.event.dom.client.DomEvent; -import com.vaadin.client.DateTimeService; -import com.vaadin.client.ui.VCalendarPanel.FocusOutListener; -import com.vaadin.client.ui.VCalendarPanel.SubmitListener; -import com.vaadin.shared.ui.datefield.Resolution; - -/** - * A client side implementation for InlineDateField - */ -public class VDateFieldCalendar extends VDateField { - - /** For internal use only. May be removed or replaced in the future. */ - public final VCalendarPanel calendarPanel; - - public VDateFieldCalendar() { - super(); - calendarPanel = new VCalendarPanel(); - calendarPanel.setParentField(this); - add(calendarPanel); - calendarPanel.setSubmitListener(new SubmitListener() { - @Override - public void onSubmit() { - updateValueFromPanel(); - } - - @Override - public void onCancel() { - // TODO Auto-generated method stub - - } - }); - calendarPanel.setFocusOutListener(new FocusOutListener() { - @Override - public boolean onFocusOut(DomEvent event) { - updateValueFromPanel(); - return false; - } - }); - } - - /** - * TODO refactor: almost same method as in VPopupCalendar.updateValue - *

- * For internal use only. May be removed or replaced in the future. - */ - - @SuppressWarnings("deprecation") - public void updateValueFromPanel() { - - // If field is invisible at the beginning, client can still be null when - // this function is called. - if (getClient() == null) { - return; - } - - Date date2 = calendarPanel.getDate(); - Date currentDate = getCurrentDate(); - if (currentDate == null || date2.getTime() != currentDate.getTime()) { - setCurrentDate((Date) date2.clone()); - getClient().updateVariable(getId(), "year", date2.getYear() + 1900, - false); - if (getCurrentResolution().getCalendarField() > Resolution.YEAR - .getCalendarField()) { - getClient().updateVariable(getId(), "month", - date2.getMonth() + 1, false); - if (getCurrentResolution().getCalendarField() > Resolution.MONTH - .getCalendarField()) { - getClient().updateVariable(getId(), "day", date2.getDate(), - false); - if (getCurrentResolution().getCalendarField() > Resolution.DAY - .getCalendarField()) { - getClient().updateVariable(getId(), "hour", - date2.getHours(), false); - if (getCurrentResolution().getCalendarField() > Resolution.HOUR - .getCalendarField()) { - getClient().updateVariable(getId(), "min", - date2.getMinutes(), false); - if (getCurrentResolution().getCalendarField() > Resolution.MINUTE - .getCalendarField()) { - getClient().updateVariable(getId(), "sec", - date2.getSeconds(), false); - if (getCurrentResolution().getCalendarField() > Resolution.SECOND - .getCalendarField()) { - getClient().updateVariable( - getId(), - "msec", - DateTimeService - .getMilliseconds(date2), - false); - } - } - } - } - } - } - if (isImmediate()) { - getClient().sendPendingVariableChanges(); - } - } - } - - public void setTabIndex(int tabIndex) { - calendarPanel.getElement().setTabIndex(tabIndex); - } - - public int getTabIndex() { - return calendarPanel.getElement().getTabIndex(); - } -} diff --git a/client/src/com/vaadin/client/ui/VDragAndDropWrapper.java b/client/src/com/vaadin/client/ui/VDragAndDropWrapper.java deleted file mode 100644 index f3905f9e46..0000000000 --- a/client/src/com/vaadin/client/ui/VDragAndDropWrapper.java +++ /dev/null @@ -1,730 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.JsArrayString; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.event.dom.client.MouseDownEvent; -import com.google.gwt.event.dom.client.MouseDownHandler; -import com.google.gwt.event.dom.client.MouseUpEvent; -import com.google.gwt.event.dom.client.MouseUpHandler; -import com.google.gwt.event.dom.client.TouchStartEvent; -import com.google.gwt.event.dom.client.TouchStartHandler; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.xhr.client.ReadyStateChangeHandler; -import com.google.gwt.xhr.client.XMLHttpRequest; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorMap; -import com.vaadin.client.LayoutManager; -import com.vaadin.client.MouseEventDetailsBuilder; -import com.vaadin.client.Util; -import com.vaadin.client.VConsole; -import com.vaadin.client.ValueMap; -import com.vaadin.client.ui.dd.DDUtil; -import com.vaadin.client.ui.dd.VAbstractDropHandler; -import com.vaadin.client.ui.dd.VAcceptCallback; -import com.vaadin.client.ui.dd.VDragAndDropManager; -import com.vaadin.client.ui.dd.VDragEvent; -import com.vaadin.client.ui.dd.VDropHandler; -import com.vaadin.client.ui.dd.VHasDropHandler; -import com.vaadin.client.ui.dd.VHtml5DragEvent; -import com.vaadin.client.ui.dd.VHtml5File; -import com.vaadin.client.ui.dd.VTransferable; -import com.vaadin.shared.ui.dd.HorizontalDropLocation; -import com.vaadin.shared.ui.dd.VerticalDropLocation; - -/** - * - * Must have features pending: - * - * drop details: locations + sizes in document hierarchy up to wrapper - * - */ -public class VDragAndDropWrapper extends VCustomComponent implements - VHasDropHandler { - - /** - * Minimum pixel delta is used to detect click from drag. #12838 - */ - private static final int MIN_PX_DELTA = 4; - private static final String CLASSNAME = "v-ddwrapper"; - protected static final String DRAGGABLE = "draggable"; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean hasTooltip = false; - private int startX = 0; - private int startY = 0; - - public VDragAndDropWrapper() { - super(); - hookHtml5Events(getElement()); - setStyleName(CLASSNAME); - - addDomHandler(new MouseDownHandler() { - - @Override - public void onMouseDown(final MouseDownEvent event) { - if (getConnector().isEnabled() - && event.getNativeEvent().getButton() == Event.BUTTON_LEFT - && startDrag(event.getNativeEvent())) { - event.preventDefault(); // prevent text selection - startX = event.getClientX(); - startY = event.getClientY(); - } - } - }, MouseDownEvent.getType()); - - addDomHandler(new MouseUpHandler() { - - @Override - public void onMouseUp(final MouseUpEvent event) { - final int deltaX = Math.abs(event.getClientX() - startX); - final int deltaY = Math.abs(event.getClientY() - startY); - if ((deltaX + deltaY) < MIN_PX_DELTA) { - setFocusOnLastElement(event); - } - } - - private void setFocusOnLastElement(final MouseUpEvent event) { - Element el = event.getRelativeElement(); - getLastChildElement(el).focus(); - } - - private Element getLastChildElement(Element el) { - do { - if (el == null) { - break; - } - el = el.getFirstChildElement(); - } while (el.getFirstChildElement() != null); - return el; - } - - }, MouseUpEvent.getType()); - - addDomHandler(new TouchStartHandler() { - - @Override - public void onTouchStart(TouchStartEvent event) { - if (getConnector().isEnabled() - && startDrag(event.getNativeEvent())) { - /* - * Dont let eg. panel start scrolling. - */ - event.stopPropagation(); - } - } - }, TouchStartEvent.getType()); - - sinkEvents(Event.TOUCHEVENTS); - } - - /** - * Starts a drag and drop operation from mousedown or touchstart event if - * required conditions are met. - * - * @param event - * @return true if the event was handled as a drag start event - */ - private boolean startDrag(NativeEvent event) { - if (dragStartMode == WRAPPER || dragStartMode == COMPONENT - || dragStartMode == COMPONENT_OTHER) { - VTransferable transferable = new VTransferable(); - transferable.setDragSource(getConnector()); - - ComponentConnector paintable = Util.findPaintable(client, - Element.as(event.getEventTarget())); - Widget widget = paintable.getWidget(); - transferable.setData("component", paintable); - VDragEvent dragEvent = VDragAndDropManager.get().startDrag( - transferable, event, true); - - transferable.setData("mouseDown", MouseEventDetailsBuilder - .buildMouseEventDetails(event).serialize()); - - if (dragStartMode == WRAPPER) { - dragEvent.createDragImage(getElement(), true); - } else if (dragStartMode == COMPONENT_OTHER - && getDragImageWidget() != null) { - dragEvent.createDragImage(getDragImageWidget().getElement(), - true); - } else { - dragEvent.createDragImage(widget.getElement(), true); - } - return true; - } - return false; - } - - protected final static int NONE = 0; - protected final static int COMPONENT = 1; - protected final static int WRAPPER = 2; - protected final static int HTML5 = 3; - protected final static int COMPONENT_OTHER = 4; - - /** For internal use only. May be removed or replaced in the future. */ - public int dragStartMode; - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - public VAbstractDropHandler dropHandler; - - private VDragEvent vaadinDragEvent; - - int filecounter = 0; - - /** For internal use only. May be removed or replaced in the future. */ - public Map fileIdToReceiver; - - /** For internal use only. May be removed or replaced in the future. */ - public ValueMap html5DataFlavors; - - private Element dragStartElement; - - /** For internal use only. May be removed or replaced in the future. */ - public void initDragStartMode() { - Element div = getElement(); - if (dragStartMode == HTML5) { - if (dragStartElement == null) { - dragStartElement = getDragStartElement(); - dragStartElement.setPropertyBoolean(DRAGGABLE, true); - VConsole.log("draggable = " - + dragStartElement.getPropertyBoolean(DRAGGABLE)); - hookHtml5DragStart(dragStartElement); - VConsole.log("drag start listeners hooked."); - } - } else { - dragStartElement = null; - if (div.hasAttribute(DRAGGABLE)) { - div.removeAttribute(DRAGGABLE); - } - } - } - - protected com.google.gwt.user.client.Element getDragStartElement() { - return getElement(); - } - - private boolean uploading; - - private final ReadyStateChangeHandler readyStateChangeHandler = new ReadyStateChangeHandler() { - - @Override - public void onReadyStateChange(XMLHttpRequest xhr) { - if (xhr.getReadyState() == XMLHttpRequest.DONE) { - // visit server for possible - // variable changes - client.sendPendingVariableChanges(); - uploading = false; - startNextUpload(); - xhr.clearOnReadyStateChange(); - } - } - }; - private Timer dragleavetimer; - - /** For internal use only. May be removed or replaced in the future. */ - public void startNextUpload() { - Scheduler.get().scheduleDeferred(new Command() { - - @Override - public void execute() { - if (!uploading) { - if (fileIds.size() > 0) { - - uploading = true; - final Integer fileId = fileIds.remove(0); - VHtml5File file = files.remove(0); - final String receiverUrl = client - .translateVaadinUri(fileIdToReceiver - .remove(fileId.toString())); - ExtendedXHR extendedXHR = (ExtendedXHR) ExtendedXHR - .create(); - extendedXHR - .setOnReadyStateChange(readyStateChangeHandler); - extendedXHR.open("POST", receiverUrl); - extendedXHR.postFile(file); - } - } - - } - }); - - } - - public boolean html5DragStart(VHtml5DragEvent event) { - if (dragStartMode == HTML5) { - /* - * Populate html5 payload with dataflavors from the serverside - */ - JsArrayString flavors = html5DataFlavors.getKeyArray(); - for (int i = 0; i < flavors.length(); i++) { - String flavor = flavors.get(i); - event.setHtml5DataFlavor(flavor, - html5DataFlavors.getString(flavor)); - } - event.setEffectAllowed("copy"); - return true; - } - return false; - } - - public boolean html5DragEnter(VHtml5DragEvent event) { - if (dropHandler == null) { - return true; - } - try { - if (dragleavetimer != null) { - // returned quickly back to wrapper - dragleavetimer.cancel(); - dragleavetimer = null; - } - if (VDragAndDropManager.get().getCurrentDropHandler() != getDropHandler()) { - VTransferable transferable = new VTransferable(); - transferable.setDragSource(getConnector()); - - vaadinDragEvent = VDragAndDropManager.get().startDrag( - transferable, event, false); - VDragAndDropManager.get().setCurrentDropHandler( - getDropHandler()); - } - try { - event.preventDefault(); - event.stopPropagation(); - } catch (Exception e) { - // VConsole.log("IE9 fails"); - } - return false; - } catch (Exception e) { - GWT.getUncaughtExceptionHandler().onUncaughtException(e); - return true; - } - } - - public boolean html5DragLeave(VHtml5DragEvent event) { - if (dropHandler == null) { - return true; - } - - try { - dragleavetimer = new Timer() { - - @Override - public void run() { - // Yes, dragleave happens before drop. Makes no sense to me. - // IMO shouldn't fire leave at all if drop happens (I guess - // this - // is what IE does). - // In Vaadin we fire it only if drop did not happen. - if (vaadinDragEvent != null - && VDragAndDropManager.get() - .getCurrentDropHandler() == getDropHandler()) { - VDragAndDropManager.get().interruptDrag(); - } - } - }; - dragleavetimer.schedule(350); - try { - event.preventDefault(); - event.stopPropagation(); - } catch (Exception e) { - // VConsole.log("IE9 fails"); - } - return false; - } catch (Exception e) { - GWT.getUncaughtExceptionHandler().onUncaughtException(e); - return true; - } - } - - public boolean html5DragOver(VHtml5DragEvent event) { - if (dropHandler == null) { - return true; - } - - if (dragleavetimer != null) { - // returned quickly back to wrapper - dragleavetimer.cancel(); - dragleavetimer = null; - } - - vaadinDragEvent.setCurrentGwtEvent(event); - getDropHandler().dragOver(vaadinDragEvent); - - try { - String s = event.getEffectAllowed(); - if ("all".equals(s) || s.contains("opy")) { - event.setDropEffect("copy"); - } else { - event.setDropEffect(s); - } - } catch (Exception e) { - // IE10 throws exception here in getEffectAllowed, ignore it, let - // drop effect be whatever it is - } - - try { - event.preventDefault(); - event.stopPropagation(); - } catch (Exception e) { - // VConsole.log("IE9 fails"); - } - return false; - } - - public boolean html5DragDrop(VHtml5DragEvent event) { - if (dropHandler == null || !currentlyValid) { - return true; - } - try { - - VTransferable transferable = vaadinDragEvent.getTransferable(); - - JsArrayString types = event.getTypes(); - for (int i = 0; i < types.length(); i++) { - String type = types.get(i); - if (isAcceptedType(type)) { - String data = event.getDataAsText(type); - if (data != null) { - transferable.setData(type, data); - } - } - } - - int fileCount = event.getFileCount(); - if (fileCount > 0) { - transferable.setData("filecount", fileCount); - for (int i = 0; i < fileCount; i++) { - final int fileId = filecounter++; - final VHtml5File file = event.getFile(i); - VConsole.log("Preparing to upload file " + file.getName() - + " with id " + fileId); - transferable.setData("fi" + i, "" + fileId); - transferable.setData("fn" + i, file.getName()); - transferable.setData("ft" + i, file.getType()); - transferable.setData("fs" + i, file.getSize()); - queueFilePost(fileId, file); - } - - } - - VDragAndDropManager.get().endDrag(); - vaadinDragEvent = null; - try { - event.preventDefault(); - event.stopPropagation(); - } catch (Exception e) { - // VConsole.log("IE9 fails"); - } - return false; - } catch (Exception e) { - GWT.getUncaughtExceptionHandler().onUncaughtException(e); - return true; - } - - } - - protected String[] acceptedTypes = new String[] { "Text", "Url", - "text/html", "text/plain", "text/rtf" }; - - private boolean isAcceptedType(String type) { - for (String t : acceptedTypes) { - if (t.equals(type)) { - return true; - } - } - return false; - } - - static class ExtendedXHR extends XMLHttpRequest { - - protected ExtendedXHR() { - } - - public final native void postFile(VHtml5File file) - /*-{ - - this.setRequestHeader('Content-Type', 'multipart/form-data'); - // Seems like IE10 will loose the file if we don't keep a reference to it... - this.fileBeingUploaded = file; - - this.send(file); - }-*/; - - } - - /** For internal use only. May be removed or replaced in the future. */ - public List fileIds = new ArrayList(); - - /** For internal use only. May be removed or replaced in the future. */ - public List files = new ArrayList(); - - private void queueFilePost(final int fileId, final VHtml5File file) { - fileIds.add(fileId); - files.add(file); - } - - @Override - public VDropHandler getDropHandler() { - return dropHandler; - } - - protected VerticalDropLocation verticalDropLocation; - protected HorizontalDropLocation horizontalDropLocation; - private VerticalDropLocation emphasizedVDrop; - private HorizontalDropLocation emphasizedHDrop; - - /** - * Flag used by html5 dd - */ - private boolean currentlyValid; - private Widget dragImageWidget; - - private static final String OVER_STYLE = "v-ddwrapper-over"; - - public class CustomDropHandler extends VAbstractDropHandler { - - @Override - public void dragEnter(VDragEvent drag) { - if (!getConnector().isEnabled()) { - return; - } - updateDropDetails(drag); - currentlyValid = false; - super.dragEnter(drag); - } - - @Override - public void dragLeave(VDragEvent drag) { - deEmphasis(true); - dragleavetimer = null; - } - - @Override - public void dragOver(final VDragEvent drag) { - if (!getConnector().isEnabled()) { - return; - } - boolean detailsChanged = updateDropDetails(drag); - if (detailsChanged) { - currentlyValid = false; - validate(new VAcceptCallback() { - - @Override - public void accepted(VDragEvent event) { - dragAccepted(drag); - } - }, drag); - } - } - - @Override - public boolean drop(VDragEvent drag) { - if (!getConnector().isEnabled()) { - return false; - } - deEmphasis(true); - - Map dd = drag.getDropDetails(); - - // this is absolute layout based, and we may want to set - // component - // relatively to where the drag ended. - // need to add current location of the drop area - - int absoluteLeft = getAbsoluteLeft(); - int absoluteTop = getAbsoluteTop(); - - dd.put("absoluteLeft", absoluteLeft); - dd.put("absoluteTop", absoluteTop); - - if (verticalDropLocation != null) { - dd.put("verticalLocation", verticalDropLocation.toString()); - dd.put("horizontalLocation", horizontalDropLocation.toString()); - } - - return super.drop(drag); - } - - @Override - protected void dragAccepted(VDragEvent drag) { - if (!getConnector().isEnabled()) { - return; - } - currentlyValid = true; - emphasis(drag); - } - - @Override - public ComponentConnector getConnector() { - return VDragAndDropWrapper.this.getConnector(); - } - - @Override - public ApplicationConnection getApplicationConnection() { - return client; - } - - } - - public ComponentConnector getConnector() { - return ConnectorMap.get(client).getConnector(this); - } - - /** - * @deprecated As of 7.2, call or override - * {@link #hookHtml5DragStart(Element)} instead - */ - @Deprecated - protected native void hookHtml5DragStart( - com.google.gwt.user.client.Element el) - /*-{ - var me = this; - el.addEventListener("dragstart", $entry(function(ev) { - return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); - }), false); - }-*/; - - /** - * @since 7.2 - */ - protected void hookHtml5DragStart(Element el) { - hookHtml5DragStart(DOM.asOld(el)); - } - - /** - * Prototype code, memory leak risk. - * - * @param el - * @deprecated As of 7.2, call or override {@link #hookHtml5Events(Element)} - * instead - */ - @Deprecated - protected native void hookHtml5Events(com.google.gwt.user.client.Element el) - /*-{ - var me = this; - - el.addEventListener("dragenter", $entry(function(ev) { - return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); - }), false); - - el.addEventListener("dragleave", $entry(function(ev) { - return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); - }), false); - - el.addEventListener("dragover", $entry(function(ev) { - return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); - }), false); - - el.addEventListener("drop", $entry(function(ev) { - return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); - }), false); - }-*/; - - /** - * Prototype code, memory leak risk. - * - * @param el - * - * @since 7.2 - */ - protected void hookHtml5Events(Element el) { - hookHtml5Events(DOM.asOld(el)); - } - - public boolean updateDropDetails(VDragEvent drag) { - VerticalDropLocation oldVL = verticalDropLocation; - verticalDropLocation = DDUtil.getVerticalDropLocation(getElement(), - drag.getCurrentGwtEvent(), 0.2); - drag.getDropDetails().put("verticalLocation", - verticalDropLocation.toString()); - HorizontalDropLocation oldHL = horizontalDropLocation; - horizontalDropLocation = DDUtil.getHorizontalDropLocation(getElement(), - drag.getCurrentGwtEvent(), 0.2); - drag.getDropDetails().put("horizontalLocation", - horizontalDropLocation.toString()); - if (oldHL != horizontalDropLocation || oldVL != verticalDropLocation) { - return true; - } else { - return false; - } - } - - protected void deEmphasis(boolean doLayout) { - if (emphasizedVDrop != null) { - VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, false); - VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-" - + emphasizedVDrop.toString().toLowerCase(), false); - VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-" - + emphasizedHDrop.toString().toLowerCase(), false); - } - if (doLayout) { - notifySizePotentiallyChanged(); - } - } - - private void notifySizePotentiallyChanged() { - LayoutManager.get(client).setNeedsMeasure(getConnector()); - } - - protected void emphasis(VDragEvent drag) { - deEmphasis(false); - VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, true); - VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-" - + verticalDropLocation.toString().toLowerCase(), true); - VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-" - + horizontalDropLocation.toString().toLowerCase(), true); - emphasizedVDrop = verticalDropLocation; - emphasizedHDrop = horizontalDropLocation; - - // TODO build (to be an example) an emphasis mode where drag image - // is fitted before or after the content - notifySizePotentiallyChanged(); - } - - /** - * Set the widget that will be used as the drag image when using - * DragStartMode {@link COMPONENT_OTHER} . - * - * @param widget - */ - public void setDragAndDropWidget(Widget widget) { - dragImageWidget = widget; - } - - /** - * @return the widget used as drag image. Returns null if no - * widget is set. - */ - public Widget getDragImageWidget() { - return dragImageWidget; - } - -} diff --git a/client/src/com/vaadin/client/ui/VDragAndDropWrapperIE.java b/client/src/com/vaadin/client/ui/VDragAndDropWrapperIE.java deleted file mode 100644 index b32b36d13b..0000000000 --- a/client/src/com/vaadin/client/ui/VDragAndDropWrapperIE.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.dom.client.AnchorElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.user.client.DOM; -import com.vaadin.client.VConsole; - -public class VDragAndDropWrapperIE extends VDragAndDropWrapper { - private AnchorElement anchor = null; - - @Override - protected com.google.gwt.user.client.Element getDragStartElement() { - VConsole.log("IE get drag start element..."); - Element div = getElement(); - if (dragStartMode == HTML5) { - if (anchor == null) { - anchor = Document.get().createAnchorElement(); - anchor.setHref("#"); - anchor.setClassName("drag-start"); - div.appendChild(anchor); - } - VConsole.log("IE get drag start element..."); - return anchor.cast(); - } else { - if (anchor != null) { - div.removeChild(anchor); - anchor = null; - } - return DOM.asOld(div); - } - } - - @Deprecated - @Override - protected native void hookHtml5DragStart( - com.google.gwt.user.client.Element el) - /*-{ - var me = this; - - el.attachEvent("ondragstart", $entry(function(ev) { - return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); - })); - }-*/; - - @Override - protected void hookHtml5DragStart(Element el) { - hookHtml5DragStart(DOM.asOld(el)); - } - - @Deprecated - @Override - protected native void hookHtml5Events(com.google.gwt.user.client.Element el) - /*-{ - var me = this; - - el.attachEvent("ondragenter", $entry(function(ev) { - return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); - })); - - el.attachEvent("ondragleave", $entry(function(ev) { - return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); - })); - - el.attachEvent("ondragover", $entry(function(ev) { - return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); - })); - - el.attachEvent("ondrop", $entry(function(ev) { - return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); - })); - }-*/; - - @Override - protected void hookHtml5Events(Element el) { - hookHtml5Events(DOM.asOld(el)); - } - -} diff --git a/client/src/com/vaadin/client/ui/VEmbedded.java b/client/src/com/vaadin/client/ui/VEmbedded.java deleted file mode 100644 index f3970f9ac7..0000000000 --- a/client/src/com/vaadin/client/ui/VEmbedded.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.HTML; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorMap; -import com.vaadin.client.UIDL; -import com.vaadin.client.Util; -import com.vaadin.client.VConsole; -import com.vaadin.client.WidgetUtil; -import com.vaadin.shared.ui.embedded.EmbeddedConstants; - -public class VEmbedded extends HTML { - public static String CLASSNAME = "v-embedded"; - - /** For internal use only. May be removed or replaced in the future. */ - public Element browserElement; - - /** For internal use only. May be removed or replaced in the future. */ - public String type; - - /** For internal use only. May be removed or replaced in the future. */ - public String mimetype; - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - public VEmbedded() { - setStyleName(CLASSNAME); - } - - /** - * Creates the Object and Embed tags for the Flash plugin so it works - * cross-browser. - *

- * For internal use only. May be removed or replaced in the future. - * - * @param uidl - * The UIDL - * @return Tags concatenated into a string - */ - public String createFlashEmbed(UIDL uidl) { - /* - * To ensure cross-browser compatibility we are using the twice-cooked - * method to embed flash i.e. we add a OBJECT tag for IE ActiveX and - * inside it a EMBED for all other browsers. - */ - - StringBuilder html = new StringBuilder(); - - // Start the object tag - html.append(""); - - // Ensure we have an movie parameter - Map parameters = getParameters(uidl); - if (parameters.get("movie") == null) { - parameters.put("movie", getSrc(uidl, client)); - } - - // Add parameters to OBJECT - for (String name : parameters.keySet()) { - html.append(""); - } - - // Build inner EMBED tag - html.append(""); - - if (uidl.hasAttribute(EmbeddedConstants.ALTERNATE_TEXT)) { - html.append(uidl - .getStringAttribute(EmbeddedConstants.ALTERNATE_TEXT)); - } - - // End object tag - html.append(""); - - return html.toString(); - } - - /** - * Returns a map (name -> value) of all parameters in the UIDL. - *

- * For internal use only. May be removed or replaced in the future. - * - * @param uidl - * @return - */ - public static Map getParameters(UIDL uidl) { - Map parameters = new HashMap(); - - Iterator childIterator = uidl.getChildIterator(); - while (childIterator.hasNext()) { - - Object child = childIterator.next(); - if (child instanceof UIDL) { - - UIDL childUIDL = (UIDL) child; - if (childUIDL.getTag().equals("embeddedparam")) { - String name = childUIDL.getStringAttribute("name"); - String value = childUIDL.getStringAttribute("value"); - parameters.put(name, value); - } - } - - } - - return parameters; - } - - /** - * Helper to return translated src-attribute from embedded's UIDL - *

- * For internal use only. May be removed or replaced in the future. - * - * @param uidl - * @param client - * @return - */ - public String getSrc(UIDL uidl, ApplicationConnection client) { - String url = client.translateVaadinUri(uidl.getStringAttribute("src")); - if (url == null) { - return ""; - } - return url; - } - - @Override - protected void onDetach() { - if (BrowserInfo.get().isIE()) { - // Force browser to fire unload event when component is detached - // from the view (IE doesn't do this automatically) - if (browserElement != null) { - /* - * src was previously set to javascript:false, but this was not - * enough to overcome a bug when detaching an iframe with a pdf - * loaded in IE9. about:blank seems to cause the adobe reader - * plugin to unload properly before the iframe is removed. See - * #7855 - */ - DOM.setElementAttribute(browserElement, "src", "about:blank"); - } - } - super.onDetach(); - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (DOM.eventGetType(event) == Event.ONLOAD) { - VConsole.log("Embeddable onload"); - Util.notifyParentOfSizeChange(this, true); - } - } - -} diff --git a/client/src/com/vaadin/client/ui/VFilterSelect.java b/client/src/com/vaadin/client/ui/VFilterSelect.java deleted file mode 100644 index 9459cc14a6..0000000000 --- a/client/src/com/vaadin/client/ui/VFilterSelect.java +++ /dev/null @@ -1,2351 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import com.google.gwt.aria.client.Roles; -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; -import com.google.gwt.event.dom.client.LoadEvent; -import com.google.gwt.event.dom.client.LoadHandler; -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.i18n.client.HasDirection.Direction; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; -import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; -import com.google.gwt.user.client.ui.TextBox; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ComputedStyle; -import com.vaadin.client.ConnectorMap; -import com.vaadin.client.DeferredWorker; -import com.vaadin.client.Focusable; -import com.vaadin.client.UIDL; -import com.vaadin.client.VConsole; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.ui.aria.AriaHelper; -import com.vaadin.client.ui.aria.HandlesAriaCaption; -import com.vaadin.client.ui.aria.HandlesAriaInvalid; -import com.vaadin.client.ui.aria.HandlesAriaRequired; -import com.vaadin.client.ui.menubar.MenuBar; -import com.vaadin.client.ui.menubar.MenuItem; -import com.vaadin.shared.AbstractComponentState; -import com.vaadin.shared.EventId; -import com.vaadin.shared.ui.ComponentStateUtil; -import com.vaadin.shared.ui.combobox.FilteringMode; -import com.vaadin.shared.util.SharedUtil; - -/** - * Client side implementation of the Select component. - * - * TODO needs major refactoring (to be extensible etc) - */ -@SuppressWarnings("deprecation") -public class VFilterSelect extends Composite implements Field, KeyDownHandler, - KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable, - SubPartAware, HandlesAriaCaption, HandlesAriaInvalid, - HandlesAriaRequired, DeferredWorker { - - /** - * Represents a suggestion in the suggestion popup box - */ - public class FilterSelectSuggestion implements Suggestion, Command { - - private final String key; - private final String caption; - private String untranslatedIconUri; - private String style; - - /** - * Constructor - * - * @param uidl - * The UIDL recieved from the server - */ - public FilterSelectSuggestion(UIDL uidl) { - key = uidl.getStringAttribute("key"); - caption = uidl.getStringAttribute("caption"); - style = uidl.getStringAttribute("style"); - - if (uidl.hasAttribute("icon")) { - untranslatedIconUri = uidl.getStringAttribute("icon"); - } - } - - /** - * Gets the visible row in the popup as a HTML string. The string - * contains an image tag with the rows icon (if an icon has been - * specified) and the caption of the item - */ - - @Override - public String getDisplayString() { - final StringBuffer sb = new StringBuffer(); - final Icon icon = client.getIcon(client - .translateVaadinUri(untranslatedIconUri)); - if (icon != null) { - sb.append(icon.getElement().getString()); - } - String content; - if ("".equals(caption)) { - // Ensure that empty options use the same height as other - // options and are not collapsed (#7506) - content = " "; - } else { - content = WidgetUtil.escapeHTML(caption); - } - sb.append("" + content + ""); - return sb.toString(); - } - - /** - * Get a string that represents this item. This is used in the text box. - */ - - @Override - public String getReplacementString() { - return caption; - } - - /** - * Get the option key which represents the item on the server side. - * - * @return The key of the item - */ - public String getOptionKey() { - return key; - } - - /** - * Get the URI of the icon. Used when constructing the displayed option. - * - * @return - */ - public String getIconUri() { - return client.translateVaadinUri(untranslatedIconUri); - } - - /** - * Gets the style set for this suggestion item. Styles are typically set - * by a server-side {@link com.vaadin.ui.ComboBox.ItemStyleGenerator}. - * The returned style is prefixed by v-filterselect-item-. - * - * @since 7.5.6 - * @return the style name to use, or null to not apply any - * custom style. - */ - public String getStyle() { - return style; - } - - /** - * Executes a selection of this item. - */ - - @Override - public void execute() { - onSuggestionSelected(this); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof FilterSelectSuggestion)) { - return false; - } - FilterSelectSuggestion other = (FilterSelectSuggestion) obj; - if ((key == null && other.key != null) - || (key != null && !key.equals(other.key))) { - return false; - } - if ((caption == null && other.caption != null) - || (caption != null && !caption.equals(other.caption))) { - return false; - } - if (!SharedUtil.equals(untranslatedIconUri, - other.untranslatedIconUri)) { - return false; - } - if (!SharedUtil.equals(style, other.style)) { - return false; - } - return true; - } - } - - /** An inner class that handles all logic related to mouse wheel. */ - private class MouseWheeler extends JsniMousewheelHandler { - - public MouseWheeler() { - super(VFilterSelect.this); - } - - @Override - protected native JavaScriptObject createMousewheelListenerFunction( - Widget widget) - /*-{ - return $entry(function(e) { - var deltaX = e.deltaX ? e.deltaX : -0.5*e.wheelDeltaX; - var deltaY = e.deltaY ? e.deltaY : -0.5*e.wheelDeltaY; - - // IE8 has only delta y - if (isNaN(deltaY)) { - deltaY = -0.5*e.wheelDelta; - } - - @com.vaadin.client.ui.VFilterSelect.JsniUtil::moveScrollFromEvent(*)(widget, deltaX, deltaY, e); - }); - }-*/; - - } - - /** - * A utility class that contains utility methods that are usually called - * from JSNI. - *

- * The methods are moved in this class to minimize the amount of JSNI code - * as much as feasible. - */ - static class JsniUtil { - public static void moveScrollFromEvent(final Widget widget, - final double deltaX, final double deltaY, - final NativeEvent event) { - - if (!Double.isNaN(deltaY)) { - ((VFilterSelect) widget).suggestionPopup.scroll(deltaY); - } - } - } - - /** - * Represents the popup box with the selection options. Wraps a suggestion - * menu. - */ - public class SuggestionPopup extends VOverlay implements PositionCallback, - CloseHandler { - - private static final int Z_INDEX = 30000; - - /** For internal use only. May be removed or replaced in the future. */ - public final SuggestionMenu menu; - - private final Element up = DOM.createDiv(); - private final Element down = DOM.createDiv(); - private final Element status = DOM.createDiv(); - - private boolean isPagingEnabled = true; - - private long lastAutoClosed; - - private int popupOuterPadding = -1; - - private int topPosition; - - private final MouseWheeler mouseWheeler = new MouseWheeler(); - - /** - * Default constructor - */ - SuggestionPopup() { - super(true, false, true); - debug("VFS.SP: constructor()"); - setOwner(VFilterSelect.this); - menu = new SuggestionMenu(); - setWidget(menu); - - getElement().getStyle().setZIndex(Z_INDEX); - - final Element root = getContainerElement(); - - up.setInnerHTML("Prev"); - DOM.sinkEvents(up, Event.ONCLICK); - - down.setInnerHTML("Next"); - DOM.sinkEvents(down, Event.ONCLICK); - - root.insertFirst(up); - root.appendChild(down); - root.appendChild(status); - - DOM.sinkEvents(root, Event.ONMOUSEDOWN | Event.ONMOUSEWHEEL); - addCloseHandler(this); - - Roles.getListRole().set(getElement()); - - setPreviewingAllNativeEvents(true); - } - - @Override - protected void onLoad() { - super.onLoad(); - mouseWheeler.attachMousewheelListener(getElement()); - } - - @Override - protected void onUnload() { - mouseWheeler.detachMousewheelListener(getElement()); - super.onUnload(); - } - - /** - * Shows the popup where the user can see the filtered options - * - * @param currentSuggestions - * The filtered suggestions - * @param currentPage - * The current page number - * @param totalSuggestions - * The total amount of suggestions - */ - public void showSuggestions( - final Collection currentSuggestions, - final int currentPage, final int totalSuggestions) { - - debug("VFS.SP: showSuggestions(" + currentSuggestions + ", " - + currentPage + ", " + totalSuggestions + ")"); - - /* - * We need to defer the opening of the popup so that the parent DOM - * has stabilized so we can calculate an absolute top and left - * correctly. This issue manifests when a Combobox is placed in - * another popupView which also needs to calculate the absoluteTop() - * to position itself. #9768 - * - * After deferring the showSuggestions method, a problem with - * navigating in the combo box occurs. Because of that the method - * navigateItemAfterPageChange in ComboBoxConnector class, which - * navigates to the exact item after page was changed also was - * marked as deferred. #11333 - */ - final SuggestionPopup popup = this; - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - @Override - public void execute() { - // Add TT anchor point - getElement().setId("VAADIN_COMBOBOX_OPTIONLIST"); - - menu.setSuggestions(currentSuggestions); - final int x = VFilterSelect.this.getAbsoluteLeft(); - - topPosition = tb.getAbsoluteTop(); - topPosition += tb.getOffsetHeight(); - - setPopupPosition(x, topPosition); - - int nullOffset = (nullSelectionAllowed - && "".equals(lastFilter) ? 1 : 0); - boolean firstPage = (currentPage == 0); - final int first = currentPage * pageLength + 1 - - (firstPage ? 0 : nullOffset); - final int last = first - + currentSuggestions.size() - - 1 - - (firstPage && "".equals(lastFilter) ? nullOffset - : 0); - final int matches = totalSuggestions - nullOffset; - if (last > 0) { - // nullsel not counted, as requested by user - status.setInnerText((matches == 0 ? 0 : first) + "-" - + last + "/" + matches); - } else { - status.setInnerText(""); - } - // We don't need to show arrows or statusbar if there is - // only one page - if (totalSuggestions <= pageLength || pageLength == 0) { - setPagingEnabled(false); - } else { - setPagingEnabled(true); - } - setPrevButtonActive(first > 1); - setNextButtonActive(last < matches); - - // clear previously fixed width - menu.setWidth(""); - menu.getElement().getFirstChildElement().getStyle() - .clearWidth(); - - setPopupPositionAndShow(popup); - // Fix for #14173 - // IE9 and IE10 have a bug, when resize an a element with - // box-shadow. - // IE9 and IE10 need explicit update to remove extra - // box-shadows - if (BrowserInfo.get().isIE9() || BrowserInfo.get().isIE10()) { - forceReflow(); - } - } - }); - } - - /** - * Should the next page button be visible to the user? - * - * @param active - */ - private void setNextButtonActive(boolean active) { - if (enableDebug) { - debug("VFS.SP: setNextButtonActive(" + active + ")"); - } - if (active) { - DOM.sinkEvents(down, Event.ONCLICK); - down.setClassName(VFilterSelect.this.getStylePrimaryName() - + "-nextpage"); - } else { - DOM.sinkEvents(down, 0); - down.setClassName(VFilterSelect.this.getStylePrimaryName() - + "-nextpage-off"); - } - } - - /** - * Should the previous page button be visible to the user - * - * @param active - */ - private void setPrevButtonActive(boolean active) { - if (enableDebug) { - debug("VFS.SP: setPrevButtonActive(" + active + ")"); - } - - if (active) { - DOM.sinkEvents(up, Event.ONCLICK); - up.setClassName(VFilterSelect.this.getStylePrimaryName() - + "-prevpage"); - } else { - DOM.sinkEvents(up, 0); - up.setClassName(VFilterSelect.this.getStylePrimaryName() - + "-prevpage-off"); - } - - } - - /** - * Selects the next item in the filtered selections - */ - public void selectNextItem() { - debug("VFS.SP: selectNextItem()"); - - final int index = menu.getSelectedIndex() + 1; - if (menu.getItems().size() > index) { - selectItem(menu.getItems().get(index)); - - } else { - selectNextPage(); - } - } - - /** - * Selects the previous item in the filtered selections - */ - public void selectPrevItem() { - debug("VFS.SP: selectPrevItem()"); - - final int index = menu.getSelectedIndex() - 1; - if (index > -1) { - selectItem(menu.getItems().get(index)); - - } else if (index == -1) { - selectPrevPage(); - - } else { - if (!menu.getItems().isEmpty()) { - selectLastItem(); - } - } - } - - /** - * Select the first item of the suggestions list popup. - * - * @since 7.2.6 - */ - public void selectFirstItem() { - debug("VFS.SP: selectFirstItem()"); - selectItem(menu.getFirstItem()); - } - - /** - * Select the last item of the suggestions list popup. - * - * @since 7.2.6 - */ - public void selectLastItem() { - debug("VFS.SP: selectLastItem()"); - selectItem(menu.getLastItem()); - } - - /* - * Sets the selected item in the popup menu. - */ - private void selectItem(final MenuItem newSelectedItem) { - menu.selectItem(newSelectedItem); - - // Set the icon. - FilterSelectSuggestion suggestion = (FilterSelectSuggestion) newSelectedItem - .getCommand(); - setSelectedItemIcon(suggestion.getIconUri()); - - // Set the text. - setText(suggestion.getReplacementString()); - - } - - /* - * Using a timer to scroll up or down the pages so when we receive lots - * of consecutive mouse wheel events the pages does not flicker. - */ - private LazyPageScroller lazyPageScroller = new LazyPageScroller(); - - private class LazyPageScroller extends Timer { - private int pagesToScroll = 0; - - @Override - public void run() { - debug("VFS.SP.LPS: run()"); - if (pagesToScroll != 0) { - if (!waitingForFilteringResponse) { - /* - * Avoid scrolling while we are waiting for a response - * because otherwise the waiting flag will be reset in - * the first response and the second response will be - * ignored, causing an empty popup... - * - * As long as the scrolling delay is suitable - * double/triple clicks will work by scrolling two or - * three pages at a time and this should not be a - * problem. - */ - filterOptions(currentPage + pagesToScroll, lastFilter); - } - pagesToScroll = 0; - } - } - - public void scrollUp() { - debug("VFS.SP.LPS: scrollUp()"); - if (pageLength > 0 && currentPage + pagesToScroll > 0) { - pagesToScroll--; - cancel(); - schedule(200); - } - } - - public void scrollDown() { - debug("VFS.SP.LPS: scrollDown()"); - if (pageLength > 0 - && totalMatches > (currentPage + pagesToScroll + 1) - * pageLength) { - pagesToScroll++; - cancel(); - schedule(200); - } - } - } - - private void scroll(double deltaY) { - boolean scrollActive = menu.isScrollActive(); - - debug("VFS.SP: scroll() scrollActive: " + scrollActive); - - if (!scrollActive) { - if (deltaY > 0d) { - lazyPageScroller.scrollDown(); - } else { - lazyPageScroller.scrollUp(); - } - } - } - - @Override - public void onBrowserEvent(Event event) { - debug("VFS.SP: onBrowserEvent()"); - - if (event.getTypeInt() == Event.ONCLICK) { - final Element target = DOM.eventGetTarget(event); - if (target == up || target == DOM.getChild(up, 0)) { - lazyPageScroller.scrollUp(); - } else if (target == down || target == DOM.getChild(down, 0)) { - lazyPageScroller.scrollDown(); - } - - } - - /* - * Prevent the keyboard focus from leaving the textfield by - * preventing the default behaviour of the browser. Fixes #4285. - */ - handleMouseDownEvent(event); - } - - /** - * Should paging be enabled. If paging is enabled then only a certain - * amount of items are visible at a time and a scrollbar or buttons are - * visible to change page. If paging is turned of then all options are - * rendered into the popup menu. - * - * @param paging - * Should the paging be turned on? - */ - public void setPagingEnabled(boolean paging) { - debug("VFS.SP: setPagingEnabled(" + paging + ")"); - if (isPagingEnabled == paging) { - return; - } - if (paging) { - down.getStyle().clearDisplay(); - up.getStyle().clearDisplay(); - status.getStyle().clearDisplay(); - } else { - down.getStyle().setDisplay(Display.NONE); - up.getStyle().setDisplay(Display.NONE); - status.getStyle().setDisplay(Display.NONE); - } - isPagingEnabled = paging; - } - - @Override - public void setPosition(int offsetWidth, int offsetHeight) { - debug("VFS.SP: setPosition(" + offsetWidth + ", " + offsetHeight - + ")"); - - int top = topPosition; - int left = getPopupLeft(); - - // reset menu size and retrieve its "natural" size - menu.setHeight(""); - if (currentPage > 0 && !hasNextPage()) { - // fix height to avoid height change when getting to last page - menu.fixHeightTo(pageLength); - } - - final int desiredHeight = offsetHeight = getOffsetHeight(); - final int desiredWidth = getMainWidth(); - - debug("VFS.SP: desired[" + desiredWidth + ", " + desiredHeight - + "]"); - - Element menuFirstChild = menu.getElement().getFirstChildElement(); - final int naturalMenuWidth = WidgetUtil - .getRequiredWidth(menuFirstChild); - - if (popupOuterPadding == -1) { - popupOuterPadding = WidgetUtil - .measureHorizontalPaddingAndBorder(getElement(), 2); - } - - if (naturalMenuWidth < desiredWidth) { - menu.setWidth((desiredWidth - popupOuterPadding) + "px"); - menuFirstChild.getStyle().setWidth(100, Unit.PCT); - } - - if (BrowserInfo.get().isIE() - && BrowserInfo.get().getBrowserMajorVersion() < 11) { - // Must take margin,border,padding manually into account for - // menu element as we measure the element child and set width to - // the element parent - double naturalMenuOuterWidth = WidgetUtil - .getRequiredWidthDouble(menuFirstChild) - + getMarginBorderPaddingWidth(menu.getElement()); - - /* - * IE requires us to specify the width for the container - * element. Otherwise it will be 100% wide - */ - double rootWidth = Math.max(desiredWidth - popupOuterPadding, - naturalMenuOuterWidth); - getContainerElement().getStyle().setWidth(rootWidth, Unit.PX); - } - - final int vfsHeight = VFilterSelect.this.getOffsetHeight(); - final int spaceAvailableAbove = top - vfsHeight; - final int spaceAvailableBelow = Window.getClientHeight() - top; - if (spaceAvailableBelow < offsetHeight - && spaceAvailableBelow < spaceAvailableAbove) { - // popup on top of input instead - top -= offsetHeight + vfsHeight; - if (top < 0) { - offsetHeight += top; - top = 0; - } - } else { - offsetHeight = Math.min(offsetHeight, spaceAvailableBelow); - } - - // fetch real width (mac FF bugs here due GWT popups overflow:auto ) - offsetWidth = menuFirstChild.getOffsetWidth(); - - if (offsetHeight < desiredHeight) { - int menuHeight = offsetHeight; - if (isPagingEnabled) { - menuHeight -= up.getOffsetHeight() + down.getOffsetHeight() - + status.getOffsetHeight(); - } else { - final ComputedStyle s = new ComputedStyle(menu.getElement()); - menuHeight -= s.getIntProperty("marginBottom") - + s.getIntProperty("marginTop"); - } - - // If the available page height is really tiny then this will be - // negative and an exception will be thrown on setHeight. - int menuElementHeight = menu.getItemOffsetHeight(); - if (menuHeight < menuElementHeight) { - menuHeight = menuElementHeight; - } - - menu.setHeight(menuHeight + "px"); - - final int naturalMenuWidthPlusScrollBar = naturalMenuWidth - + WidgetUtil.getNativeScrollbarSize(); - if (offsetWidth < naturalMenuWidthPlusScrollBar) { - menu.setWidth(naturalMenuWidthPlusScrollBar + "px"); - } - } - - if (offsetWidth + left > Window.getClientWidth()) { - left = VFilterSelect.this.getAbsoluteLeft() - + VFilterSelect.this.getOffsetWidth() - offsetWidth; - if (left < 0) { - left = 0; - menu.setWidth(Window.getClientWidth() + "px"); - } - } - - setPopupPosition(left, top); - menu.scrollSelectionIntoView(); - } - - /** - * Was the popup just closed? - * - * @return true if popup was just closed - */ - public boolean isJustClosed() { - debug("VFS.SP: justClosed()"); - final long now = (new Date()).getTime(); - return (lastAutoClosed > 0 && (now - lastAutoClosed) < 200); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google - * .gwt.event.logical.shared.CloseEvent) - */ - - @Override - public void onClose(CloseEvent event) { - if (enableDebug) { - debug("VFS.SP: onClose(" + event.isAutoClosed() + ")"); - } - if (event.isAutoClosed()) { - lastAutoClosed = (new Date()).getTime(); - } - } - - /** - * Updates style names in suggestion popup to help theme building. - * - * @param uidl - * UIDL for the whole combo box - * @param componentState - * shared state of the combo box - */ - public void updateStyleNames(UIDL uidl, - AbstractComponentState componentState) { - debug("VFS.SP: updateStyleNames()"); - setStyleName(VFilterSelect.this.getStylePrimaryName() - + "-suggestpopup"); - menu.setStyleName(VFilterSelect.this.getStylePrimaryName() - + "-suggestmenu"); - status.setClassName(VFilterSelect.this.getStylePrimaryName() - + "-status"); - if (ComponentStateUtil.hasStyles(componentState)) { - for (String style : componentState.styles) { - if (!"".equals(style)) { - addStyleDependentName(style); - } - } - } - } - - } - - /** - * The menu where the suggestions are rendered - */ - public class SuggestionMenu extends MenuBar implements SubPartAware, - LoadHandler { - - private VLazyExecutor delayedImageLoadExecutioner = new VLazyExecutor( - 100, new ScheduledCommand() { - - @Override - public void execute() { - debug("VFS.SM: delayedImageLoadExecutioner()"); - if (suggestionPopup.isVisible() - && suggestionPopup.isAttached()) { - setWidth(""); - getElement().getFirstChildElement().getStyle() - .clearWidth(); - suggestionPopup - .setPopupPositionAndShow(suggestionPopup); - } - - } - }); - - /** - * Default constructor - */ - SuggestionMenu() { - super(true); - debug("VFS.SM: constructor()"); - addDomHandler(this, LoadEvent.getType()); - - setScrollEnabled(true); - } - - /** - * Fixes menus height to use same space as full page would use. Needed - * to avoid height changes when quickly "scrolling" to last page. - */ - public void fixHeightTo(int pageItemsCount) { - setHeight(getPreferredHeight(pageItemsCount)); - } - - /* - * Gets the preferred height of the menu including pageItemsCount items. - */ - String getPreferredHeight(int pageItemsCount) { - if (currentSuggestions.size() > 0) { - final int pixels = (getPreferredHeight() / currentSuggestions - .size()) * pageItemsCount; - return pixels + "px"; - } else { - return ""; - } - } - - /** - * Sets the suggestions rendered in the menu - * - * @param suggestions - * The suggestions to be rendered in the menu - */ - public void setSuggestions( - Collection suggestions) { - if (enableDebug) { - debug("VFS.SM: setSuggestions(" + suggestions + ")"); - } - - clearItems(); - final Iterator it = suggestions.iterator(); - boolean isFirstIteration = true; - while (it.hasNext()) { - final FilterSelectSuggestion s = it.next(); - final MenuItem mi = new MenuItem(s.getDisplayString(), true, s); - String style = s.getStyle(); - if (style != null) { - mi.addStyleName("v-filterselect-item-" + style); - } - Roles.getListitemRole().set(mi.getElement()); - - WidgetUtil.sinkOnloadForImages(mi.getElement()); - - this.addItem(mi); - - // By default, first item on the list is always highlighted, - // unless adding new items is allowed. - if (isFirstIteration && !allowNewItem) { - selectItem(mi); - } - - // If the filter matches the current selection, highlight that - // instead of the first item. - if (tb.getText().equals(s.getReplacementString()) - && s == currentSuggestion) { - selectItem(mi); - } - - isFirstIteration = false; - } - } - - /** - * Send the current selection to the server. Triggered when a selection - * is made or on a blur event. - */ - public void doSelectedItemAction() { - debug("VFS.SM: doSelectedItemAction()"); - // do not send a value change event if null was and stays selected - final String enteredItemValue = tb.getText(); - if (nullSelectionAllowed && "".equals(enteredItemValue) - && selectedOptionKey != null - && !"".equals(selectedOptionKey)) { - if (nullSelectItem) { - reset(); - return; - } - // null is not visible on pages != 0, and not visible when - // filtering: handle separately - client.updateVariable(paintableId, "filter", "", false); - client.updateVariable(paintableId, "page", 0, false); - client.updateVariable(paintableId, "selected", new String[] {}, - immediate); - afterUpdateClientVariables(); - - suggestionPopup.hide(); - return; - } - - updateSelectionWhenReponseIsReceived = waitingForFilteringResponse; - if (!waitingForFilteringResponse) { - doPostFilterSelectedItemAction(); - } - } - - /** - * Triggered after a selection has been made - */ - public void doPostFilterSelectedItemAction() { - debug("VFS.SM: doPostFilterSelectedItemAction()"); - final MenuItem item = getSelectedItem(); - final String enteredItemValue = tb.getText(); - - updateSelectionWhenReponseIsReceived = false; - - // check for exact match in menu - int p = getItems().size(); - if (p > 0) { - for (int i = 0; i < p; i++) { - final MenuItem potentialExactMatch = getItems().get(i); - if (potentialExactMatch.getText().equals(enteredItemValue)) { - selectItem(potentialExactMatch); - // do not send a value change event if null was and - // stays selected - if (!"".equals(enteredItemValue) - || (selectedOptionKey != null && !"" - .equals(selectedOptionKey))) { - doItemAction(potentialExactMatch, true); - } - suggestionPopup.hide(); - return; - } - } - } - if (allowNewItem) { - - if (!prompting && !enteredItemValue.equals(lastNewItemString)) { - /* - * Store last sent new item string to avoid double sends - */ - lastNewItemString = enteredItemValue; - client.updateVariable(paintableId, "newitem", - enteredItemValue, immediate); - afterUpdateClientVariables(); - } - } else if (item != null - && !"".equals(lastFilter) - && (filteringmode == FilteringMode.CONTAINS ? item - .getText().toLowerCase() - .contains(lastFilter.toLowerCase()) : item - .getText().toLowerCase() - .startsWith(lastFilter.toLowerCase()))) { - doItemAction(item, true); - } else { - // currentSuggestion has key="" for nullselection - if (currentSuggestion != null - && !currentSuggestion.key.equals("")) { - // An item (not null) selected - String text = currentSuggestion.getReplacementString(); - setText(text); - selectedOptionKey = currentSuggestion.key; - } else { - // Null selected - setText(""); - selectedOptionKey = null; - } - } - suggestionPopup.hide(); - } - - private static final String SUBPART_PREFIX = "item"; - - @Override - public com.google.gwt.user.client.Element getSubPartElement( - String subPart) { - int index = Integer.parseInt(subPart.substring(SUBPART_PREFIX - .length())); - - MenuItem item = getItems().get(index); - - return item.getElement(); - } - - @Override - public String getSubPartName( - com.google.gwt.user.client.Element subElement) { - if (!getElement().isOrHasChild(subElement)) { - return null; - } - - Element menuItemRoot = subElement; - while (menuItemRoot != null - && !menuItemRoot.getTagName().equalsIgnoreCase("td")) { - menuItemRoot = menuItemRoot.getParentElement().cast(); - } - // "menuItemRoot" is now the root of the menu item - - final int itemCount = getItems().size(); - for (int i = 0; i < itemCount; i++) { - if (getItems().get(i).getElement() == menuItemRoot) { - String name = SUBPART_PREFIX + i; - return name; - } - } - return null; - } - - @Override - public void onLoad(LoadEvent event) { - debug("VFS.SM: onLoad()"); - // Handle icon onload events to ensure shadow is resized - // correctly - delayedImageLoadExecutioner.trigger(); - - } - - /** - * @deprecated use {@link SuggestionPopup#selectFirstItem()} instead. - */ - @Deprecated - public void selectFirstItem() { - debug("VFS.SM: selectFirstItem()"); - MenuItem firstItem = getItems().get(0); - selectItem(firstItem); - } - - /** - * @deprecated use {@link SuggestionPopup#selectLastItem()} instead. - */ - @Deprecated - public void selectLastItem() { - debug("VFS.SM: selectLastItem()"); - List items = getItems(); - MenuItem lastItem = items.get(items.size() - 1); - selectItem(lastItem); - } - - /* - * Gets the height of one menu item. - */ - int getItemOffsetHeight() { - List items = getItems(); - return items != null && items.size() > 0 ? items.get(0) - .getOffsetHeight() : 0; - } - - /* - * Gets the width of one menu item. - */ - int getItemOffsetWidth() { - List items = getItems(); - return items != null && items.size() > 0 ? items.get(0) - .getOffsetWidth() : 0; - } - - /** - * Returns true if the scroll is active on the menu element or if the - * menu currently displays the last page with less items then the - * maximum visibility (in which case the scroll is not active, but the - * scroll is active for any other page in general). - * - * @since 7.2.6 - */ - @Override - public boolean isScrollActive() { - String height = getElement().getStyle().getHeight(); - String preferredHeight = getPreferredHeight(pageLength); - - return !(height == null || height.length() == 0 || height - .equals(preferredHeight)); - } - - } - - /** - * TextBox variant used as input element for filter selects, which prevents - * selecting text when disabled. - * - * @since 7.1.5 - */ - public class FilterSelectTextBox extends TextBox { - - /** - * Overridden to avoid selecting text when text input is disabled - */ - @Override - public void setSelectionRange(int pos, int length) { - if (textInputEnabled) { - /* - * set selection range with a backwards direction: anchor at the - * back, focus at the front. This means that items that are too - * long to display will display from the start and not the end - * even on Firefox. - * - * We need the JSNI function to set selection range so that we - * can use the optional direction attribute to set the anchor to - * the end and the focus to the start. This makes Firefox work - * the same way as other browsers (#13477) - */ - WidgetUtil.setSelectionRange(getElement(), pos, length, - "backward"); - - } else { - /* - * Setting the selectionrange for an uneditable textbox leads to - * unwanted behaviour when the width of the textbox is narrower - * than the width of the entry: the end of the entry is shown - * instead of the beginning. (see #13477) - * - * To avoid this, we set the caret to the beginning of the line. - */ - - super.setSelectionRange(0, 0); - } - } - - } - - @Deprecated - public static final FilteringMode FILTERINGMODE_OFF = FilteringMode.OFF; - @Deprecated - public static final FilteringMode FILTERINGMODE_STARTSWITH = FilteringMode.STARTSWITH; - @Deprecated - public static final FilteringMode FILTERINGMODE_CONTAINS = FilteringMode.CONTAINS; - - public static final String CLASSNAME = "v-filterselect"; - private static final String STYLE_NO_INPUT = "no-input"; - - /** For internal use only. May be removed or replaced in the future. */ - public int pageLength = 10; - - private boolean enableDebug = false; - - private final FlowPanel panel = new FlowPanel(); - - /** - * The text box where the filter is written - *

- * For internal use only. May be removed or replaced in the future. - */ - public final TextBox tb; - - /** For internal use only. May be removed or replaced in the future. */ - public final SuggestionPopup suggestionPopup; - - /** - * Used when measuring the width of the popup - */ - private final HTML popupOpener = new HTML("") { - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt - * .user.client.Event) - */ - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - - /* - * Prevent the keyboard focus from leaving the textfield by - * preventing the default behaviour of the browser. Fixes #4285. - */ - handleMouseDownEvent(event); - } - }; - - private class IconWidget extends Widget { - IconWidget(Icon icon) { - setElement(icon.getElement()); - addDomHandler(VFilterSelect.this, ClickEvent.getType()); - } - } - - private IconWidget selectedItemIcon; - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - public String paintableId; - - /** For internal use only. May be removed or replaced in the future. */ - public int currentPage; - - /** - * A collection of available suggestions (options) as received from the - * server. - *

- * For internal use only. May be removed or replaced in the future. - */ - public final List currentSuggestions = new ArrayList(); - - /** For internal use only. May be removed or replaced in the future. */ - public boolean immediate; - - /** For internal use only. May be removed or replaced in the future. */ - public String selectedOptionKey; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean waitingForFilteringResponse = false; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean updateSelectionWhenReponseIsReceived = false; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean initDone = false; - - /** For internal use only. May be removed or replaced in the future. */ - public String lastFilter = ""; - - /** For internal use only. May be removed or replaced in the future. */ - public enum Select { - NONE, FIRST, LAST - } - - /** For internal use only. May be removed or replaced in the future. */ - public Select selectPopupItemWhenResponseIsReceived = Select.NONE; - - /** - * The current suggestion selected from the dropdown. This is one of the - * values in currentSuggestions except when filtering, in this case - * currentSuggestion might not be in currentSuggestions. - *

- * For internal use only. May be removed or replaced in the future. - */ - public FilterSelectSuggestion currentSuggestion; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean allowNewItem; - - /** For internal use only. May be removed or replaced in the future. */ - public int totalMatches; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean nullSelectionAllowed; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean nullSelectItem; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean enabled; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean readonly; - - /** For internal use only. May be removed or replaced in the future. */ - public FilteringMode filteringmode = FilteringMode.OFF; - - // shown in unfocused empty field, disappears on focus (e.g "Search here") - private static final String CLASSNAME_PROMPT = "prompt"; - - /** For internal use only. May be removed or replaced in the future. */ - public String inputPrompt = ""; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean prompting = false; - - /** - * Set true when popupopened has been clicked. Cleared on each UIDL-update. - * This handles the special case where are not filtering yet and the - * selected value has changed on the server-side. See #2119 - *

- * For internal use only. May be removed or replaced in the future. - */ - public boolean popupOpenerClicked; - - /** For internal use only. May be removed or replaced in the future. */ - public int suggestionPopupMinWidth = 0; - - private int popupWidth = -1; - /** - * Stores the last new item string to avoid double submissions. Cleared on - * uidl updates. - *

- * For internal use only. May be removed or replaced in the future. - */ - public String lastNewItemString; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean focused = false; - - /** - * If set to false, the component should not allow entering text to the - * field even for filtering. - */ - private boolean textInputEnabled = true; - - /** - * Default constructor. - */ - public VFilterSelect() { - tb = createTextBox(); - suggestionPopup = createSuggestionPopup(); - - popupOpener.sinkEvents(Event.ONMOUSEDOWN); - Roles.getButtonRole() - .setAriaHiddenState(popupOpener.getElement(), true); - Roles.getButtonRole().set(popupOpener.getElement()); - - panel.add(tb); - panel.add(popupOpener); - initWidget(panel); - Roles.getComboboxRole().set(panel.getElement()); - - tb.addKeyDownHandler(this); - tb.addKeyUpHandler(this); - - tb.addFocusHandler(this); - tb.addBlurHandler(this); - tb.addClickHandler(this); - - popupOpener.addClickHandler(this); - - setStyleName(CLASSNAME); - - sinkEvents(Event.ONPASTE); - } - - private static double getMarginBorderPaddingWidth(Element element) { - final ComputedStyle s = new ComputedStyle(element); - return s.getMarginWidth() + s.getBorderWidth() + s.getPaddingWidth(); - - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.Composite#onBrowserEvent(com.google.gwt - * .user.client.Event) - */ - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - - if (event.getTypeInt() == Event.ONPASTE) { - if (textInputEnabled) { - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - - @Override - public void execute() { - filterOptions(currentPage); - } - }); - } - } - } - - /** - * This method will create the TextBox used by the VFilterSelect instance. - * It is invoked during the Constructor and should only be overridden if a - * custom TextBox shall be used. The overriding method cannot use any - * instance variables. - * - * @since 7.1.5 - * @return TextBox instance used by this VFilterSelect - */ - protected TextBox createTextBox() { - return new FilterSelectTextBox(); - } - - /** - * This method will create the SuggestionPopup used by the VFilterSelect - * instance. It is invoked during the Constructor and should only be - * overridden if a custom SuggestionPopup shall be used. The overriding - * method cannot use any instance variables. - * - * @since 7.1.5 - * @return SuggestionPopup instance used by this VFilterSelect - */ - protected SuggestionPopup createSuggestionPopup() { - return new SuggestionPopup(); - } - - @Override - public void setStyleName(String style) { - super.setStyleName(style); - updateStyleNames(); - } - - @Override - public void setStylePrimaryName(String style) { - super.setStylePrimaryName(style); - updateStyleNames(); - } - - protected void updateStyleNames() { - tb.setStyleName(getStylePrimaryName() + "-input"); - popupOpener.setStyleName(getStylePrimaryName() + "-button"); - suggestionPopup.setStyleName(getStylePrimaryName() + "-suggestpopup"); - } - - /** - * Does the Select have more pages? - * - * @return true if a next page exists, else false if the current page is the - * last page - */ - public boolean hasNextPage() { - if (pageLength > 0 && totalMatches > (currentPage + 1) * pageLength) { - return true; - } else { - return false; - } - } - - /** - * Filters the options at a certain page. Uses the text box input as a - * filter - * - * @param page - * The page which items are to be filtered - */ - public void filterOptions(int page) { - filterOptions(page, tb.getText()); - } - - /** - * Filters the options at certain page using the given filter - * - * @param page - * The page to filter - * @param filter - * The filter to apply to the components - */ - public void filterOptions(int page, String filter) { - filterOptions(page, filter, true); - } - - /** - * Filters the options at certain page using the given filter - * - * @param page - * The page to filter - * @param filter - * The filter to apply to the options - * @param immediate - * Whether to send the options request immediately - */ - private void filterOptions(int page, String filter, boolean immediate) { - debug("VFS: filterOptions(" + page + ", " + filter + ", " + immediate - + ")"); - - if (filter.equals(lastFilter) && currentPage == page) { - if (!suggestionPopup.isAttached()) { - suggestionPopup.showSuggestions(currentSuggestions, - currentPage, totalMatches); - } - return; - } - if (!filter.equals(lastFilter)) { - // when filtering, let the server decide the page unless we've - // set the filter to empty and explicitly said that we want to see - // the results starting from page 0. - if ("".equals(filter) && page != 0) { - // let server decide - page = -1; - } else { - page = 0; - } - } - - waitingForFilteringResponse = true; - client.updateVariable(paintableId, "filter", filter, false); - client.updateVariable(paintableId, "page", page, immediate); - afterUpdateClientVariables(); - - lastFilter = filter; - currentPage = page; - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateReadOnly() { - debug("VFS: updateReadOnly()"); - tb.setReadOnly(readonly || !textInputEnabled); - } - - public void setTextInputEnabled(boolean textInputEnabled) { - debug("VFS: setTextInputEnabled()"); - // Always update styles as they might have been overwritten - if (textInputEnabled) { - removeStyleDependentName(STYLE_NO_INPUT); - Roles.getTextboxRole().removeAriaReadonlyProperty(tb.getElement()); - } else { - addStyleDependentName(STYLE_NO_INPUT); - Roles.getTextboxRole().setAriaReadonlyProperty(tb.getElement(), - true); - } - - if (this.textInputEnabled == textInputEnabled) { - return; - } - - this.textInputEnabled = textInputEnabled; - updateReadOnly(); - } - - /** - * Sets the text in the text box. - * - * @param text - * the text to set in the text box - */ - public void setTextboxText(final String text) { - if (enableDebug) { - debug("VFS: setTextboxText(" + text + ")"); - } - setText(text); - } - - private void setText(final String text) { - /** - * To leave caret in the beginning of the line. SetSelectionRange - * wouldn't work on IE (see #13477) - */ - Direction previousDirection = tb.getDirection(); - tb.setDirection(Direction.RTL); - tb.setText(text); - tb.setDirection(previousDirection); - } - - /** - * Turns prompting on. When prompting is turned on a command prompt is shown - * in the text box if nothing has been entered. - */ - public void setPromptingOn() { - debug("VFS: setPromptingOn()"); - if (!prompting) { - prompting = true; - addStyleDependentName(CLASSNAME_PROMPT); - } - setTextboxText(inputPrompt); - } - - /** - * Turns prompting off. When prompting is turned on a command prompt is - * shown in the text box if nothing has been entered. - *

- * For internal use only. May be removed or replaced in the future. - * - * @param text - * The text the text box should contain. - */ - public void setPromptingOff(String text) { - debug("VFS: setPromptingOff()"); - setTextboxText(text); - if (prompting) { - prompting = false; - removeStyleDependentName(CLASSNAME_PROMPT); - } - } - - /** - * Triggered when a suggestion is selected - * - * @param suggestion - * The suggestion that just got selected. - */ - public void onSuggestionSelected(FilterSelectSuggestion suggestion) { - if (enableDebug) { - debug("VFS: onSuggestionSelected(" + suggestion.caption + ": " - + suggestion.key + ")"); - } - updateSelectionWhenReponseIsReceived = false; - - currentSuggestion = suggestion; - String newKey; - if (suggestion.key.equals("")) { - // "nullselection" - newKey = ""; - } else { - // normal selection - newKey = suggestion.getOptionKey(); - } - - String text = suggestion.getReplacementString(); - if ("".equals(newKey) && !focused) { - setPromptingOn(); - } else { - setPromptingOff(text); - } - setSelectedItemIcon(suggestion.getIconUri()); - - if (!(newKey.equals(selectedOptionKey) || ("".equals(newKey) && selectedOptionKey == null))) { - selectedOptionKey = newKey; - client.updateVariable(paintableId, "selected", - new String[] { selectedOptionKey }, immediate); - afterUpdateClientVariables(); - - // currentPage = -1; // forget the page - } - suggestionPopup.hide(); - } - - /** - * Sets the icon URI of the selected item. The icon is shown on the left - * side of the item caption text. Set the URI to null to remove the icon. - * - * @param iconUri - * The URI of the icon - */ - public void setSelectedItemIcon(String iconUri) { - - if (iconUri == null || iconUri.length() == 0) { - if (selectedItemIcon != null) { - panel.remove(selectedItemIcon); - selectedItemIcon = null; - afterSelectedItemIconChange(); - } - } else { - if (selectedItemIcon != null) { - panel.remove(selectedItemIcon); - } - selectedItemIcon = new IconWidget(client.getIcon(iconUri)); - // Older IE versions don't scale icon correctly if DOM - // contains height and width attributes. - selectedItemIcon.getElement().removeAttribute("height"); - selectedItemIcon.getElement().removeAttribute("width"); - selectedItemIcon.addDomHandler(new LoadHandler() { - @Override - public void onLoad(LoadEvent event) { - afterSelectedItemIconChange(); - } - }, LoadEvent.getType()); - panel.insert(selectedItemIcon, 0); - afterSelectedItemIconChange(); - } - } - - private void afterSelectedItemIconChange() { - if (BrowserInfo.get().isWebkit() || BrowserInfo.get().isIE8()) { - // Some browsers need a nudge to reposition the text field - forceReflow(); - } - updateRootWidth(); - if (selectedItemIcon != null) { - updateSelectedIconPosition(); - } - } - - private void forceReflow() { - WidgetUtil.setStyleTemporarily(tb.getElement(), "zoom", "1"); - } - - /** - * Positions the icon vertically in the middle. Should be called after the - * icon has loaded - */ - private void updateSelectedIconPosition() { - // Position icon vertically to middle - int availableHeight = 0; - availableHeight = getOffsetHeight(); - - int iconHeight = WidgetUtil.getRequiredHeight(selectedItemIcon); - int marginTop = (availableHeight - iconHeight) / 2; - selectedItemIcon.getElement().getStyle() - .setMarginTop(marginTop, Unit.PX); - } - - private static Set navigationKeyCodes = new HashSet(); - static { - navigationKeyCodes.add(KeyCodes.KEY_DOWN); - navigationKeyCodes.add(KeyCodes.KEY_UP); - navigationKeyCodes.add(KeyCodes.KEY_PAGEDOWN); - navigationKeyCodes.add(KeyCodes.KEY_PAGEUP); - navigationKeyCodes.add(KeyCodes.KEY_ENTER); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt - * .event.dom.client.KeyDownEvent) - */ - - @Override - public void onKeyDown(KeyDownEvent event) { - if (enabled && !readonly) { - int keyCode = event.getNativeKeyCode(); - - if (enableDebug) { - debug("VFS: key down: " + keyCode); - } - if (waitingForFilteringResponse - && navigationKeyCodes.contains(keyCode)) { - /* - * Keyboard navigation events should not be handled while we are - * waiting for a response. This avoids flickering, disappearing - * items, wrongly interpreted responses and more. - */ - if (enableDebug) { - debug("Ignoring " - + keyCode - + " because we are waiting for a filtering response"); - } - DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); - event.stopPropagation(); - return; - } - - if (suggestionPopup.isAttached()) { - if (enableDebug) { - debug("Keycode " + keyCode + " target is popup"); - } - popupKeyDown(event); - } else { - if (enableDebug) { - debug("Keycode " + keyCode + " target is text field"); - } - inputFieldKeyDown(event); - } - } - } - - private void debug(String string) { - if (enableDebug) { - VConsole.error(string); - } - } - - /** - * Triggered when a key is pressed in the text box - * - * @param event - * The KeyDownEvent - */ - private void inputFieldKeyDown(KeyDownEvent event) { - if (enableDebug) { - debug("VFS: inputFieldKeyDown(" + event.getNativeKeyCode() + ")"); - } - switch (event.getNativeKeyCode()) { - case KeyCodes.KEY_DOWN: - case KeyCodes.KEY_UP: - case KeyCodes.KEY_PAGEDOWN: - case KeyCodes.KEY_PAGEUP: - // open popup as from gadget - filterOptions(-1, ""); - lastFilter = ""; - tb.selectAll(); - break; - case KeyCodes.KEY_ENTER: - /* - * This only handles the case when new items is allowed, a text is - * entered, the popup opener button is clicked to close the popup - * and enter is then pressed (see #7560). - */ - if (!allowNewItem) { - return; - } - - if (currentSuggestion != null - && tb.getText().equals( - currentSuggestion.getReplacementString())) { - // Retain behavior from #6686 by returning without stopping - // propagation if there's nothing to do - return; - } - suggestionPopup.menu.doSelectedItemAction(); - - event.stopPropagation(); - break; - } - - } - - /** - * Triggered when a key was pressed in the suggestion popup. - * - * @param event - * The KeyDownEvent of the key - */ - private void popupKeyDown(KeyDownEvent event) { - if (enableDebug) { - debug("VFS: popupKeyDown(" + event.getNativeKeyCode() + ")"); - } - // Propagation of handled events is stopped so other handlers such as - // shortcut key handlers do not also handle the same events. - switch (event.getNativeKeyCode()) { - case KeyCodes.KEY_DOWN: - suggestionPopup.selectNextItem(); - - DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); - event.stopPropagation(); - break; - case KeyCodes.KEY_UP: - suggestionPopup.selectPrevItem(); - - DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); - event.stopPropagation(); - break; - case KeyCodes.KEY_PAGEDOWN: - selectNextPage(); - event.stopPropagation(); - break; - case KeyCodes.KEY_PAGEUP: - selectPrevPage(); - event.stopPropagation(); - break; - case KeyCodes.KEY_ESCAPE: - reset(); - DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); - event.stopPropagation(); - break; - case KeyCodes.KEY_TAB: - case KeyCodes.KEY_ENTER: - - if (!allowNewItem) { - int selected = suggestionPopup.menu.getSelectedIndex(); - if (selected != -1) { - onSuggestionSelected(currentSuggestions.get(selected)); - } else { - // The way VFilterSelect is done, it handles enter and tab - // in exactly the same way so we close the popup in both - // cases even though we could leave it open when pressing - // enter - suggestionPopup.hide(); - } - } else { - // Handle addition of new items. - suggestionPopup.menu.doSelectedItemAction(); - } - - event.stopPropagation(); - break; - } - - } - - /* - * Show the prev page. - */ - private void selectPrevPage() { - if (currentPage > 0) { - filterOptions(currentPage - 1, lastFilter); - selectPopupItemWhenResponseIsReceived = Select.LAST; - } - } - - /* - * Show the next page. - */ - private void selectNextPage() { - if (hasNextPage()) { - filterOptions(currentPage + 1, lastFilter); - selectPopupItemWhenResponseIsReceived = Select.FIRST; - } - } - - /** - * Triggered when a key was depressed - * - * @param event - * The KeyUpEvent of the key depressed - */ - - @Override - public void onKeyUp(KeyUpEvent event) { - if (enableDebug) { - debug("VFS: onKeyUp(" + event.getNativeKeyCode() + ")"); - } - if (enabled && !readonly) { - switch (event.getNativeKeyCode()) { - case KeyCodes.KEY_ENTER: - case KeyCodes.KEY_TAB: - case KeyCodes.KEY_SHIFT: - case KeyCodes.KEY_CTRL: - case KeyCodes.KEY_ALT: - case KeyCodes.KEY_DOWN: - case KeyCodes.KEY_UP: - case KeyCodes.KEY_PAGEDOWN: - case KeyCodes.KEY_PAGEUP: - case KeyCodes.KEY_ESCAPE: - // NOP - break; - default: - if (textInputEnabled) { - // when filtering, we always want to see the results on the - // first page first. - filterOptions(0); - } - break; - } - } - } - - /** - * Resets the Select to its initial state - */ - private void reset() { - debug("VFS: reset()"); - if (currentSuggestion != null) { - String text = currentSuggestion.getReplacementString(); - setPromptingOff(text); - setSelectedItemIcon(currentSuggestion.getIconUri()); - - selectedOptionKey = currentSuggestion.key; - - } else { - if (focused || readonly || !enabled) { - setPromptingOff(""); - } else { - setPromptingOn(); - } - setSelectedItemIcon(null); - - selectedOptionKey = null; - } - - lastFilter = ""; - suggestionPopup.hide(); - } - - /** - * Listener for popupopener - */ - - @Override - public void onClick(ClickEvent event) { - debug("VFS: onClick()"); - if (textInputEnabled - && event.getNativeEvent().getEventTarget().cast() == tb - .getElement()) { - // Don't process clicks on the text field if text input is enabled - return; - } - if (enabled && !readonly) { - // ask suggestionPopup if it was just closed, we are using GWT - // Popup's auto close feature - if (!suggestionPopup.isJustClosed()) { - // If a focus event is not going to be sent, send the options - // request immediately; otherwise queue in the same burst as the - // focus event. Fixes #8321. - boolean immediate = focused - || !client.hasEventListeners(this, EventId.FOCUS); - filterOptions(-1, "", immediate); - popupOpenerClicked = true; - lastFilter = ""; - } - DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); - focus(); - tb.selectAll(); - } - } - - /** - * Update minimum width for FilterSelect textarea based on input prompt and - * suggestions. - *

- * For internal use only. May be removed or replaced in the future. - */ - public void updateSuggestionPopupMinWidth() { - // used only to calculate minimum width - String captions = WidgetUtil.escapeHTML(inputPrompt); - - for (FilterSelectSuggestion suggestion : currentSuggestions) { - // Collect captions so we can calculate minimum width for - // textarea - if (captions.length() > 0) { - captions += "|"; - } - captions += WidgetUtil - .escapeHTML(suggestion.getReplacementString()); - } - - // Calculate minimum textarea width - suggestionPopupMinWidth = minWidth(captions); - } - - /** - * Calculate minimum width for FilterSelect textarea. - *

- * For internal use only. May be removed or replaced in the future. - */ - public native int minWidth(String captions) - /*-{ - if(!captions || captions.length <= 0) - return 0; - captions = captions.split("|"); - var d = $wnd.document.createElement("div"); - var html = ""; - for(var i=0; i < captions.length; i++) { - html += "

" + captions[i] + "
"; - // TODO apply same CSS classname as in suggestionmenu - } - d.style.position = "absolute"; - d.style.top = "0"; - d.style.left = "0"; - d.style.visibility = "hidden"; - d.innerHTML = html; - $wnd.document.body.appendChild(d); - var w = d.offsetWidth; - $wnd.document.body.removeChild(d); - return w; - }-*/; - - /** - * A flag which prevents a focus event from taking place - */ - boolean iePreventNextFocus = false; - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event - * .dom.client.FocusEvent) - */ - - @Override - public void onFocus(FocusEvent event) { - debug("VFS: onFocus()"); - - /* - * When we disable a blur event in ie we need to refocus the textfield. - * This will cause a focus event we do not want to process, so in that - * case we just ignore it. - */ - if (BrowserInfo.get().isIE() && iePreventNextFocus) { - iePreventNextFocus = false; - return; - } - - focused = true; - if (prompting && !readonly) { - setPromptingOff(""); - } - addStyleDependentName("focus"); - - if (client.hasEventListeners(this, EventId.FOCUS)) { - client.updateVariable(paintableId, EventId.FOCUS, "", true); - afterUpdateClientVariables(); - } - - ComponentConnector connector = ConnectorMap.get(client).getConnector( - this); - client.getVTooltip().showAssistive( - connector.getTooltipInfo(getElement())); - } - - /** - * A flag which cancels the blur event and sets the focus back to the - * textfield if the Browser is IE - */ - boolean preventNextBlurEventInIE = false; - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event - * .dom.client.BlurEvent) - */ - - @Override - public void onBlur(BlurEvent event) { - debug("VFS: onBlur()"); - - if (BrowserInfo.get().isIE() && preventNextBlurEventInIE) { - /* - * Clicking in the suggestion popup or on the popup button in IE - * causes a blur event to be sent for the field. In other browsers - * this is prevented by canceling/preventing default behavior for - * the focus event, in IE we handle it here by refocusing the text - * field and ignoring the resulting focus event for the textfield - * (in onFocus). - */ - preventNextBlurEventInIE = false; - - Element focusedElement = WidgetUtil.getFocusedElement(); - if (getElement().isOrHasChild(focusedElement) - || suggestionPopup.getElement() - .isOrHasChild(focusedElement)) { - - // IF the suggestion popup or another part of the VFilterSelect - // was focused, move the focus back to the textfield and prevent - // the triggered focus event (in onFocus). - iePreventNextFocus = true; - tb.setFocus(true); - return; - } - } - - focused = false; - if (!readonly) { - if (selectedOptionKey == null) { - setPromptingOn(); - } else if (currentSuggestion != null) { - setPromptingOff(currentSuggestion.caption); - } - } - removeStyleDependentName("focus"); - - if (client.hasEventListeners(this, EventId.BLUR)) { - client.updateVariable(paintableId, EventId.BLUR, "", true); - afterUpdateClientVariables(); - } - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.Focusable#focus() - */ - - @Override - public void focus() { - debug("VFS: focus()"); - focused = true; - if (prompting && !readonly) { - setPromptingOff(""); - } - tb.setFocus(true); - } - - /** - * Calculates the width of the select if the select has undefined width. - * Should be called when the width changes or when the icon changes. - *

- * For internal use only. May be removed or replaced in the future. - */ - public void updateRootWidth() { - ComponentConnector paintable = ConnectorMap.get(client).getConnector( - this); - - if (paintable.isUndefinedWidth()) { - - /* - * When the select has a undefined with we need to check that we are - * only setting the text box width relative to the first page width - * of the items. If this is not done the text box width will change - * when the popup is used to view longer items than the text box is - * wide. - */ - int w = WidgetUtil.getRequiredWidth(this); - - if ((!initDone || currentPage + 1 < 0) - && suggestionPopupMinWidth > w) { - /* - * We want to compensate for the paddings just to preserve the - * exact size as in Vaadin 6.x, but we get here before - * MeasuredSize has been initialized. - * Util.measureHorizontalPaddingAndBorder does not work with - * border-box, so we must do this the hard way. - */ - Style style = getElement().getStyle(); - String originalPadding = style.getPadding(); - String originalBorder = style.getBorderWidth(); - style.setPaddingLeft(0, Unit.PX); - style.setBorderWidth(0, Unit.PX); - style.setProperty("padding", originalPadding); - style.setProperty("borderWidth", originalBorder); - - // Use util.getRequiredWidth instead of getOffsetWidth here - - int iconWidth = selectedItemIcon == null ? 0 : WidgetUtil - .getRequiredWidth(selectedItemIcon); - int buttonWidth = popupOpener == null ? 0 : WidgetUtil - .getRequiredWidth(popupOpener); - - /* - * Instead of setting the width of the wrapper, set the width of - * the combobox. Subtract the width of the icon and the - * popupopener - */ - - tb.setWidth((suggestionPopupMinWidth - iconWidth - buttonWidth) - + "px"); - - } - - /* - * Lock the textbox width to its current value if it's not already - * locked - */ - if (!tb.getElement().getStyle().getWidth().endsWith("px")) { - int iconWidth = selectedItemIcon == null ? 0 : selectedItemIcon - .getOffsetWidth(); - tb.setWidth((tb.getOffsetWidth() - iconWidth) + "px"); - } - } - } - - /** - * Get the width of the select in pixels where the text area and icon has - * been included. - * - * @return The width in pixels - */ - private int getMainWidth() { - return getOffsetWidth(); - } - - @Override - public void setWidth(String width) { - super.setWidth(width); - if (width.length() != 0) { - tb.setWidth("100%"); - } - } - - /** - * Handles special behavior of the mouse down event - * - * @param event - */ - private void handleMouseDownEvent(Event event) { - /* - * Prevent the keyboard focus from leaving the textfield by preventing - * the default behaviour of the browser. Fixes #4285. - */ - if (event.getTypeInt() == Event.ONMOUSEDOWN) { - event.preventDefault(); - event.stopPropagation(); - - /* - * In IE the above wont work, the blur event will still trigger. So, - * we set a flag here to prevent the next blur event from happening. - * This is not needed if do not already have focus, in that case - * there will not be any blur event and we should not cancel the - * next blur. - */ - if (BrowserInfo.get().isIE() && focused) { - preventNextBlurEventInIE = true; - debug("VFS: Going to prevent next blur event on IE"); - } - } - } - - @Override - protected void onDetach() { - super.onDetach(); - suggestionPopup.hide(); - } - - @Override - public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - String[] parts = subPart.split("/"); - if ("textbox".equals(parts[0])) { - return tb.getElement(); - } else if ("button".equals(parts[0])) { - return popupOpener.getElement(); - } else if ("popup".equals(parts[0]) && suggestionPopup.isAttached()) { - if (parts.length == 2) { - return suggestionPopup.menu.getSubPartElement(parts[1]); - } - return suggestionPopup.getElement(); - } - return null; - } - - @Override - public String getSubPartName(com.google.gwt.user.client.Element subElement) { - if (tb.getElement().isOrHasChild(subElement)) { - return "textbox"; - } else if (popupOpener.getElement().isOrHasChild(subElement)) { - return "button"; - } else if (suggestionPopup.getElement().isOrHasChild(subElement)) { - return "popup"; - } - return null; - } - - @Override - public void setAriaRequired(boolean required) { - AriaHelper.handleInputRequired(tb, required); - } - - @Override - public void setAriaInvalid(boolean invalid) { - AriaHelper.handleInputInvalid(tb, invalid); - } - - @Override - public void bindAriaCaption( - com.google.gwt.user.client.Element captionElement) { - AriaHelper.bindCaption(tb, captionElement); - } - - /* - * Anything that should be set after the client updates the server. - */ - private void afterUpdateClientVariables() { - // We need this here to be consistent with the all the calls. - // Then set your specific selection type only after - // client.updateVariable() method call. - selectPopupItemWhenResponseIsReceived = Select.NONE; - } - - @Override - public boolean isWorkPending() { - return waitingForFilteringResponse - || suggestionPopup.lazyPageScroller.isRunning(); - } - -} diff --git a/client/src/com/vaadin/client/ui/VFlash.java b/client/src/com/vaadin/client/ui/VFlash.java deleted file mode 100644 index eaf53836ee..0000000000 --- a/client/src/com/vaadin/client/ui/VFlash.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui; - -import java.util.HashMap; -import java.util.Map; - -import com.google.gwt.user.client.ui.HTML; -import com.vaadin.client.WidgetUtil; - -public class VFlash extends HTML { - - public static final String CLASSNAME = "v-flash"; - - protected String source; - protected String altText; - protected String classId; - protected String codebase; - protected String codetype; - protected String standby; - protected String archive; - protected Map embedParams = new HashMap(); - protected boolean needsRebuild = false; - protected String width; - protected String height; - - public VFlash() { - setStyleName(CLASSNAME); - } - - public void setSource(String source) { - if (this.source != source) { - this.source = source; - needsRebuild = true; - } - } - - public void setAlternateText(String altText) { - if (this.altText != altText) { - this.altText = altText; - needsRebuild = true; - } - } - - public void setClassId(String classId) { - if (this.classId != classId) { - this.classId = classId; - needsRebuild = true; - } - } - - public void setCodebase(String codebase) { - if (this.codebase != codebase) { - this.codebase = codebase; - needsRebuild = true; - } - } - - public void setCodetype(String codetype) { - if (this.codetype != codetype) { - this.codetype = codetype; - needsRebuild = true; - } - } - - public void setStandby(String standby) { - if (this.standby != standby) { - this.standby = standby; - needsRebuild = true; - } - } - - public void setArchive(String archive) { - if (this.archive != archive) { - this.archive = archive; - needsRebuild = true; - } - } - - /** - * Call this after changing values of widget. It will rebuild embedding - * structure if needed. - */ - public void rebuildIfNeeded() { - if (needsRebuild) { - needsRebuild = false; - this.setHTML(createFlashEmbed()); - } - } - - @Override - public void setWidth(String width) { - // super.setWidth(height); - - if (this.width != width) { - this.width = width; - needsRebuild = true; - } - } - - @Override - public void setHeight(String height) { - // super.setHeight(height); - - if (this.height != height) { - this.height = height; - needsRebuild = true; - } - } - - public void setEmbedParams(Map params) { - if (params == null) { - if (!embedParams.isEmpty()) { - embedParams.clear(); - needsRebuild = true; - } - return; - } - - if (!embedParams.equals(params)) { - embedParams = new HashMap(params); - needsRebuild = true; - } - } - - protected String createFlashEmbed() { - /* - * To ensure cross-browser compatibility we are using the twice-cooked - * method to embed flash i.e. we add a OBJECT tag for IE ActiveX and - * inside it a EMBED for all other browsers. - */ - - StringBuilder html = new StringBuilder(); - - // Start the object tag - html.append(""); - - // Ensure we have an movie parameter - if (embedParams.get("movie") == null) { - embedParams.put("movie", source); - } - - // Add parameters to OBJECT - for (String name : embedParams.keySet()) { - html.append(""); - } - - // Build inner EMBED tag - html.append(""); - - if (altText != null) { - html.append(""); - html.append(altText); - html.append(""); - } - - // End object tag - html.append(""); - - return html.toString(); - } - -} diff --git a/client/src/com/vaadin/client/ui/VForm.java b/client/src/com/vaadin/client/ui/VForm.java deleted file mode 100644 index ca38ea070b..0000000000 --- a/client/src/com/vaadin/client/ui/VForm.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.ComplexPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.VErrorMessage; - -public class VForm extends ComplexPanel implements KeyDownHandler { - - public static final String CLASSNAME = "v-form"; - - /** For internal use only. May be removed or replaced in the future. */ - public String id; - - /** For internal use only. May be removed or replaced in the future. */ - public Widget lo; - - /** For internal use only. May be removed or replaced in the future. */ - public Element legend = DOM.createLegend(); - - /** For internal use only. May be removed or replaced in the future. */ - public Element caption = DOM.createSpan(); - - /** For internal use only. May be removed or replaced in the future. */ - public Element desc = DOM.createDiv(); - - /** For internal use only. May be removed or replaced in the future. */ - public Icon icon; - - /** For internal use only. May be removed or replaced in the future. */ - public VErrorMessage errorMessage = new VErrorMessage(); - - /** For internal use only. May be removed or replaced in the future. */ - public Element fieldContainer = DOM.createDiv(); - - /** For internal use only. May be removed or replaced in the future. */ - public Element footerContainer = DOM.createDiv(); - - /** For internal use only. May be removed or replaced in the future. */ - public Element fieldSet = DOM.createFieldSet(); - - /** For internal use only. May be removed or replaced in the future. */ - public Widget footer; - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - public ShortcutActionHandler shortcutHandler; - - /** For internal use only. May be removed or replaced in the future. */ - public HandlerRegistration keyDownRegistration; - - public VForm() { - setElement(DOM.createDiv()); - getElement().appendChild(fieldSet); - setStyleName(CLASSNAME); - fieldSet.appendChild(legend); - legend.appendChild(caption); - - fieldSet.appendChild(desc); // Adding description for initial padding - // measurements, removed later if no - // description is set - - fieldSet.appendChild(fieldContainer); - errorMessage.setVisible(false); - - fieldSet.appendChild(errorMessage.getElement()); - fieldSet.appendChild(footerContainer); - - errorMessage.setOwner(this); - } - - @Override - public void setStyleName(String style) { - super.setStyleName(style); - updateStyleNames(); - } - - @Override - public void setStylePrimaryName(String style) { - super.setStylePrimaryName(style); - updateStyleNames(); - } - - protected void updateStyleNames() { - fieldContainer.setClassName(getStylePrimaryName() + "-content"); - errorMessage.setStyleName(getStylePrimaryName() + "-errormessage"); - desc.setClassName(getStylePrimaryName() + "-description"); - footerContainer.setClassName(getStylePrimaryName() + "-footer"); - } - - @Override - public void onKeyDown(KeyDownEvent event) { - shortcutHandler.handleKeyboardEvent(Event.as(event.getNativeEvent())); - } - - public void setFooterWidget(Widget footerWidget) { - if (footer != null) { - remove(footer); - } - if (footerWidget != null) { - super.add(footerWidget, footerContainer); - } - footer = footerWidget; - } - - public void setLayoutWidget(Widget newLayoutWidget) { - if (lo != null) { - remove(lo); - } - if (newLayoutWidget != null) { - super.add(newLayoutWidget, fieldContainer); - } - lo = newLayoutWidget; - } -} diff --git a/client/src/com/vaadin/client/ui/VFormLayout.java b/client/src/com/vaadin/client/ui/VFormLayout.java deleted file mode 100644 index 3719688f2b..0000000000 --- a/client/src/com/vaadin/client/ui/VFormLayout.java +++ /dev/null @@ -1,392 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import com.google.gwt.aria.client.Roles; -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.FlexTable; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.Focusable; -import com.vaadin.client.StyleConstants; -import com.vaadin.client.VTooltip; -import com.vaadin.client.ui.aria.AriaHelper; -import com.vaadin.shared.AbstractComponentState; -import com.vaadin.shared.ComponentConstants; -import com.vaadin.shared.ui.ComponentStateUtil; -import com.vaadin.shared.ui.MarginInfo; - -/** - * Two col Layout that places caption on left col and field on right col - */ -public class VFormLayout extends SimplePanel { - - private final static String CLASSNAME = "v-formlayout"; - - /** For internal use only. May be removed or replaced in the future. */ - public VFormLayoutTable table; - - public VFormLayout() { - super(); - setStyleName(CLASSNAME); - addStyleName(StyleConstants.UI_LAYOUT); - table = new VFormLayoutTable(); - setWidget(table); - } - - /** - * Parses the stylenames from shared state - * - * @param state - * shared state of the component - * @param enabled - * @return An array of stylenames - */ - private String[] getStylesFromState(AbstractComponentState state, - boolean enabled) { - List styles = new ArrayList(); - if (ComponentStateUtil.hasStyles(state)) { - for (String name : state.styles) { - styles.add(name); - } - } - - if (!enabled) { - styles.add(StyleConstants.DISABLED); - } - - return styles.toArray(new String[styles.size()]); - } - - public class VFormLayoutTable extends FlexTable implements ClickHandler { - - private static final int COLUMN_CAPTION = 0; - private static final int COLUMN_ERRORFLAG = 1; - public static final int COLUMN_WIDGET = 2; - - private HashMap widgetToCaption = new HashMap(); - private HashMap widgetToError = new HashMap(); - - public VFormLayoutTable() { - DOM.setElementProperty(getElement(), "cellPadding", "0"); - DOM.setElementProperty(getElement(), "cellSpacing", "0"); - - Roles.getPresentationRole().set(getElement()); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt - * .event.dom.client.ClickEvent) - */ - @Override - public void onClick(ClickEvent event) { - Caption caption = (Caption) event.getSource(); - if (caption.getOwner() != null) { - if (caption.getOwner() instanceof Focusable) { - ((Focusable) caption.getOwner()).focus(); - } else if (caption.getOwner() instanceof com.google.gwt.user.client.ui.Focusable) { - ((com.google.gwt.user.client.ui.Focusable) caption - .getOwner()).setFocus(true); - } - } - } - - public void setMargins(MarginInfo margins) { - Element margin = getElement(); - setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_TOP, - margins.hasTop()); - setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT, - margins.hasRight()); - setStyleName(margin, - CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM, - margins.hasBottom()); - setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_LEFT, - margins.hasLeft()); - - } - - public void setSpacing(boolean spacing) { - setStyleName(getElement(), CLASSNAME + "-" + "spacing", spacing); - - } - - public void setRowCount(int rowNr) { - for (int i = 0; i < rowNr; i++) { - prepareCell(i, COLUMN_CAPTION); - getCellFormatter().setStyleName(i, COLUMN_CAPTION, - CLASSNAME + "-captioncell"); - - prepareCell(i, 1); - getCellFormatter().setStyleName(i, COLUMN_ERRORFLAG, - CLASSNAME + "-errorcell"); - - prepareCell(i, 2); - getCellFormatter().setStyleName(i, COLUMN_WIDGET, - CLASSNAME + "-contentcell"); - - String rowstyles = CLASSNAME + "-row"; - if (i == 0) { - rowstyles += " " + CLASSNAME + "-firstrow"; - } - if (i == rowNr - 1) { - rowstyles += " " + CLASSNAME + "-lastrow"; - } - - getRowFormatter().setStyleName(i, rowstyles); - - } - while (getRowCount() != rowNr) { - removeRow(rowNr); - } - } - - public void setChild(int rowNr, Widget childWidget, Caption caption, - ErrorFlag error) { - setWidget(rowNr, COLUMN_WIDGET, childWidget); - setWidget(rowNr, COLUMN_CAPTION, caption); - setWidget(rowNr, COLUMN_ERRORFLAG, error); - - widgetToCaption.put(childWidget, caption); - widgetToError.put(childWidget, error); - - } - - public Caption getCaption(Widget childWidget) { - return widgetToCaption.get(childWidget); - } - - public ErrorFlag getError(Widget childWidget) { - return widgetToError.get(childWidget); - } - - public void cleanReferences(Widget oldChildWidget) { - widgetToError.remove(oldChildWidget); - widgetToCaption.remove(oldChildWidget); - - } - - public void updateCaption(Widget widget, AbstractComponentState state, - boolean enabled) { - final Caption c = widgetToCaption.get(widget); - if (c != null) { - c.updateCaption(state, enabled); - } - } - - public void updateError(Widget widget, String errorMessage, - boolean hideErrors) { - final ErrorFlag e = widgetToError.get(widget); - if (e != null) { - e.updateError(errorMessage, hideErrors); - } - - } - - } - - // TODO why duplicated here? - public class Caption extends HTML { - - public static final String CLASSNAME = "v-caption"; - - private final ComponentConnector owner; - - private Element requiredFieldIndicator; - - private Icon icon; - - private Element captionContent; - - /** - * - * @param component - * optional owner of caption. If not set, getOwner will - * return null - */ - public Caption(ComponentConnector component) { - super(); - owner = component; - } - - private void setStyles(String[] styles) { - String styleName = CLASSNAME; - - if (styles != null) { - for (String style : styles) { - if (StyleConstants.DISABLED.equals(style)) { - // Add v-disabled also without classname prefix so - // generic v-disabled CSS rules work - styleName += " " + style; - } - - styleName += " " + CLASSNAME + "-" + style; - } - } - - setStyleName(styleName); - } - - public void updateCaption(AbstractComponentState state, boolean enabled) { - // Update styles as they might have changed when the caption changed - setStyles(getStylesFromState(state, enabled)); - - boolean isEmpty = true; - - if (icon != null) { - getElement().removeChild(icon.getElement()); - icon = null; - } - if (state.resources.containsKey(ComponentConstants.ICON_RESOURCE)) { - icon = owner.getConnection().getIcon( - state.resources.get(ComponentConstants.ICON_RESOURCE) - .getURL()); - DOM.insertChild(getElement(), icon.getElement(), 0); - - isEmpty = false; - } - - if (state.caption != null) { - if (captionContent == null) { - captionContent = DOM.createSpan(); - - AriaHelper.bindCaption(owner.getWidget(), captionContent); - - DOM.insertChild(getElement(), captionContent, - icon == null ? 0 : 1); - } - String c = state.caption; - if (c == null) { - c = ""; - } else { - isEmpty = false; - } - if (state.captionAsHtml) { - captionContent.setInnerHTML(c); - } else { - captionContent.setInnerText(c); - } - } else { - // TODO should span also be removed - } - - if (state.description != null && captionContent != null) { - addStyleDependentName("hasdescription"); - } else { - removeStyleDependentName("hasdescription"); - } - - boolean required = owner instanceof AbstractFieldConnector - && ((AbstractFieldConnector) owner).isRequired(); - - AriaHelper.handleInputRequired(owner.getWidget(), required); - - if (required) { - if (requiredFieldIndicator == null) { - requiredFieldIndicator = DOM.createSpan(); - DOM.setInnerText(requiredFieldIndicator, "*"); - DOM.setElementProperty(requiredFieldIndicator, "className", - "v-required-field-indicator"); - DOM.appendChild(getElement(), requiredFieldIndicator); - - // Hide the required indicator from screen reader, as this - // information is set directly at the input field - Roles.getTextboxRole().setAriaHiddenState( - requiredFieldIndicator, true); - } - } else { - if (requiredFieldIndicator != null) { - DOM.removeChild(getElement(), requiredFieldIndicator); - requiredFieldIndicator = null; - } - } - } - - /** - * Returns Paintable for which this Caption belongs to. - * - * @return owner Widget - */ - public ComponentConnector getOwner() { - return owner; - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public class ErrorFlag extends HTML { - private static final String CLASSNAME = VFormLayout.CLASSNAME - + "-error-indicator"; - Element errorIndicatorElement; - - private ComponentConnector owner; - - public ErrorFlag(ComponentConnector owner) { - setStyleName(CLASSNAME); - - if (!BrowserInfo.get().isTouchDevice()) { - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - this.owner = owner; - } - - public ComponentConnector getOwner() { - return owner; - } - - public void updateError(String errorMessage, boolean hideErrors) { - boolean showError = null != errorMessage; - if (hideErrors) { - showError = false; - } - - AriaHelper.handleInputInvalid(owner.getWidget(), showError); - - if (showError) { - if (errorIndicatorElement == null) { - errorIndicatorElement = DOM.createDiv(); - DOM.setInnerHTML(errorIndicatorElement, " "); - DOM.setElementProperty(errorIndicatorElement, "className", - "v-errorindicator"); - DOM.appendChild(getElement(), errorIndicatorElement); - - // Hide the error indicator from screen reader, as this - // information is set directly at the input field - Roles.getFormRole().setAriaHiddenState( - errorIndicatorElement, true); - } - - } else if (errorIndicatorElement != null) { - DOM.removeChild(getElement(), errorIndicatorElement); - errorIndicatorElement = null; - } - } - - } -} diff --git a/client/src/com/vaadin/client/ui/VGridLayout.java b/client/src/com/vaadin/client/ui/VGridLayout.java deleted file mode 100644 index 42bcb5060a..0000000000 --- a/client/src/com/vaadin/client/ui/VGridLayout.java +++ /dev/null @@ -1,925 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.ComplexPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorMap; -import com.vaadin.client.LayoutManager; -import com.vaadin.client.StyleConstants; -import com.vaadin.client.Util; -import com.vaadin.client.VCaption; -import com.vaadin.client.ui.gridlayout.GridLayoutConnector; -import com.vaadin.client.ui.layout.ComponentConnectorLayoutSlot; -import com.vaadin.client.ui.layout.VLayoutSlot; -import com.vaadin.shared.ui.AlignmentInfo; -import com.vaadin.shared.ui.MarginInfo; -import com.vaadin.shared.ui.gridlayout.GridLayoutState.ChildComponentData; - -public class VGridLayout extends ComplexPanel { - - public static final String CLASSNAME = "v-gridlayout"; - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - public HashMap widgetToCell = new HashMap(); - - /** For internal use only. May be removed or replaced in the future. */ - public int[] columnWidths; - - /** For internal use only. May be removed or replaced in the future. */ - public int[] rowHeights; - - /** For internal use only. May be removed or replaced in the future. */ - public int[] colExpandRatioArray; - - /** For internal use only. May be removed or replaced in the future. */ - public int[] rowExpandRatioArray; - - int[] minColumnWidths; - - private int[] minRowHeights; - - /** For internal use only. May be removed or replaced in the future. */ - public DivElement spacingMeasureElement; - public Set explicitRowRatios; - public Set explicitColRatios; - - public boolean hideEmptyRowsAndColumns = false; - - public VGridLayout() { - super(); - setElement(Document.get().createDivElement()); - - spacingMeasureElement = Document.get().createDivElement(); - Style spacingStyle = spacingMeasureElement.getStyle(); - spacingStyle.setPosition(Position.ABSOLUTE); - getElement().appendChild(spacingMeasureElement); - - setStyleName(CLASSNAME); - addStyleName(StyleConstants.UI_LAYOUT); - } - - private GridLayoutConnector getConnector() { - return (GridLayoutConnector) ConnectorMap.get(client) - .getConnector(this); - } - - /** - * Returns the column widths measured in pixels - * - * @return - */ - protected int[] getColumnWidths() { - return columnWidths; - } - - /** - * Returns the row heights measured in pixels - * - * @return - */ - protected int[] getRowHeights() { - return rowHeights; - } - - /** - * Returns the spacing between the cells horizontally in pixels - * - * @return - */ - protected int getHorizontalSpacing() { - return LayoutManager.get(client).getOuterWidth(spacingMeasureElement); - } - - /** - * Returns the spacing between the cells vertically in pixels - * - * @return - */ - protected int getVerticalSpacing() { - return LayoutManager.get(client).getOuterHeight(spacingMeasureElement); - } - - static int[] cloneArray(int[] toBeCloned) { - int[] clone = new int[toBeCloned.length]; - for (int i = 0; i < clone.length; i++) { - clone[i] = toBeCloned[i] * 1; - } - return clone; - } - - void expandRows() { - if (!isUndefinedHeight()) { - int usedSpace = calcRowUsedSpace(); - int[] actualExpandRatio = calcRowExpandRatio(); - // Round down to avoid problems with fractions (100.1px available -> - // can use 100, not 101) - int availableSpace = (int) LayoutManager.get(client) - .getInnerHeightDouble(getElement()); - int excessSpace = availableSpace - usedSpace; - int distributed = 0; - if (excessSpace > 0) { - int expandRatioSum = 0; - for (int i = 0; i < rowHeights.length; i++) { - expandRatioSum += actualExpandRatio[i]; - } - for (int i = 0; i < rowHeights.length; i++) { - int ew = excessSpace * actualExpandRatio[i] - / expandRatioSum; - rowHeights[i] = minRowHeights[i] + ew; - distributed += ew; - } - excessSpace -= distributed; - int c = 0; - while (excessSpace > 0) { - rowHeights[c % rowHeights.length]++; - excessSpace--; - c++; - } - } - } - } - - private int[] calcRowExpandRatio() { - int[] actualExpandRatio = new int[minRowHeights.length]; - for (int i = 0; i < minRowHeights.length; i++) { - if (hiddenEmptyRow(i)) { - actualExpandRatio[i] = 0; - } else { - actualExpandRatio[i] = rowExpandRatioArray[i]; - } - } - return actualExpandRatio; - } - - /** - * Checks if it is ok to hide (or ignore) the given row. - * - * @param rowIndex - * the row to check - * @return true, if the row should be interpreted as non-existant (hides - * extra spacing) - */ - private boolean hiddenEmptyRow(int rowIndex) { - return hideEmptyRowsAndColumns && !rowHasComponentsOrRowSpan(rowIndex) - && !explicitRowRatios.contains(rowIndex); - } - - /** - * Checks if it is ok to hide (or ignore) the given column. - * - * @param columnIndex - * the column to check - * @return true, if the column should be interpreted as non-existant (hides - * extra spacing) - */ - private boolean hiddenEmptyColumn(int columnIndex) { - return hideEmptyRowsAndColumns - && !colHasComponentsOrColSpan(columnIndex) - && !explicitColRatios.contains(columnIndex); - } - - private int calcRowUsedSpace() { - int usedSpace = minRowHeights[0]; - int verticalSpacing = getVerticalSpacing(); - for (int i = 1; i < minRowHeights.length; i++) { - if (minRowHeights[i] > 0 || !hiddenEmptyRow(i)) { - usedSpace += verticalSpacing + minRowHeights[i]; - } - } - return usedSpace; - } - - void expandColumns() { - if (!isUndefinedWidth()) { - int usedSpace = calcColumnUsedSpace(); - int[] actualExpandRatio = calcColumnExpandRatio(); - // Round down to avoid problems with fractions (100.1px available -> - // can use 100, not 101) - int availableSpace = (int) LayoutManager.get(client) - .getInnerWidthDouble(getElement()); - int excessSpace = availableSpace - usedSpace; - int distributed = 0; - if (excessSpace > 0) { - int expandRatioSum = 0; - for (int i = 0; i < columnWidths.length; i++) { - expandRatioSum += actualExpandRatio[i]; - } - for (int i = 0; i < columnWidths.length; i++) { - int ew = excessSpace * actualExpandRatio[i] - / expandRatioSum; - columnWidths[i] = minColumnWidths[i] + ew; - distributed += ew; - } - excessSpace -= distributed; - int c = 0; - while (excessSpace > 0) { - columnWidths[c % columnWidths.length]++; - excessSpace--; - c++; - } - } - } - } - - /** - * Calculates column expand ratio. - */ - private int[] calcColumnExpandRatio() { - int[] actualExpandRatio = new int[minColumnWidths.length]; - for (int i = 0; i < minColumnWidths.length; i++) { - if (!hiddenEmptyColumn(i)) { - actualExpandRatio[i] = colExpandRatioArray[i]; - } else { - actualExpandRatio[i] = 0; - } - } - return actualExpandRatio; - } - - /** - * Calculates column used space - */ - private int calcColumnUsedSpace() { - int usedSpace = minColumnWidths[0]; - int horizontalSpacing = getHorizontalSpacing(); - for (int i = 1; i < minColumnWidths.length; i++) { - if (minColumnWidths[i] > 0 || !hiddenEmptyColumn(i)) { - usedSpace += horizontalSpacing + minColumnWidths[i]; - } - } - return usedSpace; - } - - private boolean rowHasComponentsOrRowSpan(int i) { - for (Cell cell : widgetToCell.values()) { - if (cell.row == i) { - return true; - } - } - for (SpanList l : rowSpans) { - for (Cell cell : l.cells) { - if (cell.row <= i && i < cell.row + cell.rowspan) { - return true; - } - } - } - return false; - } - - private boolean colHasComponentsOrColSpan(int i) { - for (Cell cell : widgetToCell.values()) { - if (cell.col == i) { - return true; - } - } - for (SpanList l : colSpans) { - for (Cell cell : l.cells) { - if (cell.col <= i && i < cell.col + cell.colspan) { - return true; - } - } - } - return false; - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateHeight() { - // Detect minimum heights & calculate spans - detectRowHeights(); - - // Expand - expandRows(); - - // Position - layoutCellsVertically(); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateWidth() { - // Detect widths & calculate spans - detectColWidths(); - // Expand - expandColumns(); - // Position - layoutCellsHorizontally(); - - } - - void layoutCellsVertically() { - int verticalSpacing = getVerticalSpacing(); - LayoutManager layoutManager = LayoutManager.get(client); - Element element = getElement(); - int paddingTop = layoutManager.getPaddingTop(element); - int paddingBottom = layoutManager.getPaddingBottom(element); - - int y = paddingTop; - for (int column = 0; column < cells.length; column++) { - y = paddingTop + 1 - 1; // Ensure IE10 does not optimize this out by - // adding something to evaluate on the RHS - // #11303 - - for (int row = 0; row < cells[column].length; row++) { - Cell cell = cells[column][row]; - if (cell != null) { - int reservedMargin; - if (cell.rowspan + row >= cells[column].length) { - // Make room for layout padding for cells reaching the - // bottom of the layout - reservedMargin = paddingBottom; - } else { - reservedMargin = 0; - } - - cell.layoutVertically(y, reservedMargin); - } - if (!hideEmptyRowsAndColumns || rowHasComponentsOrRowSpan(row) - || rowHeights[row] > 0) { - y += rowHeights[row] + verticalSpacing; - } - } - } - - if (isUndefinedHeight()) { - int outerHeight = y - verticalSpacing - + layoutManager.getPaddingBottom(element) - + layoutManager.getBorderHeight(element); - element.getStyle().setHeight(outerHeight, Unit.PX); - - getConnector().getLayoutManager().reportOuterHeight(getConnector(), - outerHeight); - } - } - - void layoutCellsHorizontally() { - LayoutManager layoutManager = LayoutManager.get(client); - Element element = getElement(); - int x = layoutManager.getPaddingLeft(element); - int paddingRight = layoutManager.getPaddingRight(element); - int horizontalSpacing = getHorizontalSpacing(); - for (int i = 0; i < cells.length; i++) { - for (int j = 0; j < cells[i].length; j++) { - Cell cell = cells[i][j]; - if (cell != null) { - int reservedMargin; - // Make room for layout padding for cells reaching the - // right edge of the layout - if (i + cell.colspan >= cells.length) { - reservedMargin = paddingRight; - } else { - reservedMargin = 0; - } - cell.layoutHorizontally(x, reservedMargin); - } - } - if (!hideEmptyRowsAndColumns || colHasComponentsOrColSpan(i) - || columnWidths[i] > 0) { - x += columnWidths[i] + horizontalSpacing; - } - } - - if (isUndefinedWidth()) { - int outerWidth = x - horizontalSpacing - + layoutManager.getPaddingRight(element) - + layoutManager.getBorderWidth(element); - element.getStyle().setWidth(outerWidth, Unit.PX); - getConnector().getLayoutManager().reportOuterWidth(getConnector(), - outerWidth); - } - } - - private boolean isUndefinedHeight() { - return getConnector().isUndefinedHeight(); - } - - private boolean isUndefinedWidth() { - return getConnector().isUndefinedWidth(); - } - - private void detectRowHeights() { - for (int i = 0; i < rowHeights.length; i++) { - rowHeights[i] = 0; - } - - // collect min rowheight from non-rowspanned cells - for (int i = 0; i < cells.length; i++) { - for (int j = 0; j < cells[i].length; j++) { - Cell cell = cells[i][j]; - if (cell != null) { - if (cell.rowspan == 1) { - if (!cell.hasRelativeHeight() - && rowHeights[j] < cell.getHeight()) { - rowHeights[j] = cell.getHeight(); - } - } else { - storeRowSpannedCell(cell); - } - } - } - } - - distributeRowSpanHeights(); - - minRowHeights = cloneArray(rowHeights); - } - - private void detectColWidths() { - // collect min colwidths from non-colspanned cells - for (int i = 0; i < columnWidths.length; i++) { - columnWidths[i] = 0; - } - - for (int i = 0; i < cells.length; i++) { - for (int j = 0; j < cells[i].length; j++) { - Cell cell = cells[i][j]; - if (cell != null) { - if (cell.colspan == 1) { - if (!cell.hasRelativeWidth() - && columnWidths[i] < cell.getWidth()) { - columnWidths[i] = cell.getWidth(); - } - } else { - storeColSpannedCell(cell); - } - } - } - } - - distributeColSpanWidths(); - - minColumnWidths = cloneArray(columnWidths); - } - - private void storeRowSpannedCell(Cell cell) { - SpanList l = null; - for (SpanList list : rowSpans) { - if (list.span < cell.rowspan) { - continue; - } else { - // insert before this - l = list; - break; - } - } - if (l == null) { - l = new SpanList(cell.rowspan); - rowSpans.add(l); - } else if (l.span != cell.rowspan) { - SpanList newL = new SpanList(cell.rowspan); - rowSpans.add(rowSpans.indexOf(l), newL); - l = newL; - } - l.cells.add(cell); - } - - /** - * Iterates colspanned cells, ensures cols have enough space to accommodate - * them - */ - void distributeColSpanWidths() { - for (SpanList list : colSpans) { - for (Cell cell : list.cells) { - // cells with relative content may return non 0 here if on - // subsequent renders - int width = cell.hasRelativeWidth() ? 0 : cell.getWidth(); - distributeSpanSize(columnWidths, cell.col, cell.colspan, - getHorizontalSpacing(), width, colExpandRatioArray); - } - } - } - - /** - * Iterates rowspanned cells, ensures rows have enough space to accommodate - * them - */ - private void distributeRowSpanHeights() { - for (SpanList list : rowSpans) { - for (Cell cell : list.cells) { - // cells with relative content may return non 0 here if on - // subsequent renders - int height = cell.hasRelativeHeight() ? 0 : cell.getHeight(); - distributeSpanSize(rowHeights, cell.row, cell.rowspan, - getVerticalSpacing(), height, rowExpandRatioArray); - } - } - } - - private static void distributeSpanSize(int[] dimensions, - int spanStartIndex, int spanSize, int spacingSize, int size, - int[] expansionRatios) { - int allocated = dimensions[spanStartIndex]; - for (int i = 1; i < spanSize; i++) { - allocated += spacingSize + dimensions[spanStartIndex + i]; - } - if (allocated < size) { - // dimensions needs to be expanded due spanned cell - int neededExtraSpace = size - allocated; - int allocatedExtraSpace = 0; - - // Divide space according to expansion ratios if any span has a - // ratio - int totalExpansion = 0; - for (int i = 0; i < spanSize; i++) { - int itemIndex = spanStartIndex + i; - totalExpansion += expansionRatios[itemIndex]; - } - - for (int i = 0; i < spanSize; i++) { - int itemIndex = spanStartIndex + i; - int expansion; - if (totalExpansion == 0) { - // Divide equally among all cells if there are no - // expansion ratios - expansion = neededExtraSpace / spanSize; - } else { - expansion = neededExtraSpace * expansionRatios[itemIndex] - / totalExpansion; - } - dimensions[itemIndex] += expansion; - allocatedExtraSpace += expansion; - } - - // We might still miss a couple of pixels because of - // rounding errors... - if (neededExtraSpace > allocatedExtraSpace) { - for (int i = 0; i < spanSize; i++) { - // Add one pixel to every cell until we have - // compensated for any rounding error - int itemIndex = spanStartIndex + i; - dimensions[itemIndex] += 1; - allocatedExtraSpace += 1; - if (neededExtraSpace == allocatedExtraSpace) { - break; - } - } - } - } - } - - private LinkedList colSpans = new LinkedList(); - private LinkedList rowSpans = new LinkedList(); - - private class SpanList { - final int span; - List cells = new LinkedList(); - - public SpanList(int span) { - this.span = span; - } - } - - void storeColSpannedCell(Cell cell) { - SpanList l = null; - for (SpanList list : colSpans) { - if (list.span < cell.colspan) { - continue; - } else { - // insert before this - l = list; - break; - } - } - if (l == null) { - l = new SpanList(cell.colspan); - colSpans.add(l); - } else if (l.span != cell.colspan) { - - SpanList newL = new SpanList(cell.colspan); - colSpans.add(colSpans.indexOf(l), newL); - l = newL; - } - l.cells.add(cell); - } - - Cell[][] cells; - - /** - * Private helper class. - */ - /** For internal use only. May be removed or replaced in the future. */ - public class Cell { - public Cell(int row, int col) { - this.row = row; - this.col = col; - } - - public boolean hasRelativeHeight() { - if (slot != null) { - return slot.getChild().isRelativeHeight(); - } else { - return true; - } - } - - /** - * @return total of spanned cols - */ - private int getAvailableWidth() { - int width = columnWidths[col]; - for (int i = 1; i < colspan; i++) { - width += getHorizontalSpacing() + columnWidths[col + i]; - } - return width; - } - - /** - * @return total of spanned rows - */ - private int getAvailableHeight() { - int height = rowHeights[row]; - for (int i = 1; i < rowspan; i++) { - height += getVerticalSpacing() + rowHeights[row + i]; - } - return height; - } - - public void layoutHorizontally(int x, int marginRight) { - if (slot != null) { - slot.positionHorizontally(x, getAvailableWidth(), marginRight); - } - } - - public void layoutVertically(int y, int marginBottom) { - if (slot != null) { - slot.positionVertically(y, getAvailableHeight(), marginBottom); - } - } - - public int getWidth() { - if (slot != null) { - return slot.getUsedWidth(); - } else { - return 0; - } - } - - public int getHeight() { - if (slot != null) { - return slot.getUsedHeight(); - } else { - return 0; - } - } - - protected boolean hasRelativeWidth() { - if (slot != null) { - return slot.getChild().isRelativeWidth(); - } else { - return true; - } - } - - private int row; - private int col; - int colspan = 1; - int rowspan = 1; - - private AlignmentInfo alignment; - - /** For internal use only. May be removed or replaced in the future. */ - public ComponentConnectorLayoutSlot slot; - - public void updateCell(ChildComponentData childComponentData) { - if (row != childComponentData.row1 - || col != childComponentData.column1) { - // cell has been moved, update matrix - if (col < cells.length && cells.length != 0 - && row < cells[0].length && cells[col][row] == this) { - // Remove old reference if still relevant - cells[col][row] = null; - } - - row = childComponentData.row1; - col = childComponentData.column1; - - cells[col][row] = this; - } - - // Set cell width - colspan = 1 + childComponentData.column2 - - childComponentData.column1; - // Set cell height - rowspan = 1 + childComponentData.row2 - childComponentData.row1; - setAlignment(new AlignmentInfo(childComponentData.alignment)); - } - - public void setComponent(ComponentConnector component, - List ordering) { - if (slot == null || slot.getChild() != component) { - slot = new ComponentConnectorLayoutSlot(CLASSNAME, component, - getConnector()); - slot.setAlignment(alignment); - if (component.isRelativeWidth()) { - slot.getWrapperElement().getStyle().setWidth(100, Unit.PCT); - } - Element slotWrapper = slot.getWrapperElement(); - int childIndex = ordering.indexOf(component); - // insert new slot by proper index - // do not break default focus order - com.google.gwt.user.client.Element element = getElement(); - if (childIndex == ordering.size()) { - element.appendChild(slotWrapper); - } else if (childIndex == 0) { - element.insertAfter(slotWrapper, spacingMeasureElement); - } else { - // here we use childIndex - 1 + 1(spacingMeasureElement) - Element previousSlot = (Element) element - .getChild(childIndex); - element.insertAfter(slotWrapper, previousSlot); - } - - Widget widget = component.getWidget(); - insert(widget, slotWrapper, getWidgetCount(), false); - Cell oldCell = widgetToCell.put(widget, this); - if (oldCell != null) { - oldCell.slot.getWrapperElement().removeFromParent(); - oldCell.slot = null; - } - } - } - - public void setAlignment(AlignmentInfo alignmentInfo) { - alignment = alignmentInfo; - if (slot != null) { - slot.setAlignment(alignmentInfo); - } - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public Cell getCell(int row, int col) { - return cells[col][row]; - } - - /** - * Creates a new Cell with the given coordinates. - *

- * For internal use only. May be removed or replaced in the future. - * - * @param row - * @param col - * @return - */ - public Cell createNewCell(int row, int col) { - Cell cell = new Cell(row, col); - cells[col][row] = cell; - return cell; - } - - /** - * Returns the deepest nested child component which contains "element". The - * child component is also returned if "element" is part of its caption. - *

- * For internal use only. May be removed or replaced in the future. - * - * @param element - * An element that is a nested sub element of the root element in - * this layout - * @return The Paintable which the element is a part of. Null if the element - * belongs to the layout and not to a child. - * @deprecated As of 7.2, call or override {@link #getComponent(Element)} - * instead - */ - @Deprecated - public ComponentConnector getComponent( - com.google.gwt.user.client.Element element) { - return Util.getConnectorForElement(client, this, element); - - } - - /** - * Returns the deepest nested child component which contains "element". The - * child component is also returned if "element" is part of its caption. - *

- * For internal use only. May be removed or replaced in the future. - * - * @param element - * An element that is a nested sub element of the root element in - * this layout - * @return The Paintable which the element is a part of. Null if the element - * belongs to the layout and not to a child. - * - * @since 7.2 - */ - public ComponentConnector getComponent(Element element) { - return getComponent(DOM.asOld(element)); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void setCaption(Widget widget, VCaption caption) { - VLayoutSlot slot = widgetToCell.get(widget).slot; - - if (caption != null) { - // Logical attach. - getChildren().add(caption); - } - - // Physical attach if not null, also removes old caption - slot.setCaption(caption); - - if (caption != null) { - // Adopt. - adopt(caption); - } - } - - private void togglePrefixedStyleName(String name, boolean enabled) { - if (enabled) { - addStyleDependentName(name); - } else { - removeStyleDependentName(name); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateMarginStyleNames(MarginInfo marginInfo) { - togglePrefixedStyleName("margin-top", marginInfo.hasTop()); - togglePrefixedStyleName("margin-right", marginInfo.hasRight()); - togglePrefixedStyleName("margin-bottom", marginInfo.hasBottom()); - togglePrefixedStyleName("margin-left", marginInfo.hasLeft()); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateSpacingStyleName(boolean spacingEnabled) { - String styleName = getStylePrimaryName(); - if (spacingEnabled) { - spacingMeasureElement.addClassName(styleName + "-spacing-on"); - spacingMeasureElement.removeClassName(styleName + "-spacing-off"); - } else { - spacingMeasureElement.removeClassName(styleName + "-spacing-on"); - spacingMeasureElement.addClassName(styleName + "-spacing-off"); - } - } - - public void setSize(int rows, int cols) { - if (cells == null) { - cells = new Cell[cols][rows]; - } else if (cells.length != cols || cells[0].length != rows) { - Cell[][] newCells = new Cell[cols][rows]; - for (int i = 0; i < cells.length; i++) { - for (int j = 0; j < cells[i].length; j++) { - if (i < cols && j < rows) { - newCells[i][j] = cells[i][j]; - } - } - } - cells = newCells; - } - } - - @Override - public boolean remove(Widget w) { - boolean removed = super.remove(w); - if (removed) { - Cell cell = widgetToCell.remove(w); - if (cell != null) { - cell.slot.setCaption(null); - cell.slot.getWrapperElement().removeFromParent(); - cell.slot = null; - Style style = w.getElement().getStyle(); - style.clearTop(); - style.clearLeft(); - style.clearPosition(); - - if (cells.length < cell.col && cells.length != 0 - && cells[0].length < cell.row - && cells[cell.col][cell.row] == cell) { - cells[cell.col][cell.row] = null; - } - } - } - return removed; - } - -} diff --git a/client/src/com/vaadin/client/ui/VHorizontalLayout.java b/client/src/com/vaadin/client/ui/VHorizontalLayout.java deleted file mode 100644 index b890aefe3d..0000000000 --- a/client/src/com/vaadin/client/ui/VHorizontalLayout.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui; - -import com.vaadin.client.StyleConstants; -import com.vaadin.client.ui.orderedlayout.VAbstractOrderedLayout; - -/** - * Represents a layout where the children is ordered vertically - */ -public class VHorizontalLayout extends VAbstractOrderedLayout { - - public static final String CLASSNAME = "v-horizontallayout"; - - /** - * Default constructor - */ - public VHorizontalLayout() { - super(false); - setStyleName(CLASSNAME); - } - - @Override - public void setStyleName(String style) { - super.setStyleName(style); - addStyleName(StyleConstants.UI_LAYOUT); - addStyleName("v-horizontal"); - } -} diff --git a/client/src/com/vaadin/client/ui/VImage.java b/client/src/com/vaadin/client/ui/VImage.java deleted file mode 100644 index 2742182179..0000000000 --- a/client/src/com/vaadin/client/ui/VImage.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui; - -import com.google.gwt.user.client.ui.Image; - -public class VImage extends Image { - - public static final String CLASSNAME = "v-image"; - - public VImage() { - setStylePrimaryName(CLASSNAME); - } -} diff --git a/client/src/com/vaadin/client/ui/VLabel.java b/client/src/com/vaadin/client/ui/VLabel.java deleted file mode 100644 index a3572759c4..0000000000 --- a/client/src/com/vaadin/client/ui/VLabel.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.HTML; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.Util; -import com.vaadin.client.VTooltip; -import com.vaadin.client.WidgetUtil; - -public class VLabel extends HTML { - - public static final String CLASSNAME = "v-label"; - private static final String CLASSNAME_UNDEFINED_WIDTH = "v-label-undef-w"; - - public VLabel() { - super(); - setStyleName(CLASSNAME); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - public VLabel(String text) { - super(text); - setStyleName(CLASSNAME); - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (event.getTypeInt() == Event.ONLOAD) { - Util.notifyParentOfSizeChange(this, true); - event.stopPropagation(); - return; - } - } - - @Override - public void setWidth(String width) { - super.setWidth(width); - if (width == null || width.equals("")) { - setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, true); - } else { - setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, false); - } - } - - @Override - public void setText(String text) { - if (BrowserInfo.get().isIE8()) { - // #3983 - IE8 incorrectly replaces \n with
so we do the - // escaping manually and set as HTML - super.setHTML(WidgetUtil.escapeHTML(text)); - } else { - super.setText(text); - } - } -} diff --git a/client/src/com/vaadin/client/ui/VLazyExecutor.java b/client/src/com/vaadin/client/ui/VLazyExecutor.java deleted file mode 100644 index dfa4f574c6..0000000000 --- a/client/src/com/vaadin/client/ui/VLazyExecutor.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui; - -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.user.client.Timer; - -/** - * Executes the given command {@code delayMs} milliseconds after a call to - * {@link #trigger()}. Calling {@link #trigger()} again before the command has - * been executed causes the execution to be rescheduled to {@code delayMs} after - * the second call. - * - */ -public class VLazyExecutor { - - private Timer timer; - private int delayMs; - private ScheduledCommand cmd; - - /** - * @param delayMs - * Delay in milliseconds to wait before executing the command - * @param cmd - * The command to execute - */ - public VLazyExecutor(int delayMs, ScheduledCommand cmd) { - this.delayMs = delayMs; - this.cmd = cmd; - } - - /** - * Triggers execution of the command. Each call reschedules any existing - * execution to {@link #delayMs} milliseconds from that point in time. - */ - public void trigger() { - if (timer == null) { - timer = new Timer() { - @Override - public void run() { - timer = null; - cmd.execute(); - } - }; - } - // Schedule automatically cancels any old schedule - timer.schedule(delayMs); - - } - -} diff --git a/client/src/com/vaadin/client/ui/VLink.java b/client/src/com/vaadin/client/ui/VLink.java deleted file mode 100644 index de99a8617f..0000000000 --- a/client/src/com/vaadin/client/ui/VLink.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.HasEnabled; -import com.vaadin.client.Util; -import com.vaadin.shared.ui.BorderStyle; - -public class VLink extends HTML implements ClickHandler, HasEnabled { - - public static final String CLASSNAME = "v-link"; - - @Deprecated - protected static final BorderStyle BORDER_STYLE_DEFAULT = BorderStyle.DEFAULT; - - @Deprecated - protected static final BorderStyle BORDER_STYLE_MINIMAL = BorderStyle.MINIMAL; - - @Deprecated - protected static final BorderStyle BORDER_STYLE_NONE = BorderStyle.NONE; - - /** For internal use only. May be removed or replaced in the future. */ - public String src; - - /** For internal use only. May be removed or replaced in the future. */ - public String target; - - /** For internal use only. May be removed or replaced in the future. */ - public BorderStyle borderStyle = BorderStyle.DEFAULT; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean enabled; - - /** For internal use only. May be removed or replaced in the future. */ - public int targetWidth; - - /** For internal use only. May be removed or replaced in the future. */ - public int targetHeight; - - /** For internal use only. May be removed or replaced in the future. */ - public Element errorIndicatorElement; - - /** For internal use only. May be removed or replaced in the future. */ - public final Element anchor = DOM.createAnchor(); - - /** For internal use only. May be removed or replaced in the future. */ - public final Element captionElement = DOM.createSpan(); - - /** For internal use only. May be removed or replaced in the future. */ - public Icon icon; - - public VLink() { - super(); - getElement().appendChild(anchor); - anchor.appendChild(captionElement); - addClickHandler(this); - setStyleName(CLASSNAME); - } - - @Override - public void onClick(ClickEvent event) { - if (enabled) { - if (target == null) { - target = "_self"; - } - String features; - switch (borderStyle) { - case NONE: - features = "menubar=no,location=no,status=no"; - break; - case MINIMAL: - features = "menubar=yes,location=no,status=no"; - break; - default: - features = ""; - break; - } - - if (targetWidth > 0) { - features += (features.length() > 0 ? "," : "") + "width=" - + targetWidth; - } - if (targetHeight > 0) { - features += (features.length() > 0 ? "," : "") + "height=" - + targetHeight; - } - - if (features.length() > 0) { - // if 'special features' are set, use window.open(), unless - // a modifier key is held (ctrl to open in new tab etc) - Event e = DOM.eventGetCurrentEvent(); - if (!e.getCtrlKey() && !e.getAltKey() && !e.getShiftKey() - && !e.getMetaKey()) { - Window.open(src, target, features); - e.preventDefault(); - } - } - } - } - - @Override - public void onBrowserEvent(Event event) { - final Element target = DOM.eventGetTarget(event); - if (event.getTypeInt() == Event.ONLOAD) { - Util.notifyParentOfSizeChange(this, true); - } - if (target == captionElement || target == anchor - || (icon != null && target == icon.getElement())) { - super.onBrowserEvent(event); - } - if (!enabled) { - event.preventDefault(); - } - - } - - @Override - public boolean isEnabled() { - return enabled; - } - - @Override - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/client/src/com/vaadin/client/ui/VListSelect.java b/client/src/com/vaadin/client/ui/VListSelect.java deleted file mode 100644 index b6f4f0c722..0000000000 --- a/client/src/com/vaadin/client/ui/VListSelect.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.ArrayList; -import java.util.Iterator; - -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.user.client.ui.ListBox; -import com.vaadin.client.UIDL; - -public class VListSelect extends VOptionGroupBase { - - public static final String CLASSNAME = "v-select"; - - private static final int VISIBLE_COUNT = 10; - - protected ListBox select; - - private int lastSelectedIndex = -1; - - public VListSelect() { - super(new ListBox(true), CLASSNAME); - select = getOptionsContainer(); - select.addChangeHandler(this); - select.addClickHandler(this); - select.setVisibleItemCount(VISIBLE_COUNT); - setStyleName(CLASSNAME); - - updateEnabledState(); - } - - @Override - public void setStyleName(String style) { - super.setStyleName(style); - updateStyleNames(); - } - - @Override - public void setStylePrimaryName(String style) { - super.setStylePrimaryName(style); - updateStyleNames(); - } - - protected void updateStyleNames() { - container.setStyleName(getStylePrimaryName()); - select.setStyleName(getStylePrimaryName() + "-select"); - } - - protected ListBox getOptionsContainer() { - return (ListBox) optionsContainer; - } - - @Override - public void buildOptions(UIDL uidl) { - int scrollTop = select.getElement().getScrollTop(); - int rowCount = getRows(); - select.setMultipleSelect(isMultiselect()); - select.clear(); - if (!isMultiselect() && isNullSelectionAllowed() - && !isNullSelectionItemAvailable()) { - // can't unselect last item in singleselect mode - select.addItem("", (String) null); - } - for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { - final UIDL optionUidl = (UIDL) i.next(); - select.addItem(optionUidl.getStringAttribute("caption"), - optionUidl.getStringAttribute("key")); - if (optionUidl.hasAttribute("selected")) { - int itemIndex = select.getItemCount() - 1; - select.setItemSelected(itemIndex, true); - lastSelectedIndex = itemIndex; - } - } - if (getRows() > 0) { - select.setVisibleItemCount(getRows()); - } - // FIXME: temporary hack for preserving the scroll state when the - // contents haven't been changed obviously. This should be dealt with in - // the rewrite. - if (rowCount == getRows()) { - select.getElement().setScrollTop(scrollTop); - } - } - - @Override - protected String[] getSelectedItems() { - final ArrayList selectedItemKeys = new ArrayList(); - for (int i = 0; i < select.getItemCount(); i++) { - if (select.isItemSelected(i)) { - selectedItemKeys.add(select.getValue(i)); - } - } - return selectedItemKeys.toArray(new String[selectedItemKeys.size()]); - } - - @Override - public void onChange(ChangeEvent event) { - final int si = select.getSelectedIndex(); - if (si == -1 && !isNullSelectionAllowed()) { - select.setSelectedIndex(lastSelectedIndex); - } else { - lastSelectedIndex = si; - if (isMultiselect()) { - client.updateVariable(paintableId, "selected", - getSelectedItems(), isImmediate()); - } else { - client.updateVariable(paintableId, "selected", - new String[] { "" + getSelectedItem() }, isImmediate()); - } - } - } - - @Override - public void setHeight(String height) { - select.setHeight(height); - super.setHeight(height); - } - - @Override - public void setWidth(String width) { - select.setWidth(width); - super.setWidth(width); - } - - @Override - public void setTabIndex(int tabIndex) { - getOptionsContainer().setTabIndex(tabIndex); - } - - @Override - protected void updateEnabledState() { - select.setEnabled(isEnabled() && !isReadonly()); - } - - @Override - public void focus() { - select.setFocus(true); - } -} diff --git a/client/src/com/vaadin/client/ui/VMediaBase.java b/client/src/com/vaadin/client/ui/VMediaBase.java deleted file mode 100644 index 53d7cef02d..0000000000 --- a/client/src/com/vaadin/client/ui/VMediaBase.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.MediaElement; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.SourceElement; -import com.google.gwt.dom.client.Text; -import com.google.gwt.user.client.ui.Widget; - -public abstract class VMediaBase extends Widget { - - private MediaElement media; - private Text altText; - - /** - * Sets the MediaElement that is to receive all commands and properties. - * - * @param element - */ - public void setMediaElement(MediaElement element) { - setElement(element); - media = element; - } - - public void play() { - media.play(); - } - - public void pause() { - media.pause(); - } - - public void setAltText(String alt) { - if (altText == null) { - altText = Document.get().createTextNode(alt); - media.appendChild(altText); - } else { - altText.setNodeValue(alt); - } - } - - public void setControls(boolean shouldShowControls) { - media.setControls(shouldShowControls); - } - - public void setAutoplay(boolean shouldAutoplay) { - media.setAutoplay(shouldAutoplay); - } - - public void setMuted(boolean mediaMuted) { - media.setMuted(mediaMuted); - } - - public void removeAllSources() { - NodeList l = media - .getElementsByTagName(SourceElement.TAG); - for (int i = l.getLength() - 1; i >= 0; i--) { - media.removeChild(l.getItem(i)); - } - - } - - public void load() { - media.load(); - } - - public void addSource(String sourceUrl, String sourceType) { - Element src = Document.get().createElement(SourceElement.TAG); - src.setAttribute("src", sourceUrl); - src.setAttribute("type", sourceType); - media.appendChild(src); - } -} diff --git a/client/src/com/vaadin/client/ui/VMenuBar.java b/client/src/com/vaadin/client/ui/VMenuBar.java deleted file mode 100644 index b2b40c12f7..0000000000 --- a/client/src/com/vaadin/client/ui/VMenuBar.java +++ /dev/null @@ -1,1718 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.KeyPressEvent; -import com.google.gwt.event.dom.client.KeyPressHandler; -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.ui.HasHTML; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.LayoutManager; -import com.vaadin.client.TooltipInfo; -import com.vaadin.client.UIDL; -import com.vaadin.client.Util; -import com.vaadin.client.WidgetUtil; -import com.vaadin.shared.ui.menubar.MenuBarConstants; - -public class VMenuBar extends SimpleFocusablePanel implements - CloseHandler, KeyPressHandler, KeyDownHandler, - FocusHandler, SubPartAware { - - // The hierarchy of VMenuBar is a bit weird as VMenuBar is the Paintable, - // used for the root menu but also used for the sub menus. - - /** Set the CSS class name to allow styling. */ - public static final String CLASSNAME = "v-menubar"; - public static final String SUBMENU_CLASSNAME_PREFIX = "-submenu"; - - /** - * For server connections. - *

- * For internal use only. May be removed or replaced in the future. - */ - public String uidlId; - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - public final VMenuBar hostReference = this; - - /** For internal use only. May be removed or replaced in the future. */ - public CustomMenuItem moreItem = null; - - /** For internal use only. May be removed or replaced in the future. */ - public VMenuBar collapsedRootItems; - - /** - * An empty command to be used when the item has no command associated - *

- * For internal use only. May be removed or replaced in the future. - */ - public static final Command emptyCommand = null; - - /** Widget fields **/ - protected boolean subMenu; - protected ArrayList items; - protected Element containerElement; - protected VOverlay popup; - protected VMenuBar visibleChildMenu; - protected boolean menuVisible = false; - protected VMenuBar parentMenu; - protected CustomMenuItem selected; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean enabled = true; - - private VLazyExecutor iconLoadedExecutioner = new VLazyExecutor(100, - new ScheduledCommand() { - - @Override - public void execute() { - iLayout(true); - } - }); - - /** For internal use only. May be removed or replaced in the future. */ - public boolean openRootOnHover; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean htmlContentAllowed; - - public VMenuBar() { - // Create an empty horizontal menubar - this(false, null); - - // Navigation is only handled by the root bar - addFocusHandler(this); - - /* - * Firefox auto-repeat works correctly only if we use a key press - * handler, other browsers handle it correctly when using a key down - * handler - */ - if (BrowserInfo.get().isGecko()) { - addKeyPressHandler(this); - } else { - addKeyDownHandler(this); - } - } - - public VMenuBar(boolean subMenu, VMenuBar parentMenu) { - - items = new ArrayList(); - popup = null; - visibleChildMenu = null; - this.subMenu = subMenu; - - containerElement = getElement(); - - sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT - | Event.ONLOAD); - - if (parentMenu == null) { - // Root menu - setStyleName(CLASSNAME); - } else { - // Child menus inherits style name - setStyleName(parentMenu.getStyleName()); - } - } - - @Override - public void setStyleName(String style) { - super.setStyleName(style); - updateStyleNames(); - } - - @Override - public void setStylePrimaryName(String style) { - super.setStylePrimaryName(style); - updateStyleNames(); - } - - protected void updateStyleNames() { - String primaryStyleName = getParentMenu() != null ? getParentMenu() - .getStylePrimaryName() : getStylePrimaryName(); - - // Reset the style name for all the items - for (CustomMenuItem item : items) { - item.setStyleName(primaryStyleName + "-menuitem"); - } - - if (subMenu - && !getStylePrimaryName().endsWith(SUBMENU_CLASSNAME_PREFIX)) { - /* - * Sub-menus should get the sub-menu prefix - */ - super.setStylePrimaryName(primaryStyleName - + SUBMENU_CLASSNAME_PREFIX); - } - } - - @Override - protected void onDetach() { - super.onDetach(); - if (!subMenu) { - setSelected(null); - hideChildren(); - menuVisible = false; - } - } - - void updateSize() { - // Take from setWidth - if (!subMenu) { - // Only needed for root level menu - hideChildren(); - setSelected(null); - menuVisible = false; - } - } - - /** - * Build the HTML content for a menu item. - *

- * For internal use only. May be removed or replaced in the future. - */ - public String buildItemHTML(UIDL item) { - // Construct html from the text and the optional icon - StringBuffer itemHTML = new StringBuffer(); - if (item.hasAttribute("separator")) { - itemHTML.append("---"); - } else { - // Add submenu indicator - if (item.getChildCount() > 0) { - String bgStyle = ""; - itemHTML.append(""); - } - - itemHTML.append(""); - Icon icon = client.getIcon(item.getStringAttribute("icon")); - if (icon != null) { - itemHTML.append(icon.getElement().getString()); - } - String itemText = item.getStringAttribute("text"); - if (!htmlContentAllowed) { - itemText = WidgetUtil.escapeHTML(itemText); - } - itemHTML.append(itemText); - itemHTML.append(""); - } - return itemHTML.toString(); - } - - /** - * This is called by the items in the menu and it communicates the - * information to the server - * - * @param clickedItemId - * id of the item that was clicked - */ - public void onMenuClick(int clickedItemId) { - // Updating the state to the server can not be done before - // the server connection is known, i.e., before updateFromUIDL() - // has been called. - if (uidlId != null && client != null) { - // Communicate the user interaction parameters to server. This call - // will initiate an AJAX request to the server. - client.updateVariable(uidlId, "clickedId", clickedItemId, true); - } - } - - /** Widget methods **/ - - /** - * Returns a list of items in this menu - */ - public List getItems() { - return items; - } - - /** - * Remove all the items in this menu - */ - public void clearItems() { - Element e = getContainerElement(); - while (DOM.getChildCount(e) > 0) { - DOM.removeChild(e, DOM.getChild(e, 0)); - } - items.clear(); - } - - /** - * Returns the containing element of the menu - * - * @return - */ - @Override - public com.google.gwt.user.client.Element getContainerElement() { - return DOM.asOld(containerElement); - } - - /** - * Add a new item to this menu - * - * @param html - * items text - * @param cmd - * items command - * @return the item created - */ - public CustomMenuItem addItem(String html, Command cmd) { - CustomMenuItem item = GWT.create(CustomMenuItem.class); - item.setHTML(html); - item.setCommand(cmd); - addItem(item); - return item; - } - - /** - * Add a new item to this menu - * - * @param item - */ - public void addItem(CustomMenuItem item) { - if (items.contains(item)) { - return; - } - DOM.appendChild(getContainerElement(), item.getElement()); - item.setParentMenu(this); - item.setSelected(false); - items.add(item); - } - - public void addItem(CustomMenuItem item, int index) { - if (items.contains(item)) { - return; - } - DOM.insertChild(getContainerElement(), item.getElement(), index); - item.setParentMenu(this); - item.setSelected(false); - items.add(index, item); - } - - /** - * Remove the given item from this menu - * - * @param item - */ - public void removeItem(CustomMenuItem item) { - if (items.contains(item)) { - int index = items.indexOf(item); - - DOM.removeChild(getContainerElement(), - DOM.getChild(getContainerElement(), index)); - items.remove(index); - } - } - - /* - * @see - * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user - * .client.Event) - */ - @Override - public void onBrowserEvent(Event e) { - super.onBrowserEvent(e); - - // Handle onload events (icon loaded, size changes) - if (DOM.eventGetType(e) == Event.ONLOAD) { - VMenuBar parent = getParentMenu(); - if (parent != null) { - // The onload event for an image in a popup should be sent to - // the parent, which owns the popup - parent.iconLoaded(); - } else { - // Onload events for images in the root menu are handled by the - // root menu itself - iconLoaded(); - } - return; - } - - Element targetElement = DOM.eventGetTarget(e); - CustomMenuItem targetItem = null; - for (int i = 0; i < items.size(); i++) { - CustomMenuItem item = items.get(i); - if (DOM.isOrHasChild(item.getElement(), targetElement)) { - targetItem = item; - } - } - - if (targetItem != null) { - switch (DOM.eventGetType(e)) { - - case Event.ONCLICK: - if (isEnabled() && targetItem.isEnabled()) { - itemClick(targetItem); - } - if (subMenu) { - // Prevent moving keyboard focus to child menus - VMenuBar parent = parentMenu; - while (parent.getParentMenu() != null) { - parent = parent.getParentMenu(); - } - parent.setFocus(true); - } - - break; - - case Event.ONMOUSEOVER: - LazyCloser.cancelClosing(); - - if (isEnabled() && targetItem.isEnabled()) { - itemOver(targetItem); - } - break; - - case Event.ONMOUSEOUT: - itemOut(targetItem); - LazyCloser.schedule(); - break; - } - } else if (subMenu && DOM.eventGetType(e) == Event.ONCLICK && subMenu) { - // Prevent moving keyboard focus to child menus - VMenuBar parent = parentMenu; - while (parent.getParentMenu() != null) { - parent = parent.getParentMenu(); - } - parent.setFocus(true); - } - } - - private boolean isEnabled() { - return enabled; - } - - private void iconLoaded() { - iconLoadedExecutioner.trigger(); - } - - /** - * When an item is clicked - * - * @param item - */ - public void itemClick(CustomMenuItem item) { - if (item.getCommand() != null) { - setSelected(null); - - if (visibleChildMenu != null) { - visibleChildMenu.hideChildren(); - } - - hideParents(true); - menuVisible = false; - Scheduler.get().scheduleDeferred(item.getCommand()); - - } else { - if (item.getSubMenu() != null - && item.getSubMenu() != visibleChildMenu) { - setSelected(item); - showChildMenu(item); - menuVisible = true; - } else if (!subMenu) { - setSelected(null); - hideChildren(); - menuVisible = false; - } - } - } - - /** - * When the user hovers the mouse over the item - * - * @param item - */ - public void itemOver(CustomMenuItem item) { - if ((openRootOnHover || subMenu || menuVisible) && !item.isSeparator()) { - setSelected(item); - if (!subMenu && openRootOnHover && !menuVisible) { - menuVisible = true; // start opening menus - LazyCloser.prepare(this); - } - } - - if (menuVisible && visibleChildMenu != item.getSubMenu() - && popup != null) { - // #15255 - disable animation-in/out when hide in this case - popup.hide(false, false, false); - } - - if (menuVisible && item.getSubMenu() != null - && visibleChildMenu != item.getSubMenu()) { - showChildMenu(item); - } - } - - /** - * When the mouse is moved away from an item - * - * @param item - */ - public void itemOut(CustomMenuItem item) { - if (visibleChildMenu != item.getSubMenu()) { - hideChildMenu(item); - setSelected(null); - } else if (visibleChildMenu == null) { - setSelected(null); - } - } - - /** - * Used to autoclose submenus when they the menu is in a mode which opens - * root menus on mouse hover. - */ - private static class LazyCloser extends Timer { - static LazyCloser INSTANCE; - private VMenuBar activeRoot; - - @Override - public void run() { - activeRoot.hideChildren(); - activeRoot.setSelected(null); - activeRoot.menuVisible = false; - activeRoot = null; - } - - public static void cancelClosing() { - if (INSTANCE != null) { - INSTANCE.cancel(); - } - } - - public static void prepare(VMenuBar vMenuBar) { - if (INSTANCE == null) { - INSTANCE = new LazyCloser(); - } - if (INSTANCE.activeRoot == vMenuBar) { - INSTANCE.cancel(); - } else if (INSTANCE.activeRoot != null) { - INSTANCE.cancel(); - INSTANCE.run(); - } - INSTANCE.activeRoot = vMenuBar; - } - - public static void schedule() { - if (INSTANCE != null && INSTANCE.activeRoot != null) { - INSTANCE.schedule(750); - } - } - - } - - /** - * Shows the child menu of an item. The caller must ensure that the item has - * a submenu. - * - * @param item - */ - public void showChildMenu(CustomMenuItem item) { - - int left = 0; - int top = 0; - if (subMenu) { - left = item.getParentMenu().getAbsoluteLeft() - + item.getParentMenu().getOffsetWidth(); - top = item.getAbsoluteTop(); - } else { - left = item.getAbsoluteLeft(); - top = item.getParentMenu().getAbsoluteTop() - + item.getParentMenu().getOffsetHeight(); - } - showChildMenuAt(item, top, left); - } - - protected void showChildMenuAt(CustomMenuItem item, int top, int left) { - final int shadowSpace = 10; - - popup = createOverlay(); - popup.setOwner(this); - - /* - * Use parents primary style name if possible and remove the submenu - * prefix if needed - */ - String primaryStyleName = parentMenu != null ? parentMenu - .getStylePrimaryName() : getStylePrimaryName(); - if (subMenu) { - primaryStyleName = primaryStyleName.replace( - SUBMENU_CLASSNAME_PREFIX, ""); - } - popup.setStyleName(primaryStyleName + "-popup"); - - // Setting owner and handlers to support tooltips. Needed for tooltip - // handling of overlay widgets (will direct queries to parent menu) - if (parentMenu == null) { - popup.setOwner(this); - } else { - VMenuBar parent = parentMenu; - while (parent.getParentMenu() != null) { - parent = parent.getParentMenu(); - } - popup.setOwner(parent); - } - if (client != null) { - client.getVTooltip().connectHandlersToWidget(popup); - } - - popup.setWidget(item.getSubMenu()); - popup.addCloseHandler(this); - popup.addAutoHidePartner(item.getElement()); - - // at 0,0 because otherwise IE7 add extra scrollbars (#5547) - popup.setPopupPosition(0, 0); - - item.getSubMenu().onShow(); - visibleChildMenu = item.getSubMenu(); - item.getSubMenu().setParentMenu(this); - - popup.show(); - - if (left + popup.getOffsetWidth() >= RootPanel.getBodyElement() - .getOffsetWidth() - shadowSpace) { - if (subMenu) { - left = item.getParentMenu().getAbsoluteLeft() - - popup.getOffsetWidth() - shadowSpace; - } else { - left = RootPanel.getBodyElement().getOffsetWidth() - - popup.getOffsetWidth() - shadowSpace; - } - // Accommodate space for shadow - if (left < shadowSpace) { - left = shadowSpace; - } - } - - top = adjustPopupHeight(top, shadowSpace); - - popup.setPopupPosition(left, top); - - } - - /** - * Create an overlay for the menu bar. - * - * This method can be overridden to use a custom overlay. - * - * @since 7.6 - * @return overlay to use - */ - protected VOverlay createOverlay() { - return new VOverlay(true, false, true); - } - - private int adjustPopupHeight(int top, final int shadowSpace) { - // Check that the popup will fit the screen - int availableHeight = RootPanel.getBodyElement().getOffsetHeight() - - top - shadowSpace; - int missingHeight = popup.getOffsetHeight() - availableHeight; - if (missingHeight > 0) { - // First move the top of the popup to get more space - // Don't move above top of screen, don't move more than needed - int moveUpBy = Math.min(top - shadowSpace, missingHeight); - - // Update state - top -= moveUpBy; - missingHeight -= moveUpBy; - availableHeight += moveUpBy; - - if (missingHeight > 0) { - int contentWidth = visibleChildMenu.getOffsetWidth(); - - // If there's still not enough room, limit height to fit and add - // a scroll bar - Style style = popup.getElement().getStyle(); - style.setHeight(availableHeight, Unit.PX); - style.setOverflowY(Overflow.SCROLL); - - // Make room for the scroll bar by adjusting the width of the - // popup - style.setWidth( - contentWidth + WidgetUtil.getNativeScrollbarSize(), - Unit.PX); - popup.positionOrSizeUpdated(); - } - } - return top; - } - - /** - * Hides the submenu of an item - * - * @param item - */ - public void hideChildMenu(CustomMenuItem item) { - if (visibleChildMenu != null - && !(visibleChildMenu == item.getSubMenu())) { - popup.hide(); - } - } - - /** - * When the menu is shown. - */ - public void onShow() { - // remove possible previous selection - if (selected != null) { - selected.setSelected(false); - selected = null; - } - menuVisible = true; - } - - /** - * Listener method, fired when this menu is closed - */ - @Override - public void onClose(CloseEvent event) { - hideChildren(); - if (event.isAutoClosed()) { - hideParents(true); - menuVisible = false; - } - visibleChildMenu = null; - popup = null; - } - - /** - * Recursively hide all child menus - */ - public void hideChildren() { - hideChildren(true, true); - } - - /** - * - * Recursively hide all child menus - * - * @param animateIn - * enable/disable animate-in animation when hide popup - * @param animateOut - * enable/disable animate-out animation when hide popup - * @since 7.3.7 - */ - public void hideChildren(boolean animateIn, boolean animateOut) { - if (visibleChildMenu != null) { - visibleChildMenu.hideChildren(animateIn, animateOut); - popup.hide(false, animateIn, animateOut); - } - } - - /** - * Recursively hide all parent menus - */ - public void hideParents(boolean autoClosed) { - if (visibleChildMenu != null) { - popup.hide(); - setSelected(null); - menuVisible = !autoClosed; - } - - if (getParentMenu() != null) { - getParentMenu().hideParents(autoClosed); - } - } - - /** - * Returns the parent menu of this menu, or null if this is the top-level - * menu - * - * @return - */ - public VMenuBar getParentMenu() { - return parentMenu; - } - - /** - * Set the parent menu of this menu - * - * @param parent - */ - public void setParentMenu(VMenuBar parent) { - parentMenu = parent; - } - - /** - * Returns the currently selected item of this menu, or null if nothing is - * selected - * - * @return - */ - public CustomMenuItem getSelected() { - return selected; - } - - /** - * Set the currently selected item of this menu - * - * @param item - */ - public void setSelected(CustomMenuItem item) { - // If we had something selected, unselect - if (item != selected && selected != null) { - selected.setSelected(false); - } - // If we have a valid selection, select it - if (item != null) { - item.setSelected(true); - } - - selected = item; - } - - /** - * - * A class to hold information on menu items - * - */ - public static class CustomMenuItem extends Widget implements HasHTML { - - protected String html = null; - protected Command command = null; - protected VMenuBar subMenu = null; - protected VMenuBar parentMenu = null; - protected boolean enabled = true; - protected boolean isSeparator = false; - protected boolean checkable = false; - protected boolean checked = false; - protected boolean selected = false; - protected String description = null; - - private String styleName; - - /** - * Default menu item {@link Widget} constructor for GWT.create(). - * - * Use {@link #setHTML(String)} and {@link #setCommand(Command)} after - * constructing a menu item. - */ - public CustomMenuItem() { - this("", null); - } - - /** - * Creates a menu item {@link Widget}. - * - * @param html - * @param cmd - * @deprecated use the default constructor and {@link #setHTML(String)} - * and {@link #setCommand(Command)} instead - */ - @Deprecated - public CustomMenuItem(String html, Command cmd) { - // We need spans to allow inline-block in IE - setElement(DOM.createSpan()); - - setHTML(html); - setCommand(cmd); - setSelected(false); - } - - @Override - public void setStyleName(String style) { - super.setStyleName(style); - updateStyleNames(); - - // Pass stylename down to submenus - if (getSubMenu() != null) { - getSubMenu().setStyleName(style); - } - } - - public void setSelected(boolean selected) { - this.selected = selected; - updateStyleNames(); - } - - public void setChecked(boolean checked) { - if (checkable && !isSeparator) { - this.checked = checked; - } else { - this.checked = false; - } - updateStyleNames(); - } - - public boolean isChecked() { - return checked; - } - - public void setCheckable(boolean checkable) { - if (checkable && !isSeparator) { - this.checkable = true; - } else { - setChecked(false); - this.checkable = false; - } - } - - public boolean isCheckable() { - return checkable; - } - - /* - * setters and getters for the fields - */ - - public void setSubMenu(VMenuBar subMenu) { - this.subMenu = subMenu; - } - - public VMenuBar getSubMenu() { - return subMenu; - } - - public void setParentMenu(VMenuBar parentMenu) { - this.parentMenu = parentMenu; - updateStyleNames(); - } - - protected void updateStyleNames() { - if (parentMenu == null) { - // Style names depend on the parent menu's primary style name so - // don't do updates until the item has a parent - return; - } - - String primaryStyleName = parentMenu.getStylePrimaryName(); - if (parentMenu.subMenu) { - primaryStyleName = primaryStyleName.replace( - SUBMENU_CLASSNAME_PREFIX, ""); - } - - String currentStyles = super.getStyleName(); - List customStyles = new ArrayList(); - for (String style : currentStyles.split(" ")) { - if (!style.isEmpty() && !style.startsWith(primaryStyleName)) { - customStyles.add(style); - } - } - - if (isSeparator) { - super.setStyleName(primaryStyleName + "-separator"); - } else { - super.setStyleName(primaryStyleName + "-menuitem"); - } - - for (String customStyle : customStyles) { - super.addStyleName(customStyle); - } - - if (styleName != null) { - addStyleDependentName(styleName); - } - - if (enabled) { - removeStyleDependentName("disabled"); - } else { - addStyleDependentName("disabled"); - } - - if (selected && isSelectable()) { - addStyleDependentName("selected"); - // needed for IE6 to have a single style name to match for an - // element - // TODO Can be optimized now that IE6 is not supported any more - if (checkable) { - if (checked) { - removeStyleDependentName("selected-unchecked"); - addStyleDependentName("selected-checked"); - } else { - removeStyleDependentName("selected-checked"); - addStyleDependentName("selected-unchecked"); - } - } - } else { - removeStyleDependentName("selected"); - // needed for IE6 to have a single style name to match for an - // element - removeStyleDependentName("selected-checked"); - removeStyleDependentName("selected-unchecked"); - } - - if (checkable && !isSeparator) { - if (checked) { - addStyleDependentName("checked"); - removeStyleDependentName("unchecked"); - } else { - addStyleDependentName("unchecked"); - removeStyleDependentName("checked"); - } - } - } - - public VMenuBar getParentMenu() { - return parentMenu; - } - - public void setCommand(Command command) { - this.command = command; - } - - public Command getCommand() { - return command; - } - - @Override - public String getHTML() { - return html; - } - - @Override - public void setHTML(String html) { - this.html = html; - DOM.setInnerHTML(getElement(), html); - - // Sink the onload event for any icons. The onload - // events are handled by the parent VMenuBar. - WidgetUtil.sinkOnloadForImages(getElement()); - } - - @Override - public String getText() { - return html; - } - - @Override - public void setText(String text) { - setHTML(WidgetUtil.escapeHTML(text)); - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - updateStyleNames(); - } - - public boolean isEnabled() { - return enabled; - } - - private void setSeparator(boolean separator) { - isSeparator = separator; - updateStyleNames(); - if (!separator) { - setEnabled(enabled); - } - } - - public boolean isSeparator() { - return isSeparator; - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - setSeparator(uidl.hasAttribute("separator")); - setEnabled(!uidl - .hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DISABLED)); - - if (!isSeparator() - && uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_CHECKED)) { - // if the selected attribute is present (either true or false), - // the item is selectable - setCheckable(true); - setChecked(uidl - .getBooleanAttribute(MenuBarConstants.ATTRIBUTE_CHECKED)); - } else { - setCheckable(false); - } - - if (uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_STYLE)) { - styleName = uidl - .getStringAttribute(MenuBarConstants.ATTRIBUTE_ITEM_STYLE); - } - - if (uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DESCRIPTION)) { - description = uidl - .getStringAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DESCRIPTION); - } - - updateStyleNames(); - } - - public TooltipInfo getTooltip() { - if (description == null) { - return null; - } - - return new TooltipInfo(description, null, this); - } - - /** - * Checks if the item can be selected. - * - * @return true if it is possible to select this item, false otherwise - */ - public boolean isSelectable() { - return !isSeparator() && isEnabled(); - } - - } - - /** - * @author Jouni Koivuviita / Vaadin Ltd. - */ - public void iLayout() { - iLayout(false); - updateSize(); - } - - public void iLayout(boolean iconLoadEvent) { - // Only collapse if there is more than one item in the root menu and the - // menu has an explicit size - if ((getItems().size() > 1 || (collapsedRootItems != null && collapsedRootItems - .getItems().size() > 0)) - && getElement().getStyle().getProperty("width") != null - && moreItem != null) { - - // Measure the width of the "more" item - final boolean morePresent = getItems().contains(moreItem); - addItem(moreItem); - final int moreItemWidth = moreItem.getOffsetWidth(); - if (!morePresent) { - removeItem(moreItem); - } - - int availableWidth = LayoutManager.get(client).getInnerWidth( - getElement()); - - // Used width includes the "more" item if present - int usedWidth = getConsumedWidth(); - int diff = availableWidth - usedWidth; - removeItem(moreItem); - - if (diff < 0) { - // Too many items: collapse last items from root menu - int widthNeeded = usedWidth - availableWidth; - if (!morePresent) { - widthNeeded += moreItemWidth; - } - int widthReduced = 0; - - while (widthReduced < widthNeeded && getItems().size() > 0) { - // Move last root menu item to collapsed menu - CustomMenuItem collapse = getItems().get( - getItems().size() - 1); - widthReduced += collapse.getOffsetWidth(); - removeItem(collapse); - collapsedRootItems.addItem(collapse, 0); - } - } else if (collapsedRootItems.getItems().size() > 0) { - // Space available for items: expand first items from collapsed - // menu - int widthAvailable = diff + moreItemWidth; - int widthGrowth = 0; - - while (widthAvailable > widthGrowth - && collapsedRootItems.getItems().size() > 0) { - // Move first item from collapsed menu to the root menu - CustomMenuItem expand = collapsedRootItems.getItems() - .get(0); - collapsedRootItems.removeItem(expand); - addItem(expand); - widthGrowth += expand.getOffsetWidth(); - if (collapsedRootItems.getItems().size() > 0) { - widthAvailable -= moreItemWidth; - } - if (widthGrowth > widthAvailable) { - removeItem(expand); - collapsedRootItems.addItem(expand, 0); - } else { - widthAvailable = diff + moreItemWidth; - } - } - } - if (collapsedRootItems.getItems().size() > 0) { - addItem(moreItem); - } - } - - // If a popup is open we might need to adjust the shadow as well if an - // icon shown in that popup was loaded - if (popup != null) { - // Forces a recalculation of the shadow size - popup.show(); - } - if (iconLoadEvent) { - // Size have changed if the width is undefined - Util.notifyParentOfSizeChange(this, false); - } - } - - private int getConsumedWidth() { - int w = 0; - for (CustomMenuItem item : getItems()) { - if (!collapsedRootItems.getItems().contains(item)) { - w += item.getOffsetWidth(); - } - } - return w; - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google - * .gwt.event.dom.client.KeyPressEvent) - */ - @Override - public void onKeyPress(KeyPressEvent event) { - // A bug fix for #14041 - // getKeyCode and getCharCode return different values for different - // browsers - int keyCode = event.getNativeEvent().getKeyCode(); - if (keyCode == 0) { - keyCode = event.getNativeEvent().getCharCode(); - } - if (handleNavigation(keyCode, - event.isControlKeyDown() || event.isMetaKeyDown(), - event.isShiftKeyDown())) { - event.preventDefault(); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt - * .event.dom.client.KeyDownEvent) - */ - @Override - public void onKeyDown(KeyDownEvent event) { - // A bug fix for #14041 - // getKeyCode and getCharCode return different values for different - // browsers - int keyCode = event.getNativeEvent().getKeyCode(); - if (keyCode == 0) { - keyCode = event.getNativeEvent().getCharCode(); - } - if (handleNavigation(keyCode, - event.isControlKeyDown() || event.isMetaKeyDown(), - event.isShiftKeyDown())) { - event.preventDefault(); - } - } - - /** - * Get the key that moves the selection upwards. By default it is the up - * arrow key but by overriding this you can change the key to whatever you - * want. - * - * @return The keycode of the key - */ - protected int getNavigationUpKey() { - return KeyCodes.KEY_UP; - } - - /** - * Get the key that moves the selection downwards. By default it is the down - * arrow key but by overriding this you can change the key to whatever you - * want. - * - * @return The keycode of the key - */ - protected int getNavigationDownKey() { - return KeyCodes.KEY_DOWN; - } - - /** - * Get the key that moves the selection left. By default it is the left - * arrow key but by overriding this you can change the key to whatever you - * want. - * - * @return The keycode of the key - */ - protected int getNavigationLeftKey() { - return KeyCodes.KEY_LEFT; - } - - /** - * Get the key that moves the selection right. By default it is the right - * arrow key but by overriding this you can change the key to whatever you - * want. - * - * @return The keycode of the key - */ - protected int getNavigationRightKey() { - return KeyCodes.KEY_RIGHT; - } - - /** - * Get the key that selects a menu item. By default it is the Enter key but - * by overriding this you can change the key to whatever you want. - * - * @deprecated use {@link #isNavigationSelectKey(int)} instead - * @return - */ - @Deprecated - protected int getNavigationSelectKey() { - return KeyCodes.KEY_ENTER; - } - - /** - * Checks whether key code selects a menu item. By default it is the Enter - * and Space keys but by overriding this you can change the keys to whatever - * you want. - * - * @since 7.2 - * @param keycode - * @return true if key selects menu item - */ - protected boolean isNavigationSelectKey(int keycode) { - return keycode == getNavigationSelectKey() - || keycode == KeyCodes.KEY_SPACE; - } - - /** - * Get the key that closes the menu. By default it is the escape key but by - * overriding this yoy can change the key to whatever you want. - * - * @return - */ - protected int getCloseMenuKey() { - return KeyCodes.KEY_ESCAPE; - } - - /** - * Handles the keyboard events handled by the MenuBar - * - * @param event - * The keyboard event received - * @return true iff the navigation event was handled - */ - public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { - - // If tab or shift+tab close menus - if (keycode == KeyCodes.KEY_TAB) { - setSelected(null); - hideChildren(); - menuVisible = false; - return false; - } - - if (ctrl || shift || !isEnabled()) { - // Do not handle tab key, nor ctrl keys - return false; - } - - if (keycode == getNavigationLeftKey()) { - if (getSelected() == null) { - // If nothing is selected then select the last item - setSelected(items.get(items.size() - 1)); - if (!getSelected().isSelectable()) { - handleNavigation(keycode, ctrl, shift); - } - } else if (visibleChildMenu == null && getParentMenu() == null) { - // If this is the root menu then move to the left - int idx = items.indexOf(getSelected()); - if (idx > 0) { - setSelected(items.get(idx - 1)); - } else { - setSelected(items.get(items.size() - 1)); - } - - if (!getSelected().isSelectable()) { - handleNavigation(keycode, ctrl, shift); - } - } else if (visibleChildMenu != null) { - // Redirect all navigation to the submenu - visibleChildMenu.handleNavigation(keycode, ctrl, shift); - - } else if (getParentMenu().getParentMenu() == null) { - // Inside a sub menu, whose parent is a root menu item - VMenuBar root = getParentMenu(); - - root.getSelected().getSubMenu().setSelected(null); - // #15255 - disable animate-in/out when hide popup - root.hideChildren(false, false); - - // Get the root menus items and select the previous one - int idx = root.getItems().indexOf(root.getSelected()); - idx = idx > 0 ? idx : root.getItems().size(); - CustomMenuItem selected = root.getItems().get(--idx); - - while (selected.isSeparator() || !selected.isEnabled()) { - idx = idx > 0 ? idx : root.getItems().size(); - selected = root.getItems().get(--idx); - } - - root.setSelected(selected); - openMenuAndFocusFirstIfPossible(selected); - } else { - getParentMenu().getSelected().getSubMenu().setSelected(null); - getParentMenu().hideChildren(); - } - - return true; - - } else if (keycode == getNavigationRightKey()) { - - if (getSelected() == null) { - // If nothing is selected then select the first item - setSelected(items.get(0)); - if (!getSelected().isSelectable()) { - handleNavigation(keycode, ctrl, shift); - } - } else if (visibleChildMenu == null && getParentMenu() == null) { - // If this is the root menu then move to the right - int idx = items.indexOf(getSelected()); - - if (idx < items.size() - 1) { - setSelected(items.get(idx + 1)); - } else { - setSelected(items.get(0)); - } - - if (!getSelected().isSelectable()) { - handleNavigation(keycode, ctrl, shift); - } - } else if (visibleChildMenu == null - && getSelected().getSubMenu() != null) { - // If the item has a submenu then show it and move the selection - // there - showChildMenu(getSelected()); - menuVisible = true; - visibleChildMenu.handleNavigation(keycode, ctrl, shift); - } else if (visibleChildMenu == null) { - - // Get the root menu - VMenuBar root = getParentMenu(); - while (root.getParentMenu() != null) { - root = root.getParentMenu(); - } - - // Hide the submenu (#15255 - disable animate-in/out when hide - // popup) - root.hideChildren(false, false); - - // Get the root menus items and select the next one - int idx = root.getItems().indexOf(root.getSelected()); - idx = idx < root.getItems().size() - 1 ? idx : -1; - CustomMenuItem selected = root.getItems().get(++idx); - - while (selected.isSeparator() || !selected.isEnabled()) { - idx = idx < root.getItems().size() - 1 ? idx : -1; - selected = root.getItems().get(++idx); - } - - root.setSelected(selected); - openMenuAndFocusFirstIfPossible(selected); - } else if (visibleChildMenu != null) { - // Redirect all navigation to the submenu - visibleChildMenu.handleNavigation(keycode, ctrl, shift); - } - - return true; - - } else if (keycode == getNavigationUpKey()) { - - if (getSelected() == null) { - // If nothing is selected then select the last item - setSelected(items.get(items.size() - 1)); - if (!getSelected().isSelectable()) { - handleNavigation(keycode, ctrl, shift); - } - } else if (visibleChildMenu != null) { - // Redirect all navigation to the submenu - visibleChildMenu.handleNavigation(keycode, ctrl, shift); - } else { - // Select the previous item if possible or loop to the last item - int idx = items.indexOf(getSelected()); - if (idx > 0) { - setSelected(items.get(idx - 1)); - } else { - setSelected(items.get(items.size() - 1)); - } - - if (!getSelected().isSelectable()) { - handleNavigation(keycode, ctrl, shift); - } - } - - return true; - - } else if (keycode == getNavigationDownKey()) { - - if (getSelected() == null) { - // If nothing is selected then select the first item - selectFirstItem(); - } else if (visibleChildMenu == null && getParentMenu() == null) { - // If this is the root menu the show the child menu with arrow - // down, if there is a child menu - openMenuAndFocusFirstIfPossible(getSelected()); - } else if (visibleChildMenu != null) { - // Redirect all navigation to the submenu - visibleChildMenu.handleNavigation(keycode, ctrl, shift); - } else { - // Select the next item if possible or loop to the first item - int idx = items.indexOf(getSelected()); - if (idx < items.size() - 1) { - setSelected(items.get(idx + 1)); - } else { - setSelected(items.get(0)); - } - - if (!getSelected().isSelectable()) { - handleNavigation(keycode, ctrl, shift); - } - } - return true; - - } else if (keycode == getCloseMenuKey()) { - setSelected(null); - hideChildren(); - menuVisible = false; - - } else if (isNavigationSelectKey(keycode)) { - if (getSelected() == null) { - // If nothing is selected then select the first item - selectFirstItem(); - } else if (visibleChildMenu != null) { - // Redirect all navigation to the submenu - visibleChildMenu.handleNavigation(keycode, ctrl, shift); - menuVisible = false; - } else if (visibleChildMenu == null - && getSelected().getSubMenu() != null) { - // If the item has a sub menu then show it and move the - // selection there - openMenuAndFocusFirstIfPossible(getSelected()); - } else { - final Command command = getSelected().getCommand(); - - setSelected(null); - hideParents(true); - - // #17076 keyboard selected menuitem without children: do - // not leave menu to visible ("hover open") mode - menuVisible = false; - - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - @Override - public void execute() { - if (command != null) { - command.execute(); - } - } - }); - } - } - - return false; - } - - private void selectFirstItem() { - for (int i = 0; i < items.size(); i++) { - CustomMenuItem item = items.get(i); - if (item.isSelectable()) { - setSelected(item); - break; - } - } - } - - private void openMenuAndFocusFirstIfPossible(CustomMenuItem menuItem) { - VMenuBar subMenu = menuItem.getSubMenu(); - if (subMenu == null) { - // No child menu? Nothing to do - return; - } - - VMenuBar parentMenu = menuItem.getParentMenu(); - parentMenu.showChildMenu(menuItem); - - menuVisible = true; - // Select the first item in the newly open submenu - subMenu.selectFirstItem(); - - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event - * .dom.client.FocusEvent) - */ - @Override - public void onFocus(FocusEvent event) { - - } - - private final String SUBPART_PREFIX = "item"; - - @Override - public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - if (subPart.startsWith(SUBPART_PREFIX)) { - int index = Integer.parseInt(subPart.substring(SUBPART_PREFIX - .length())); - CustomMenuItem item = getItems().get(index); - - return item.getElement(); - } else { - Queue submenuItems = new LinkedList(); - for (CustomMenuItem item : getItems()) { - if (isItemNamed(item, subPart)) { - return item.getElement(); - } - if (item.getSubMenu() != null) { - submenuItems.addAll(item.getSubMenu().getItems()); - } - } - while (!submenuItems.isEmpty()) { - CustomMenuItem item = submenuItems.poll(); - if (!item.isSeparator() && isItemNamed(item, subPart)) { - return item.getElement(); - } - if (item.getSubMenu() != null && item.getSubMenu().menuVisible) { - submenuItems.addAll(item.getSubMenu().getItems()); - } - - } - return null; - } - } - - private boolean isItemNamed(CustomMenuItem item, String name) { - Element lastChildElement = getLastChildElement(item); - if (getText(lastChildElement).equals(name)) { - return true; - } - return false; - } - - /* - * Returns the text content of element without including the text of - * possible nested elements. It is assumed that the last child of element - * contains the text of interest and that the last child does not itself - * have children with text content. This method is used by - * getSubPartElement(String) so that possible text icons are not included in - * the textual matching (#14879). - */ - private native String getText(Element element) - /*-{ - var n = element.childNodes.length; - if(n > 0){ - return element.childNodes[n - 1].nodeValue; - } - else{ - return ""; - } - }-*/; - - private Element getLastChildElement(CustomMenuItem item) { - Element lastChildElement = item.getElement().getFirstChildElement(); - while (lastChildElement.getNextSiblingElement() != null) { - lastChildElement = lastChildElement.getNextSiblingElement(); - } - return lastChildElement; - } - - @Override - public String getSubPartName(com.google.gwt.user.client.Element subElement) { - if (!getElement().isOrHasChild(subElement)) { - return null; - } - - Element menuItemRoot = subElement; - while (menuItemRoot != null && menuItemRoot.getParentElement() != null - && menuItemRoot.getParentElement() != getElement()) { - menuItemRoot = menuItemRoot.getParentElement().cast(); - } - // "menuItemRoot" is now the root of the menu item - - final int itemCount = getItems().size(); - for (int i = 0; i < itemCount; i++) { - if (getItems().get(i).getElement() == menuItemRoot) { - String name = SUBPART_PREFIX + i; - return name; - } - } - return null; - } - - /** - * Get menu item with given DOM element - * - * @param element - * Element used in search - * @return Menu item or null if not found - * @deprecated As of 7.2, call or override - * {@link #getMenuItemWithElement(Element)} instead - */ - @Deprecated - public CustomMenuItem getMenuItemWithElement( - com.google.gwt.user.client.Element element) { - for (int i = 0; i < items.size(); i++) { - CustomMenuItem item = items.get(i); - if (DOM.isOrHasChild(item.getElement(), element)) { - return item; - } - - if (item.getSubMenu() != null) { - item = item.getSubMenu().getMenuItemWithElement(element); - if (item != null) { - return item; - } - } - } - - return null; - } - - /** - * Get menu item with given DOM element - * - * @param element - * Element used in search - * @return Menu item or null if not found - * - * @since 7.2 - */ - public CustomMenuItem getMenuItemWithElement(Element element) { - return getMenuItemWithElement(DOM.asOld(element)); - } -} diff --git a/client/src/com/vaadin/client/ui/VNativeButton.java b/client/src/com/vaadin/client/ui/VNativeButton.java deleted file mode 100644 index 77b2515f45..0000000000 --- a/client/src/com/vaadin/client/ui/VNativeButton.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.Button; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.MouseEventDetailsBuilder; -import com.vaadin.client.StyleConstants; -import com.vaadin.client.Util; -import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.ui.button.ButtonServerRpc; - -public class VNativeButton extends Button implements ClickHandler { - - public static final String CLASSNAME = "v-nativebutton"; - - /** For internal use only. May be removed or replaced in the future. */ - public String paintableId; - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - public ButtonServerRpc buttonRpcProxy; - - /** For internal use only. May be removed or replaced in the future. */ - public Element errorIndicatorElement; - - /** For internal use only. May be removed or replaced in the future. */ - public final Element captionElement = DOM.createSpan(); - - /** For internal use only. May be removed or replaced in the future. */ - public Icon icon; - - /** - * Helper flag to handle special-case where the button is moved from under - * mouse while clicking it. In this case mouse leaves the button without - * moving. - */ - private boolean clickPending; - - private boolean cancelNextClick = false; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean disableOnClick = false; - - public VNativeButton() { - setStyleName(CLASSNAME); - - getElement().appendChild(captionElement); - captionElement.setClassName(getStyleName() + "-caption"); - - addClickHandler(this); - - sinkEvents(Event.ONMOUSEDOWN | Event.ONLOAD | Event.ONMOUSEMOVE - | Event.ONFOCUS); - } - - @Override - public void setText(String text) { - captionElement.setInnerText(text); - } - - @Override - public void setHTML(String html) { - captionElement.setInnerHTML(html); - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - - if (DOM.eventGetType(event) == Event.ONLOAD) { - Util.notifyParentOfSizeChange(this, true); - - } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN - && event.getButton() == Event.BUTTON_LEFT) { - clickPending = true; - - } else if (DOM.eventGetType(event) == Event.ONMOUSEMOVE) { - clickPending = false; - } else if (DOM.eventGetType(event) == Event.ONMOUSEOUT) { - if (clickPending) { - click(); - } - clickPending = false; - } else if (event.getTypeInt() == Event.ONFOCUS) { - if (BrowserInfo.get().isIE() - && BrowserInfo.get().getBrowserMajorVersion() < 11 - && clickPending) { - /* - * The focus event will mess up IE and IE will not trigger the - * mouse up event (which in turn triggers the click event) until - * the mouse is moved. This will result in it appearing as a - * native button not triggering the event. So we manually - * trigger the click here and cancel the next original event - * which will occur on the next mouse move. See ticket #11094 - * for details. - */ - click(); - clickPending = false; - cancelNextClick = true; - } - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event - * .dom.client.ClickEvent) - */ - @Override - public void onClick(ClickEvent event) { - if (paintableId == null || client == null || cancelNextClick) { - cancelNextClick = false; - return; - } - - if (BrowserInfo.get().isWebkit()) { - // Webkit does not focus non-text input elements on click - // (#11854) - setFocus(true); - } - if (disableOnClick) { - setEnabled(false); - // FIXME: This should be moved to NativeButtonConnector along with - // buttonRpcProxy - addStyleName(StyleConstants.DISABLED); - buttonRpcProxy.disableOnClick(); - } - - // Add mouse details - MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(event.getNativeEvent(), getElement()); - buttonRpcProxy.click(details); - - clickPending = false; - } - -} diff --git a/client/src/com/vaadin/client/ui/VNativeSelect.java b/client/src/com/vaadin/client/ui/VNativeSelect.java deleted file mode 100644 index 330442b550..0000000000 --- a/client/src/com/vaadin/client/ui/VNativeSelect.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.ArrayList; -import java.util.Iterator; - -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.user.client.ui.ListBox; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.UIDL; - -public class VNativeSelect extends VOptionGroupBase implements Field { - - public static final String CLASSNAME = "v-select"; - - protected ListBox select; - - private boolean firstValueIsTemporaryNullItem = false; - - public VNativeSelect() { - super(new ListBox(false), CLASSNAME); - select = getOptionsContainer(); - select.setVisibleItemCount(1); - select.addChangeHandler(this); - select.setStyleName(CLASSNAME + "-select"); - - updateEnabledState(); - } - - protected ListBox getOptionsContainer() { - return (ListBox) optionsContainer; - } - - @Override - public void buildOptions(UIDL uidl) { - select.clear(); - firstValueIsTemporaryNullItem = false; - - if (isNullSelectionAllowed() && !isNullSelectionItemAvailable()) { - // can't unselect last item in singleselect mode - select.addItem("", (String) null); - } - boolean selected = false; - for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { - final UIDL optionUidl = (UIDL) i.next(); - select.addItem(optionUidl.getStringAttribute("caption"), - optionUidl.getStringAttribute("key")); - if (optionUidl.hasAttribute("selected")) { - select.setItemSelected(select.getItemCount() - 1, true); - selected = true; - } - } - if (!selected && !isNullSelectionAllowed()) { - // null-select not allowed, but value not selected yet; add null and - // remove when something is selected - select.insertItem("", (String) null, 0); - select.setItemSelected(0, true); - firstValueIsTemporaryNullItem = true; - } - } - - @Override - protected String[] getSelectedItems() { - final ArrayList selectedItemKeys = new ArrayList(); - for (int i = 0; i < select.getItemCount(); i++) { - if (select.isItemSelected(i)) { - selectedItemKeys.add(select.getValue(i)); - } - } - return selectedItemKeys.toArray(new String[selectedItemKeys.size()]); - } - - @Override - public void onChange(ChangeEvent event) { - - if (select.isMultipleSelect()) { - client.updateVariable(paintableId, "selected", getSelectedItems(), - isImmediate()); - } else { - client.updateVariable(paintableId, "selected", new String[] { "" - + getSelectedItem() }, isImmediate()); - } - if (firstValueIsTemporaryNullItem) { - // remove temporary empty item - select.removeItem(0); - firstValueIsTemporaryNullItem = false; - /* - * Workaround to achrome bug that may cause value change event not - * to fire when selection is done with keyboard. - * - * http://dev.vaadin.com/ticket/10109 - * - * Problem is confirmed to exist only on Chrome-Win, but just - * execute in for all webkits. Probably exists also in other - * webkits/blinks on windows. - */ - if (BrowserInfo.get().isWebkit()) { - select.getElement().blur(); - select.getElement().focus(); - } - - } - } - - @Override - public void setHeight(String height) { - select.setHeight(height); - super.setHeight(height); - } - - @Override - public void setWidth(String width) { - select.setWidth(width); - super.setWidth(width); - } - - @Override - public void setTabIndex(int tabIndex) { - getOptionsContainer().setTabIndex(tabIndex); - } - - @Override - protected void updateEnabledState() { - select.setEnabled(isEnabled() && !isReadonly()); - } - - @Override - public void focus() { - select.setFocus(true); - } - - /** - * @return the root select widget - */ - public ListBox getSelect() { - return getOptionsContainer(); - } - -} diff --git a/client/src/com/vaadin/client/ui/VNotification.java b/client/src/com/vaadin/client/ui/VNotification.java deleted file mode 100644 index 1ff51df8d0..0000000000 --- a/client/src/com/vaadin/client/ui/VNotification.java +++ /dev/null @@ -1,683 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.ArrayList; -import java.util.EventObject; -import java.util.Iterator; - -import com.google.gwt.aria.client.Roles; -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Event.NativePreviewEvent; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.AnimationUtil; -import com.vaadin.client.AnimationUtil.AnimationEndListener; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.UIDL; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.ui.aria.AriaHelper; -import com.vaadin.shared.Position; -import com.vaadin.shared.ui.ui.NotificationRole; -import com.vaadin.shared.ui.ui.UIConstants; -import com.vaadin.shared.ui.ui.UIState.NotificationTypeConfiguration; - -public class VNotification extends VOverlay { - - public static final Position CENTERED = Position.MIDDLE_CENTER; - public static final Position CENTERED_TOP = Position.TOP_CENTER; - public static final Position CENTERED_BOTTOM = Position.BOTTOM_CENTER; - public static final Position TOP_LEFT = Position.TOP_LEFT; - public static final Position TOP_RIGHT = Position.TOP_RIGHT; - public static final Position BOTTOM_LEFT = Position.BOTTOM_LEFT; - public static final Position BOTTOM_RIGHT = Position.BOTTOM_RIGHT; - - private static final String STYLENAME_POSITION_TOP = "v-position-top"; - private static final String STYLENAME_POSITION_RIGHT = "v-position-right"; - private static final String STYLENAME_POSITION_BOTTOM = "v-position-bottom"; - private static final String STYLENAME_POSITION_LEFT = "v-position-left"; - private static final String STYLENAME_POSITION_MIDDLE = "v-position-middle"; - private static final String STYLENAME_POSITION_CENTER = "v-position-center"; - private static final String STYLENAME_POSITION_ASSISTIVE = "v-position-assistive"; - - public static final String CAPTION = "caption"; - public static final String DESCRIPTION = "description"; - public static final String DETAILS = "details"; - - /** - * Position that is only accessible for assistive devices, invisible for - * visual users. - */ - public static final Position ASSISTIVE = Position.ASSISTIVE; - - public static final int DELAY_FOREVER = -1; - public static final int DELAY_NONE = 0; - - private static final String STYLENAME = "v-Notification"; - private static final int mouseMoveThreshold = 7; - private static final int Z_INDEX_BASE = 20000; - public static final String STYLE_SYSTEM = "system"; - - private static final ArrayList notifications = new ArrayList(); - - private boolean infiniteDelay = false; - private int hideDelay = 0; - - private Timer delay; - - private int x = -1; - private int y = -1; - - private String temporaryStyle; - - private ArrayList listeners; - private static final int TOUCH_DEVICE_IDLE_DELAY = 1000; - - /** - * Default constructor. You should use GWT.create instead. - */ - public VNotification() { - setStyleName(STYLENAME); - sinkEvents(Event.ONCLICK); - getElement().getStyle().setZIndex(Z_INDEX_BASE); - } - - /** - * @deprecated Use static {@link #createNotification(int)} instead to enable - * GWT deferred binding. - * - * @param delayMsec - */ - @Deprecated - public VNotification(int delayMsec) { - this(); - setDelay(delayMsec); - - if (BrowserInfo.get().isTouchDevice()) { - new Timer() { - @Override - public void run() { - if (isAttached()) { - hide(); - } - } - }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY); - } - } - - /** - * @deprecated Use static {@link #createNotification(int, int, int)} instead - * to enable GWT deferred binding. - * - * @param delayMsec - * @param fadeMsec - * @param startOpacity - */ - @Deprecated - public VNotification(int delayMsec, int fadeMsec, int startOpacity) { - this(delayMsec); - AnimationUtil.setAnimationDuration(getElement(), fadeMsec + "ms"); - getElement().getStyle().setOpacity(startOpacity / 100); - } - - private void setDelay(int delayMsec) { - if (delayMsec < 0) { - infiniteDelay = true; - hideDelay = 0; - } else { - infiniteDelay = false; - hideDelay = delayMsec; - } - } - - @Override - public void show() { - show(CENTERED); - } - - public void show(String style) { - show(CENTERED, style); - } - - public void show(com.vaadin.shared.Position position) { - show(position, null); - } - - public void show(Widget widget, Position position, String style) { - NotificationTypeConfiguration styleSetup = getUiState(style); - setWaiAriaRole(styleSetup); - - FlowPanel panel = new FlowPanel(); - if (hasPrefix(styleSetup)) { - panel.add(new Label(styleSetup.prefix)); - AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(), - true); - } - - panel.add(widget); - - if (hasPostfix(styleSetup)) { - panel.add(new Label(styleSetup.postfix)); - AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(), - true); - } - setWidget(panel); - show(position, style); - } - - private boolean hasPostfix(NotificationTypeConfiguration styleSetup) { - return styleSetup != null && styleSetup.postfix != null - && !styleSetup.postfix.isEmpty(); - } - - private boolean hasPrefix(NotificationTypeConfiguration styleSetup) { - return styleSetup != null && styleSetup.prefix != null - && !styleSetup.prefix.isEmpty(); - } - - public void show(String html, Position position, String style) { - NotificationTypeConfiguration styleSetup = getUiState(style); - String assistiveDeviceOnlyStyle = AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE; - - setWaiAriaRole(styleSetup); - - String type = ""; - String usage = ""; - - if (hasPrefix(styleSetup)) { - type = "" - + styleSetup.prefix + ""; - } - - if (hasPostfix(styleSetup)) { - usage = "" - + styleSetup.postfix + ""; - } - - setWidget(new HTML(type + html + usage)); - show(position, style); - } - - private NotificationTypeConfiguration getUiState(String style) { - if (getApplicationConnection() == null - || getApplicationConnection().getUIConnector() == null) { - return null; - } - - return getApplicationConnection().getUIConnector().getState().notificationConfigurations - .get(style); - } - - private void setWaiAriaRole(NotificationTypeConfiguration styleSetup) { - Roles.getAlertRole().set(getElement()); - - if (styleSetup != null && styleSetup.notificationRole != null) { - if (NotificationRole.STATUS == styleSetup.notificationRole) { - Roles.getStatusRole().set(getElement()); - } - } - } - - public void show(Position position, String style) { - if (temporaryStyle != null) { - removeStyleName(temporaryStyle); - removeStyleDependentName(temporaryStyle); - temporaryStyle = null; - } - if (style != null && style.length() > 0) { - temporaryStyle = style; - addStyleName(style); - addStyleDependentName(style); - } - - setPosition(position); - super.show(); - updatePositionOffsets(position); - notifications.add(this); - positionOrSizeUpdated(); - /** - * Android 4 fails to render notifications correctly without a little - * nudge (#8551) Chrome 41 now requires this too (#17252) - */ - if (BrowserInfo.get().isAndroid() || isChrome41OrHigher()) { - WidgetUtil.setStyleTemporarily(getElement(), "display", "none"); - } - } - - private boolean isChrome41OrHigher() { - return BrowserInfo.get().isChrome() - && BrowserInfo.get().getBrowserMajorVersion() >= 41; - } - - protected void hideAfterDelay() { - if (delay == null) { - delay = new Timer() { - @Override - public void run() { - VNotification.super.hide(); - } - }; - delay.schedule(hideDelay); - } - } - - @Override - public void hide() { - if (delay != null) { - delay.cancel(); - } - // Run only once - if (notifications.contains(this)) { - DOM.removeEventPreview(this); - - // Still animating in, wait for it to finish before touching - // the animation delay (which would restart the animation-in - // in some browsers) - if (getStyleName().contains( - VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN)) { - AnimationUtil.addAnimationEndListener(getElement(), - new AnimationEndListener() { - @Override - public void onAnimationEnd(NativeEvent event) { - if (AnimationUtil - .getAnimationName(event) - .contains( - VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN)) { - VNotification.this.hide(); - } - } - }); - } else { - VNotification.super.hide(); - fireEvent(new HideEvent(this)); - notifications.remove(this); - } - } - } - - private void updatePositionOffsets(com.vaadin.shared.Position position) { - final Element el = getElement(); - - // Remove all offsets (GWT PopupPanel defaults) - el.getStyle().clearTop(); - el.getStyle().clearLeft(); - - switch (position) { - case MIDDLE_LEFT: - case MIDDLE_RIGHT: - center(); - el.getStyle().clearLeft(); - break; - case TOP_CENTER: - case BOTTOM_CENTER: - center(); - el.getStyle().clearTop(); - break; - case MIDDLE_CENTER: - center(); - break; - } - } - - public void setPosition(com.vaadin.shared.Position position) { - final Element el = getElement(); - - // Remove any previous positions - el.removeClassName(STYLENAME_POSITION_TOP); - el.removeClassName(STYLENAME_POSITION_RIGHT); - el.removeClassName(STYLENAME_POSITION_BOTTOM); - el.removeClassName(STYLENAME_POSITION_LEFT); - el.removeClassName(STYLENAME_POSITION_MIDDLE); - el.removeClassName(STYLENAME_POSITION_CENTER); - el.removeClassName(STYLENAME_POSITION_ASSISTIVE); - - switch (position) { - case TOP_LEFT: - el.addClassName(STYLENAME_POSITION_TOP); - el.addClassName(STYLENAME_POSITION_LEFT); - break; - case TOP_RIGHT: - el.addClassName(STYLENAME_POSITION_TOP); - el.addClassName(STYLENAME_POSITION_RIGHT); - break; - case MIDDLE_LEFT: - el.addClassName(STYLENAME_POSITION_MIDDLE); - el.addClassName(STYLENAME_POSITION_LEFT); - break; - case MIDDLE_RIGHT: - el.addClassName(STYLENAME_POSITION_MIDDLE); - el.addClassName(STYLENAME_POSITION_RIGHT); - break; - case BOTTOM_RIGHT: - el.addClassName(STYLENAME_POSITION_BOTTOM); - el.addClassName(STYLENAME_POSITION_RIGHT); - break; - case BOTTOM_LEFT: - el.addClassName(STYLENAME_POSITION_BOTTOM); - el.addClassName(STYLENAME_POSITION_LEFT); - break; - case TOP_CENTER: - el.addClassName(STYLENAME_POSITION_TOP); - el.addClassName(STYLENAME_POSITION_CENTER); - break; - case BOTTOM_CENTER: - el.addClassName(STYLENAME_POSITION_BOTTOM); - el.addClassName(STYLENAME_POSITION_CENTER); - break; - case ASSISTIVE: - el.addClassName(STYLENAME_POSITION_ASSISTIVE); - break; - } - } - - @Override - public void onBrowserEvent(Event event) { - hide(); - } - - @Override - /* - * Fix for #14689: {@link #onEventPreview(Event)} method is deprecated and - * it's called now only for the very first handler (see super impl). We need - * it to work for any handler. So let's call old {@link - * #onEventPreview(Event)} method explicitly with updated logic for {@link - * #onPreviewNativeEvent(Event)}. - */ - protected void onPreviewNativeEvent(NativePreviewEvent event) { - if (!onEventPreview(Event.as(event.getNativeEvent()))) { - event.cancel(); - } - } - - @Override - public boolean onEventPreview(Event event) { - int type = DOM.eventGetType(event); - // "modal" - if (infiniteDelay || temporaryStyle == STYLE_SYSTEM) { - if (type == Event.ONCLICK || type == Event.ONTOUCHEND) { - if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) { - hide(); - return false; - } - } else if (type == Event.ONKEYDOWN - && event.getKeyCode() == KeyCodes.KEY_ESCAPE) { - hide(); - return false; - } - if (temporaryStyle == STYLE_SYSTEM) { - return true; - } else { - return false; - } - } - // default - switch (type) { - case Event.ONMOUSEMOVE: - if (x < 0) { - x = DOM.eventGetClientX(event); - y = DOM.eventGetClientY(event); - } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold - || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) { - hideAfterDelay(); - } - break; - case Event.ONMOUSEDOWN: - case Event.ONMOUSEWHEEL: - case Event.ONSCROLL: - hideAfterDelay(); - break; - case Event.ONKEYDOWN: - if (event.getRepeat()) { - return true; - } - hideAfterDelay(); - break; - default: - break; - } - return true; - } - - public void addEventListener(EventListener listener) { - if (listeners == null) { - listeners = new ArrayList(); - } - listeners.add(listener); - } - - public void removeEventListener(EventListener listener) { - if (listeners == null) { - return; - } - listeners.remove(listener); - } - - private void fireEvent(HideEvent event) { - if (listeners != null) { - for (Iterator it = listeners.iterator(); it - .hasNext();) { - EventListener l = it.next(); - l.notificationHidden(event); - } - } - } - - public static void showNotification(ApplicationConnection client, - final UIDL notification) { - boolean onlyPlainText = notification - .hasAttribute(UIConstants.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED); - String html = ""; - if (notification.hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON)) { - String iconUri = notification - .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON); - html += client.getIcon(iconUri).getElement().getString(); - } - if (notification - .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION)) { - String caption = notification - .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION); - if (onlyPlainText) { - caption = WidgetUtil.escapeHTML(caption); - caption = caption.replaceAll("\\n", "
"); - } - html += "

" - + caption + "

"; - } - if (notification - .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE)) { - String message = notification - .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE); - if (onlyPlainText) { - message = WidgetUtil.escapeHTML(message); - message = message.replaceAll("\\n", "
"); - } - html += "

" + message + "

"; - } - - final String style = notification - .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE) ? notification - .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE) - : null; - - final int pos = notification - .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_POSITION); - Position position = Position.values()[pos]; - - final int delay = notification - .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_DELAY); - createNotification(delay, client.getUIConnector().getWidget()).show( - html, position, style); - } - - /** - * Meant for internal usage only. - * - * @since 7.5.0 - * @param client - * application connection - * @param style - * the dependent style name - * @return the given dependent style name prefixed with current notification - * primary style - */ - public static String getDependentStyle(ApplicationConnection client, - String style) { - VNotification notification = createNotification(-1, client - .getUIConnector().getWidget()); - String styleName = notification.getStyleName(); - notification.addStyleDependentName(style); - String extendedStyle = notification.getStyleName(); - return extendedStyle.substring(styleName.length()).trim(); - } - - public static VNotification createNotification(int delayMsec, Widget owner) { - final VNotification notification = GWT.create(VNotification.class); - notification.setWaiAriaRole(null); - notification.setDelay(delayMsec); - - if (!notification.infiniteDelay && BrowserInfo.get().isTouchDevice()) { - new Timer() { - @Override - public void run() { - if (notification.isAttached()) { - notification.hide(); - } - } - }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY); - } - notification.setOwner(owner); - return notification; - } - - public class HideEvent extends EventObject { - - public HideEvent(Object source) { - super(source); - } - } - - public interface EventListener extends java.util.EventListener { - public void notificationHidden(HideEvent event); - } - - /** - * Moves currently visible notifications to the top of the event preview - * stack. Can be called when opening other overlays such as subwindows to - * ensure the notifications receive the events they need and don't linger - * indefinitely. See #7136. - * - * TODO Should this be a generic Overlay feature instead? - */ - public static void bringNotificationsToFront() { - for (VNotification notification : notifications) { - DOM.removeEventPreview(notification); - DOM.addEventPreview(notification); - } - } - - /** - * Shows an error notification and redirects the user to the given URL when - * she clicks on the notification. - * - * If both message and caption are null, redirects the user to the url - * immediately - * - * @since 7.5.1 - * @param connection - * A reference to the ApplicationConnection - * @param caption - * The caption for the error or null to exclude the caption - * @param message - * The message for the error or null to exclude the message - * @param details - * A details message or null to exclude the details - * @param url - * A url to redirect to after the user clicks the error - * notification - */ - public static void showError(ApplicationConnection connection, - String caption, String message, String details, String url) { - - StringBuilder html = new StringBuilder(); - if (caption != null) { - html.append("

"); - html.append(caption); - html.append("

"); - } - if (message != null) { - html.append("

"); - html.append(message); - html.append("

"); - } - - if (html.length() > 0) { - - // Add error description - if (details != null) { - html.append("

"); - html.append(""); - html.append(details); - html.append("

"); - } - - VNotification n = VNotification.createNotification(1000 * 60 * 45, - connection.getUIConnector().getWidget()); - n.addEventListener(new NotificationRedirect(url)); - n.show(html.toString(), VNotification.CENTERED_TOP, - VNotification.STYLE_SYSTEM); - } else { - WidgetUtil.redirect(url); - } - } - - /** - * Listens for Notification hide event, and redirects. Used for system - * messages, such as session expired. - * - */ - private static class NotificationRedirect implements - VNotification.EventListener { - String url; - - NotificationRedirect(String url) { - this.url = url; - } - - @Override - public void notificationHidden(HideEvent event) { - WidgetUtil.redirect(url); - } - - } - -} diff --git a/client/src/com/vaadin/client/ui/VOptionGroup.java b/client/src/com/vaadin/client/ui/VOptionGroup.java deleted file mode 100644 index 9a28111dc5..0000000000 --- a/client/src/com/vaadin/client/ui/VOptionGroup.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import com.google.gwt.aria.client.Roles; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.LoadEvent; -import com.google.gwt.event.dom.client.LoadHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.ui.CheckBox; -import com.google.gwt.user.client.ui.FocusWidget; -import com.google.gwt.user.client.ui.Focusable; -import com.google.gwt.user.client.ui.HasEnabled; -import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.RadioButton; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.StyleConstants; -import com.vaadin.client.UIDL; -import com.vaadin.client.Util; -import com.vaadin.client.WidgetUtil; -import com.vaadin.shared.EventId; -import com.vaadin.shared.ui.optiongroup.OptionGroupConstants; - -public class VOptionGroup extends VOptionGroupBase implements FocusHandler, - BlurHandler { - - public static final String CLASSNAME = "v-select-optiongroup"; - - /** For internal use only. May be removed or replaced in the future. */ - public final Panel panel; - - private final Map optionsToKeys; - - private final Map optionsEnabled; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean sendFocusEvents = false; - /** For internal use only. May be removed or replaced in the future. */ - public boolean sendBlurEvents = false; - /** For internal use only. May be removed or replaced in the future. */ - public List focusHandlers = null; - /** For internal use only. May be removed or replaced in the future. */ - public List blurHandlers = null; - - private final LoadHandler iconLoadHandler = new LoadHandler() { - @Override - public void onLoad(LoadEvent event) { - Util.notifyParentOfSizeChange(VOptionGroup.this, true); - } - }; - - /** - * used to check whether a blur really was a blur of the complete - * optiongroup: if a control inside this optiongroup gains focus right after - * blur of another control inside this optiongroup (meaning: if onFocus - * fires after onBlur has fired), the blur and focus won't be sent to the - * server side as only a focus change inside this optiongroup occured - */ - private boolean blurOccured = false; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean htmlContentAllowed = false; - - private boolean wasHtmlContentAllowed = false; - private boolean wasMultiselect = false; - - public VOptionGroup() { - super(CLASSNAME); - panel = (Panel) optionsContainer; - optionsToKeys = new HashMap(); - optionsEnabled = new HashMap(); - - wasMultiselect = isMultiselect(); - } - - /* - * Try to update content of existing elements, rebuild panel entirely - * otherwise - */ - @Override - public void buildOptions(UIDL uidl) { - /* - * In order to retain focus, we need to update values rather than - * recreate panel from scratch (#10451). However, the panel will be - * rebuilt (losing focus) if number of elements or their order is - * changed. - */ - HashMap keysToOptions = new HashMap(); - for (Map.Entry entry : optionsToKeys.entrySet()) { - keysToOptions.put(entry.getValue(), entry.getKey()); - } - ArrayList existingwidgets = new ArrayList(); - ArrayList newwidgets = new ArrayList(); - - // Get current order of elements - for (Widget wid : panel) { - existingwidgets.add(wid); - } - - optionsEnabled.clear(); - - if (isMultiselect()) { - Roles.getGroupRole().set(getElement()); - } else { - Roles.getRadiogroupRole().set(getElement()); - } - - for (final Iterator it = uidl.getChildIterator(); it.hasNext();) { - final UIDL opUidl = (UIDL) it.next(); - - String itemHtml = opUidl.getStringAttribute("caption"); - if (!htmlContentAllowed) { - itemHtml = WidgetUtil.escapeHTML(itemHtml); - } - - String iconUrl = opUidl.getStringAttribute("icon"); - if (iconUrl != null && iconUrl.length() != 0) { - Icon icon = client.getIcon(iconUrl); - itemHtml = icon.getElement().getString() + itemHtml; - } - - String key = opUidl.getStringAttribute("key"); - CheckBox op = keysToOptions.get(key); - - // Need to recreate object if isMultiselect is changed (#10451) - // OR if htmlContentAllowed changed due to Safari 5 issue - if ((op == null) || (htmlContentAllowed != wasHtmlContentAllowed) - || (isMultiselect() != wasMultiselect)) { - // Create a new element - if (isMultiselect()) { - op = new VCheckBox(); - } else { - op = new RadioButton(paintableId); - op.setStyleName("v-radiobutton"); - } - if (iconUrl != null && iconUrl.length() != 0) { - WidgetUtil.sinkOnloadForImages(op.getElement()); - op.addHandler(iconLoadHandler, LoadEvent.getType()); - } - - op.addStyleName(CLASSNAME_OPTION); - op.addClickHandler(this); - - optionsToKeys.put(op, key); - } - - op.setHTML(itemHtml); - op.setValue(opUidl.getBooleanAttribute("selected")); - boolean optionEnabled = !opUidl - .getBooleanAttribute(OptionGroupConstants.ATTRIBUTE_OPTION_DISABLED); - boolean enabled = optionEnabled && !isReadonly() && isEnabled(); - op.setEnabled(enabled); - optionsEnabled.put(op, optionEnabled); - - setStyleName(op.getElement(), StyleConstants.DISABLED, - !(optionEnabled && isEnabled())); - - newwidgets.add(op); - } - - if (!newwidgets.equals(existingwidgets)) { - // Rebuild the panel, losing focus - panel.clear(); - for (Widget wid : newwidgets) { - panel.add(wid); - } - } - - wasHtmlContentAllowed = htmlContentAllowed; - wasMultiselect = isMultiselect(); - } - - @Override - protected String[] getSelectedItems() { - return selectedKeys.toArray(new String[selectedKeys.size()]); - } - - @Override - public void onClick(ClickEvent event) { - super.onClick(event); - if (event.getSource() instanceof CheckBox) { - CheckBox source = (CheckBox) event.getSource(); - if (!source.isEnabled()) { - // Click events on the text are received even though the - // checkbox is disabled - return; - } - if (BrowserInfo.get().isWebkit()) { - // Webkit does not focus non-text input elements on click - // (#11854) - source.setFocus(true); - } - - final boolean selected = source.getValue(); - final String key = optionsToKeys.get(source); - if (!isMultiselect()) { - selectedKeys.clear(); - } - if (selected) { - selectedKeys.add(key); - } else { - selectedKeys.remove(key); - } - client.updateVariable(paintableId, "selected", getSelectedItems(), - isImmediate()); - } - } - - @Override - public void setTabIndex(int tabIndex) { - for (Iterator iterator = panel.iterator(); iterator.hasNext();) { - FocusWidget widget = (FocusWidget) iterator.next(); - widget.setTabIndex(tabIndex); - } - } - - @Override - protected void updateEnabledState() { - boolean optionGroupEnabled = isEnabled() && !isReadonly(); - // sets options enabled according to the widget's enabled, - // readonly and each options own enabled - for (Widget w : panel) { - if (w instanceof HasEnabled) { - HasEnabled hasEnabled = (HasEnabled) w; - Boolean isOptionEnabled = optionsEnabled.get(w); - if (isOptionEnabled == null) { - hasEnabled.setEnabled(optionGroupEnabled); - setStyleName(w.getElement(), StyleConstants.DISABLED, - !isEnabled()); - } else { - hasEnabled - .setEnabled(isOptionEnabled && optionGroupEnabled); - setStyleName(w.getElement(), StyleConstants.DISABLED, - !(isOptionEnabled && isEnabled())); - } - } - } - } - - @Override - public void focus() { - Iterator iterator = panel.iterator(); - if (iterator.hasNext()) { - ((Focusable) iterator.next()).setFocus(true); - } - } - - @Override - public void onFocus(FocusEvent arg0) { - if (!blurOccured) { - // no blur occured before this focus event - // panel was blurred => fire the event to the server side if - // requested by server side - if (sendFocusEvents) { - client.updateVariable(paintableId, EventId.FOCUS, "", true); - } - } else { - // blur occured before this focus event - // another control inside the panel (checkbox / radio box) was - // blurred => do not fire the focus and set blurOccured to false, so - // blur will not be fired, too - blurOccured = false; - } - } - - @Override - public void onBlur(BlurEvent arg0) { - blurOccured = true; - if (sendBlurEvents) { - Scheduler.get().scheduleDeferred(new Command() { - @Override - public void execute() { - // check whether blurOccured still is true and then send the - // event out to the server - if (blurOccured) { - client.updateVariable(paintableId, EventId.BLUR, "", - true); - blurOccured = false; - } - } - }); - } - } -} diff --git a/client/src/com/vaadin/client/ui/VOptionGroupBase.java b/client/src/com/vaadin/client/ui/VOptionGroupBase.java deleted file mode 100644 index ce75043d89..0000000000 --- a/client/src/com/vaadin/client/ui/VOptionGroupBase.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.Set; - -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyPressEvent; -import com.google.gwt.event.dom.client.KeyPressHandler; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HasEnabled; -import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.Focusable; -import com.vaadin.client.UIDL; - -public abstract class VOptionGroupBase extends Composite implements Field, - ClickHandler, ChangeHandler, KeyPressHandler, Focusable, HasEnabled { - - public static final String CLASSNAME_OPTION = "v-select-option"; - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - public String paintableId; - - /** For internal use only. May be removed or replaced in the future. */ - public Set selectedKeys; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean immediate; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean multiselect; - - private boolean enabled; - - private boolean readonly; - - /** For internal use only. May be removed or replaced in the future. */ - public int cols = 0; - - /** For internal use only. May be removed or replaced in the future. */ - public int rows = 0; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean nullSelectionAllowed = true; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean nullSelectionItemAvailable = false; - - /** - * Widget holding the different options (e.g. ListBox or Panel for radio - * buttons) (optional, fallbacks to container Panel) - *

- * For internal use only. May be removed or replaced in the future. - */ - public Widget optionsContainer; - - /** - * Panel containing the component. - *

- * For internal use only. May be removed or replaced in the future. - */ - public final Panel container; - - /** For internal use only. May be removed or replaced in the future. */ - public VTextField newItemField; - - /** For internal use only. May be removed or replaced in the future. */ - public VNativeButton newItemButton; - - public VOptionGroupBase(String classname) { - container = new FlowPanel(); - initWidget(container); - optionsContainer = container; - container.setStyleName(classname); - immediate = false; - multiselect = false; - } - - /* - * Call this if you wish to specify your own container for the option - * elements (e.g. SELECT) - */ - public VOptionGroupBase(Widget w, String classname) { - this(classname); - optionsContainer = w; - container.add(optionsContainer); - } - - protected boolean isImmediate() { - return immediate; - } - - protected boolean isMultiselect() { - return multiselect; - } - - @Override - public boolean isEnabled() { - return enabled; - } - - public boolean isReadonly() { - return readonly; - } - - protected boolean isNullSelectionAllowed() { - return nullSelectionAllowed; - } - - protected boolean isNullSelectionItemAvailable() { - return nullSelectionItemAvailable; - } - - /** - * For internal use only. May be removed or replaced in the future. - * - * @return "cols" specified in uidl, 0 if not specified - */ - public int getColumns() { - return cols; - } - - /** - * For internal use only. May be removed or replaced in the future. - * - * @return "rows" specified in uidl, 0 if not specified - */ - public int getRows() { - return rows; - } - - public abstract void setTabIndex(int tabIndex); - - @Override - public void onClick(ClickEvent event) { - if (event.getSource() == newItemButton - && !newItemField.getText().equals("")) { - client.updateVariable(paintableId, "newitem", - newItemField.getText(), true); - newItemField.setText(""); - } - } - - @Override - public void onChange(ChangeEvent event) { - if (multiselect) { - client.updateVariable(paintableId, "selected", getSelectedItems(), - immediate); - } else { - client.updateVariable(paintableId, "selected", new String[] { "" - + getSelectedItem() }, immediate); - } - } - - @Override - public void onKeyPress(KeyPressEvent event) { - if (event.getSource() == newItemField - && event.getCharCode() == KeyCodes.KEY_ENTER) { - newItemButton.click(); - } - } - - public void setReadonly(boolean readonly) { - if (this.readonly != readonly) { - this.readonly = readonly; - updateEnabledState(); - } - } - - @Override - public void setEnabled(boolean enabled) { - if (this.enabled != enabled) { - this.enabled = enabled; - updateEnabledState(); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public abstract void buildOptions(UIDL uidl); - - protected abstract String[] getSelectedItems(); - - protected abstract void updateEnabledState(); - - protected String getSelectedItem() { - final String[] sel = getSelectedItems(); - if (sel.length > 0) { - return sel[0]; - } else { - return null; - } - } - -} diff --git a/client/src/com/vaadin/client/ui/VOverlay.java b/client/src/com/vaadin/client/ui/VOverlay.java deleted file mode 100644 index 5dc29f5a42..0000000000 --- a/client/src/com/vaadin/client/ui/VOverlay.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.logging.Logger; - -import com.google.gwt.aria.client.Roles; -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.RootPanel; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.Util; -import com.vaadin.client.widgets.Overlay; - -/** - * In Vaadin UI this VOverlay should always be used for all elements that - * temporary float over other components like context menus etc. This is to deal - * stacking order correctly with VWindow objects. - *

- * To use this correctly, use {@link GWT#create(Class)} to create the - * {@link Overlay} superclass and the default widgetset will replace it with - * this. The widget will not be dependent on this Vaadin specific widget and can - * be used in a pure GWT environment. - * - * @deprecated as this is specifically for Vaadin only, it should not be used - * directly. - */ -@Deprecated -public class VOverlay extends Overlay implements CloseHandler { - - /* - * ApplicationConnection that this overlay belongs to, which is needed to - * create the overlay in the correct container so that the correct styles - * are applied. If not given, owner will be used to figure out, and as a - * last fallback, the overlay is created w/o container, potentially missing - * styles. - */ - protected ApplicationConnection ac; - - public VOverlay() { - super(); - } - - public VOverlay(boolean autoHide) { - super(autoHide); - } - - public VOverlay(boolean autoHide, boolean modal) { - super(autoHide, modal); - } - - /** - * @deprecated See main JavaDoc for VOverlay. Use the other constructors - * without the showShadow parameter. - */ - @Deprecated - public VOverlay(boolean autoHide, boolean modal, boolean showShadow) { - super(autoHide, modal, showShadow); - } - - /* - * A "thread local" of sorts, set temporarily so that VOverlayImpl knows - * which VOverlay is using it, so that it can be attached to the correct - * overlay container. - * - * TODO this is a strange pattern that we should get rid of when possible. - */ - protected static VOverlay current; - - /** - * Get the {@link ApplicationConnection} that this overlay belongs to. If - * it's not set, {@link #getOwner()} is used to figure it out. - * - * @return - */ - protected ApplicationConnection getApplicationConnection() { - if (ac != null) { - return ac; - } else if (getOwner() != null) { - ComponentConnector c = Util.findConnectorFor(getOwner()); - if (c != null) { - ac = c.getConnection(); - } - return ac; - } else { - return null; - } - } - - /** - * Gets the 'overlay container' element. Tries to find the current - * {@link ApplicationConnection} using {@link #getApplicationConnection()}. - * - * @return the overlay container element for the current - * {@link ApplicationConnection} or another element if the current - * {@link ApplicationConnection} cannot be determined. - */ - @Override - public com.google.gwt.user.client.Element getOverlayContainer() { - ApplicationConnection ac = getApplicationConnection(); - if (ac == null) { - // could not figure out which one we belong to, styling will - // probably fail - Logger.getLogger(getClass().getSimpleName()) - .warning( - "Could not determine ApplicationConnection for Overlay. Overlay will be attached directly to the root panel"); - return super.getOverlayContainer(); - } else { - return getOverlayContainer(ac); - } - } - - /** - * Gets the 'overlay container' element pertaining to the given - * {@link ApplicationConnection}. Each overlay should be created in a - * overlay container element, so that the correct theme and styles can be - * applied. - * - * @param ac - * A reference to {@link ApplicationConnection} - * @return The overlay container - */ - public static com.google.gwt.user.client.Element getOverlayContainer( - ApplicationConnection ac) { - String id = ac.getConfiguration().getRootPanelId(); - id = id += "-overlays"; - Element container = DOM.getElementById(id); - if (container == null) { - container = DOM.createDiv(); - container.setId(id); - String styles = ac.getUIConnector().getWidget().getParent() - .getStyleName(); - if (styles != null && !styles.equals("")) { - container.addClassName(styles); - } - container.addClassName(CLASSNAME_CONTAINER); - RootPanel.get().getElement().appendChild(container); - } - return DOM.asOld(container); - } - - /** - * Set the label of the container element, where tooltip, notification and - * dialgs are added to. - * - * @param applicationConnection - * the application connection for which to change the label - * @param overlayContainerLabel - * label for the container - */ - public static void setOverlayContainerLabel( - ApplicationConnection applicationConnection, - String overlayContainerLabel) { - Roles.getAlertRole().setAriaLabelProperty( - VOverlay.getOverlayContainer(applicationConnection), - overlayContainerLabel); - } - -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/ui/VPanel.java b/client/src/com/vaadin/client/ui/VPanel.java deleted file mode 100644 index 946ff83180..0000000000 --- a/client/src/com/vaadin/client/ui/VPanel.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.SimplePanel; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.Focusable; -import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; -import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler; - -public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, - Focusable { - - public static final String CLASSNAME = "v-panel"; - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - public String id; - - /** For internal use only. May be removed or replaced in the future. */ - public final Element captionNode = DOM.createDiv(); - - private final Element captionText = DOM.createSpan(); - - private Icon icon; - - /** For internal use only. May be removed or replaced in the future. */ - public final Element bottomDecoration = DOM.createDiv(); - - /** For internal use only. May be removed or replaced in the future. */ - public final Element contentNode = DOM.createDiv(); - - private Element errorIndicatorElement; - - /** For internal use only. May be removed or replaced in the future. */ - public ShortcutActionHandler shortcutHandler; - - /** For internal use only. May be removed or replaced in the future. */ - public int scrollTop; - - /** For internal use only. May be removed or replaced in the future. */ - public int scrollLeft; - - private TouchScrollHandler touchScrollHandler; - - public VPanel() { - super(); - DivElement captionWrap = Document.get().createDivElement(); - captionWrap.appendChild(captionNode); - captionNode.appendChild(captionText); - - captionWrap.setClassName(CLASSNAME + "-captionwrap"); - captionNode.setClassName(CLASSNAME + "-caption"); - contentNode.setClassName(CLASSNAME + "-content"); - bottomDecoration.setClassName(CLASSNAME + "-deco"); - - getElement().appendChild(captionWrap); - - /* - * Make contentNode focusable only by using the setFocus() method. This - * behaviour can be changed by invoking setTabIndex() in the serverside - * implementation - */ - contentNode.setTabIndex(-1); - - getElement().appendChild(contentNode); - - getElement().appendChild(bottomDecoration); - setStyleName(CLASSNAME); - DOM.sinkEvents(getElement(), Event.ONKEYDOWN); - DOM.sinkEvents(contentNode, Event.ONSCROLL | Event.TOUCHEVENTS); - - contentNode.getStyle().setProperty("position", "relative"); - getElement().getStyle().setProperty("overflow", "hidden"); - - makeScrollable(); - } - - /** - * Sets the keyboard focus on the Panel - * - * @param focus - * Should the panel have focus or not. - */ - public void setFocus(boolean focus) { - if (focus) { - getContainerElement().focus(); - } else { - getContainerElement().blur(); - } - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.Focusable#focus() - */ - - @Override - public void focus() { - setFocus(true); - - } - - @Override - protected com.google.gwt.user.client.Element getContainerElement() { - return DOM.asOld(contentNode); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void setCaption(String text) { - DOM.setInnerHTML(captionText, text); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void setErrorIndicatorVisible(boolean showError) { - if (showError) { - if (errorIndicatorElement == null) { - errorIndicatorElement = DOM.createSpan(); - DOM.setElementProperty(errorIndicatorElement, "className", - "v-errorindicator"); - DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS); - sinkEvents(Event.MOUSEEVENTS); - } - DOM.insertBefore(captionNode, errorIndicatorElement, captionText); - } else if (errorIndicatorElement != null) { - DOM.removeChild(captionNode, errorIndicatorElement); - errorIndicatorElement = null; - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void setIconUri(String iconUri, ApplicationConnection client) { - if (icon != null) { - captionNode.removeChild(icon.getElement()); - } - icon = client.getIcon(iconUri); - if (icon != null) { - DOM.insertChild(captionNode, icon.getElement(), 0); - } - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - - final int type = DOM.eventGetType(event); - if (type == Event.ONKEYDOWN && shortcutHandler != null) { - shortcutHandler.handleKeyboardEvent(event); - return; - } - if (type == Event.ONSCROLL) { - int newscrollTop = DOM.getElementPropertyInt(contentNode, - "scrollTop"); - int newscrollLeft = DOM.getElementPropertyInt(contentNode, - "scrollLeft"); - if (client != null - && (newscrollLeft != scrollLeft || newscrollTop != scrollTop)) { - scrollLeft = newscrollLeft; - scrollTop = newscrollTop; - client.updateVariable(id, "scrollTop", scrollTop, false); - client.updateVariable(id, "scrollLeft", scrollLeft, false); - } - } - } - - @Override - public ShortcutActionHandler getShortcutActionHandler() { - return shortcutHandler; - } - - /** - * Ensures the panel is scrollable eg. after style name changes. - *

- * For internal use only. May be removed or replaced in the future. - */ - public void makeScrollable() { - if (touchScrollHandler == null) { - touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); - } - touchScrollHandler.addElement(contentNode); - } -} diff --git a/client/src/com/vaadin/client/ui/VPasswordField.java b/client/src/com/vaadin/client/ui/VPasswordField.java deleted file mode 100644 index dcbb60364c..0000000000 --- a/client/src/com/vaadin/client/ui/VPasswordField.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.user.client.DOM; - -/** - * This class represents a password field. - * - * @author Vaadin Ltd. - * - */ -public class VPasswordField extends VTextField { - - public VPasswordField() { - super(DOM.createInputPassword()); - } - -} diff --git a/client/src/com/vaadin/client/ui/VPopupCalendar.java b/client/src/com/vaadin/client/ui/VPopupCalendar.java deleted file mode 100644 index 47f11b09b1..0000000000 --- a/client/src/com/vaadin/client/ui/VPopupCalendar.java +++ /dev/null @@ -1,737 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.Date; - -import com.google.gwt.aria.client.Id; -import com.google.gwt.aria.client.LiveValue; -import com.google.gwt.aria.client.Roles; -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.DomEvent; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.MouseOutEvent; -import com.google.gwt.event.dom.client.MouseOutHandler; -import com.google.gwt.event.dom.client.MouseOverEvent; -import com.google.gwt.event.dom.client.MouseOverHandler; -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.i18n.client.DateTimeFormat; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; -import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ComputedStyle; -import com.vaadin.client.VConsole; -import com.vaadin.client.ui.VCalendarPanel.FocusOutListener; -import com.vaadin.client.ui.VCalendarPanel.SubmitListener; -import com.vaadin.client.ui.aria.AriaHelper; -import com.vaadin.shared.ui.datefield.PopupDateFieldState; -import com.vaadin.shared.ui.datefield.Resolution; - -/** - * Represents a date selection component with a text field and a popup date - * selector. - * - * Note: To change the keyboard assignments used in the popup dialog you - * should extend com.vaadin.client.ui.VCalendarPanel and then pass - * set it by calling the setCalendarPanel(VCalendarPanel panel) - * method. - * - */ -public class VPopupCalendar extends VTextualDate implements Field, - ClickHandler, CloseHandler, SubPartAware { - - /** For internal use only. May be removed or replaced in the future. */ - public final Button calendarToggle = new Button(); - - /** For internal use only. May be removed or replaced in the future. */ - public VCalendarPanel calendar; - - /** For internal use only. May be removed or replaced in the future. */ - public final VOverlay popup; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean parsable = true; - - private boolean open = false; - - /* - * #14857: If calendarToggle button is clicked when calendar popup is - * already open we should prevent calling openCalendarPanel() in onClick, - * since we don't want to reopen it again right after it closes. - */ - private boolean preventOpenPopupCalendar = false; - private boolean cursorOverCalendarToggleButton = false; - private boolean toggleButtonClosesWithGuarantee = false; - - private boolean textFieldEnabled = true; - - private String captionId; - - private Label selectedDate; - - private Element descriptionForAssisitveDevicesElement; - - public VPopupCalendar() { - super(); - - calendarToggle.setText(""); - calendarToggle.addClickHandler(this); - - calendarToggle.addDomHandler(new MouseOverHandler() { - @Override - public void onMouseOver(MouseOverEvent event) { - cursorOverCalendarToggleButton = true; - } - }, MouseOverEvent.getType()); - - calendarToggle.addDomHandler(new MouseOutHandler() { - @Override - public void onMouseOut(MouseOutEvent event) { - cursorOverCalendarToggleButton = false; - } - }, MouseOutEvent.getType()); - - // -2 instead of -1 to avoid FocusWidget.onAttach to reset it - calendarToggle.getElement().setTabIndex(-2); - - Roles.getButtonRole().set(calendarToggle.getElement()); - Roles.getButtonRole().setAriaHiddenState(calendarToggle.getElement(), - true); - - add(calendarToggle); - - // Description of the usage of the widget for assisitve device users - descriptionForAssisitveDevicesElement = DOM.createDiv(); - descriptionForAssisitveDevicesElement - .setInnerText(PopupDateFieldState.DESCRIPTION_FOR_ASSISTIVE_DEVICES); - AriaHelper.ensureHasId(descriptionForAssisitveDevicesElement); - Roles.getTextboxRole().setAriaDescribedbyProperty(text.getElement(), - Id.of(descriptionForAssisitveDevicesElement)); - AriaHelper.setVisibleForAssistiveDevicesOnly( - descriptionForAssisitveDevicesElement, true); - - calendar = GWT.create(VCalendarPanel.class); - calendar.setParentField(this); - calendar.setFocusOutListener(new FocusOutListener() { - @Override - public boolean onFocusOut(DomEvent event) { - event.preventDefault(); - closeCalendarPanel(); - return true; - } - }); - - // FIXME: Problem is, that the element with the provided id does not - // exist yet in html. This is the same problem as with the context menu. - // Apply here the same fix (#11795) - Roles.getTextboxRole().setAriaControlsProperty(text.getElement(), - Id.of(calendar.getElement())); - Roles.getButtonRole().setAriaControlsProperty( - calendarToggle.getElement(), Id.of(calendar.getElement())); - - calendar.setSubmitListener(new SubmitListener() { - @Override - public void onSubmit() { - // Update internal value and send valuechange event if immediate - updateValue(calendar.getDate()); - - // Update text field (a must when not immediate). - buildDate(true); - - closeCalendarPanel(); - } - - @Override - public void onCancel() { - closeCalendarPanel(); - } - }); - - popup = new VOverlay(true, false, true); - popup.setOwner(this); - - FlowPanel wrapper = new FlowPanel(); - selectedDate = new Label(); - selectedDate.setStyleName(getStylePrimaryName() + "-selecteddate"); - AriaHelper.setVisibleForAssistiveDevicesOnly(selectedDate.getElement(), - true); - - Roles.getTextboxRole().setAriaLiveProperty(selectedDate.getElement(), - LiveValue.ASSERTIVE); - Roles.getTextboxRole().setAriaAtomicProperty(selectedDate.getElement(), - true); - wrapper.add(selectedDate); - wrapper.add(calendar); - - popup.setWidget(wrapper); - popup.addCloseHandler(this); - - DOM.setElementProperty(calendar.getElement(), "id", - "PID_VAADIN_POPUPCAL"); - - sinkEvents(Event.ONKEYDOWN); - - updateStyleNames(); - } - - @Override - protected void onAttach() { - super.onAttach(); - DOM.appendChild(RootPanel.get().getElement(), - descriptionForAssisitveDevicesElement); - } - - @Override - protected void onDetach() { - super.onDetach(); - descriptionForAssisitveDevicesElement.removeFromParent(); - closeCalendarPanel(); - } - - @SuppressWarnings("deprecation") - public void updateValue(Date newDate) { - Date currentDate = getCurrentDate(); - if (currentDate == null || newDate.getTime() != currentDate.getTime()) { - setCurrentDate((Date) newDate.clone()); - getClient().updateVariable(getId(), "year", - newDate.getYear() + 1900, false); - if (getCurrentResolution().getCalendarField() > Resolution.YEAR - .getCalendarField()) { - getClient().updateVariable(getId(), "month", - newDate.getMonth() + 1, false); - if (getCurrentResolution().getCalendarField() > Resolution.MONTH - .getCalendarField()) { - getClient().updateVariable(getId(), "day", - newDate.getDate(), false); - if (getCurrentResolution().getCalendarField() > Resolution.DAY - .getCalendarField()) { - getClient().updateVariable(getId(), "hour", - newDate.getHours(), false); - if (getCurrentResolution().getCalendarField() > Resolution.HOUR - .getCalendarField()) { - getClient().updateVariable(getId(), "min", - newDate.getMinutes(), false); - if (getCurrentResolution().getCalendarField() > Resolution.MINUTE - .getCalendarField()) { - getClient().updateVariable(getId(), "sec", - newDate.getSeconds(), false); - } - } - } - } - } - } - } - - /** - * Checks whether the text field is enabled. - * - * @see VPopupCalendar#setTextFieldEnabled(boolean) - * @return The current state of the text field. - */ - public boolean isTextFieldEnabled() { - return textFieldEnabled; - } - - /** - * Sets the state of the text field of this component. By default the text - * field is enabled. Disabling it causes only the button for date selection - * to be active, thus preventing the user from entering invalid dates. See - * {@link http://dev.vaadin.com/ticket/6790}. - * - * @param state - */ - public void setTextFieldEnabled(boolean textFieldEnabled) { - this.textFieldEnabled = textFieldEnabled; - updateTextFieldEnabled(); - } - - protected void updateTextFieldEnabled() { - boolean reallyEnabled = isEnabled() && isTextFieldEnabled(); - // IE has a non input disabled themeing that can not be overridden so we - // must fake the functionality using readonly and unselectable - if (BrowserInfo.get().isIE()) { - if (!reallyEnabled) { - text.getElement().setAttribute("unselectable", "on"); - text.getElement().setAttribute("readonly", ""); - text.setTabIndex(-2); - } else if (reallyEnabled - && text.getElement().hasAttribute("unselectable")) { - text.getElement().removeAttribute("unselectable"); - text.getElement().removeAttribute("readonly"); - text.setTabIndex(0); - } - } else { - text.setEnabled(reallyEnabled); - } - - if (reallyEnabled) { - calendarToggle.setTabIndex(-1); - Roles.getButtonRole().setAriaHiddenState( - calendarToggle.getElement(), true); - } else { - calendarToggle.setTabIndex(0); - Roles.getButtonRole().setAriaHiddenState( - calendarToggle.getElement(), false); - } - - handleAriaAttributes(); - } - - /** - * Set correct tab index for disabled text field in IE as the value set in - * setTextFieldEnabled(...) gets overridden in - * TextualDateConnection.updateFromUIDL(...) - * - * @since 7.3.1 - */ - public void setTextFieldTabIndex() { - if (BrowserInfo.get().isIE() && !textFieldEnabled) { - // index needs to be -2 because FocusWidget updates -1 to 0 onAttach - text.setTabIndex(-2); - } - } - - @Override - public void bindAriaCaption( - com.google.gwt.user.client.Element captionElement) { - if (captionElement == null) { - captionId = null; - } else { - captionId = captionElement.getId(); - } - - if (isTextFieldEnabled()) { - super.bindAriaCaption(captionElement); - } else { - AriaHelper.bindCaption(calendarToggle, captionElement); - } - - handleAriaAttributes(); - } - - private void handleAriaAttributes() { - Widget removeFromWidget; - Widget setForWidget; - - if (isTextFieldEnabled()) { - setForWidget = text; - removeFromWidget = calendarToggle; - } else { - setForWidget = calendarToggle; - removeFromWidget = text; - } - - Roles.getFormRole().removeAriaLabelledbyProperty( - removeFromWidget.getElement()); - if (captionId == null) { - Roles.getFormRole().removeAriaLabelledbyProperty( - setForWidget.getElement()); - } else { - Roles.getFormRole().setAriaLabelledbyProperty( - setForWidget.getElement(), Id.of(captionId)); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String) - */ - @Override - public void setStyleName(String style) { - super.setStyleName(style); - updateStyleNames(); - } - - @Override - public void setStylePrimaryName(String style) { - removeStyleName(getStylePrimaryName() + "-popupcalendar"); - super.setStylePrimaryName(style); - updateStyleNames(); - } - - @Override - protected void updateStyleNames() { - super.updateStyleNames(); - if (getStylePrimaryName() != null && calendarToggle != null) { - addStyleName(getStylePrimaryName() + "-popupcalendar"); - calendarToggle.setStyleName(getStylePrimaryName() + "-button"); - popup.setStyleName(getStylePrimaryName() + "-popup"); - calendar.setStyleName(getStylePrimaryName() + "-calendarpanel"); - } - } - - /** - * Opens the calendar panel popup - */ - public void openCalendarPanel() { - - if (!open && !readonly && isEnabled()) { - open = true; - - if (getCurrentDate() != null) { - calendar.setDate((Date) getCurrentDate().clone()); - } else { - calendar.setDate(new Date()); - } - - // clear previous values - popup.setWidth(""); - popup.setHeight(""); - popup.setPopupPositionAndShow(new PopupPositionCallback()); - } else { - VConsole.error("Cannot reopen popup, it is already open!"); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event - * .dom.client.ClickEvent) - */ - @Override - public void onClick(ClickEvent event) { - if (event.getSource() == calendarToggle && isEnabled()) { - if (!preventOpenPopupCalendar) { - openCalendarPanel(); - } - preventOpenPopupCalendar = false; - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt - * .event.logical.shared.CloseEvent) - */ - @Override - public void onClose(CloseEvent event) { - if (event.getSource() == popup) { - buildDate(); - if (!BrowserInfo.get().isTouchDevice() && textFieldEnabled) { - /* - * Move focus to textbox, unless on touch device (avoids opening - * virtual keyboard) or if textField is disabled. - */ - focus(); - } - - open = false; - - if (cursorOverCalendarToggleButton - && !toggleButtonClosesWithGuarantee) { - preventOpenPopupCalendar = true; - } - - toggleButtonClosesWithGuarantee = false; - } - } - - /** - * Sets focus to Calendar panel. - * - * @param focus - */ - public void setFocus(boolean focus) { - calendar.setFocus(focus); - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - updateTextFieldEnabled(); - calendarToggle.setEnabled(enabled); - Roles.getButtonRole().setAriaDisabledState(calendarToggle.getElement(), - !enabled); - } - - /** - * Sets the content of a special field for assistive devices, so that they - * can recognize the change and inform the user (reading out in case of - * screen reader) - * - * @param selectedDate - * Date that is currently selected - */ - public void setFocusedDate(Date selectedDate) { - this.selectedDate.setText(DateTimeFormat.getFormat("dd, MMMM, yyyy") - .format(selectedDate)); - } - - /** - * For internal use only. May be removed or replaced in the future. - * - * @see com.vaadin.client.ui.VTextualDate#buildDate() - */ - @Override - public void buildDate() { - // Save previous value - String previousValue = getText(); - super.buildDate(); - - // Restore previous value if the input could not be parsed - if (!parsable) { - setText(previousValue); - } - updateTextFieldEnabled(); - } - - /** - * Update the text field contents from the date. See {@link #buildDate()}. - * - * @param forceValid - * true to force the text field to be updated, false to only - * update if the parsable flag is true. - */ - protected void buildDate(boolean forceValid) { - if (forceValid) { - parsable = true; - } - buildDate(); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.VDateField#onBrowserEvent(com.google - * .gwt.user.client.Event) - */ - @Override - public void onBrowserEvent(com.google.gwt.user.client.Event event) { - super.onBrowserEvent(event); - if (DOM.eventGetType(event) == Event.ONKEYDOWN - && event.getKeyCode() == getOpenCalenderPanelKey()) { - openCalendarPanel(); - event.preventDefault(); - } - } - - /** - * Get the key code that opens the calendar panel. By default it is the down - * key but you can override this to be whatever you like - * - * @return - */ - protected int getOpenCalenderPanelKey() { - return KeyCodes.KEY_DOWN; - } - - /** - * Closes the open popup panel - */ - public void closeCalendarPanel() { - if (open) { - toggleButtonClosesWithGuarantee = true; - popup.hide(true); - } - } - - private final String CALENDAR_TOGGLE_ID = "popupButton"; - - @Override - public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - if (subPart.equals(CALENDAR_TOGGLE_ID)) { - return calendarToggle.getElement(); - } - - return super.getSubPartElement(subPart); - } - - @Override - public String getSubPartName(com.google.gwt.user.client.Element subElement) { - if (calendarToggle.getElement().isOrHasChild(subElement)) { - return CALENDAR_TOGGLE_ID; - } - - return super.getSubPartName(subElement); - } - - /** - * Set a description that explains the usage of the Widget for users of - * assistive devices. - * - * @param descriptionForAssistiveDevices - * String with the description - */ - public void setDescriptionForAssistiveDevices( - String descriptionForAssistiveDevices) { - descriptionForAssisitveDevicesElement - .setInnerText(descriptionForAssistiveDevices); - } - - /** - * Get the description that explains the usage of the Widget for users of - * assistive devices. - * - * @return String with the description - */ - public String getDescriptionForAssistiveDevices() { - return descriptionForAssisitveDevicesElement.getInnerText(); - } - - /** - * Sets the start range for this component. The start range is inclusive, - * and it depends on the current resolution, what is considered inside the - * range. - * - * @param startDate - * - the allowed range's start date - */ - public void setRangeStart(Date rangeStart) { - calendar.setRangeStart(rangeStart); - } - - /** - * Sets the end range for this component. The end range is inclusive, and it - * depends on the current resolution, what is considered inside the range. - * - * @param endDate - * - the allowed range's end date - */ - public void setRangeEnd(Date rangeEnd) { - calendar.setRangeEnd(rangeEnd); - } - - private class PopupPositionCallback implements PositionCallback { - - @Override - public void setPosition(int offsetWidth, int offsetHeight) { - final int width = offsetWidth; - final int height = offsetHeight; - final int browserWindowWidth = Window.getClientWidth() - + Window.getScrollLeft(); - final int windowHeight = Window.getClientHeight() - + Window.getScrollTop(); - int left = calendarToggle.getAbsoluteLeft(); - - // Add a little extra space to the right to avoid - // problems with IE7 scrollbars and to make it look - // nicer. - int extraSpace = 30; - - boolean overflow = left + width + extraSpace > browserWindowWidth; - if (overflow) { - // Part of the popup is outside the browser window - // (to the right) - left = browserWindowWidth - width - extraSpace; - } - - int top = calendarToggle.getAbsoluteTop(); - int extraHeight = 2; - boolean verticallyRepositioned = false; - ComputedStyle style = new ComputedStyle(popup.getElement()); - int[] margins = style.getMargin(); - int desiredPopupBottom = top + height - + calendarToggle.getOffsetHeight() + margins[0] - + margins[2]; - - if (desiredPopupBottom > windowHeight) { - int updatedLeft = left; - left = getLeftPosition(left, width, style, overflow); - - // if position has not been changed then it means there is no - // space to make popup fully visible - if (updatedLeft == left) { - // let's try to show popup on the top of the field - int updatedTop = top - extraHeight - height - margins[0] - - margins[2]; - verticallyRepositioned = updatedTop >= 0; - if (verticallyRepositioned) { - top = updatedTop; - } - } - // Part of the popup is outside the browser window - // (below) - if (!verticallyRepositioned) { - verticallyRepositioned = true; - top = windowHeight - height - extraSpace + extraHeight; - } - } - if (verticallyRepositioned) { - popup.setPopupPosition(left, top); - } else { - popup.setPopupPosition(left, - top + calendarToggle.getOffsetHeight() + extraHeight); - } - doSetFocus(); - } - - private int getLeftPosition(int left, int width, ComputedStyle style, - boolean overflow) { - if (positionRightSide()) { - // Show to the right of the popup button unless we - // are in the lower right corner of the screen - if (overflow) { - return left; - } else { - return left + calendarToggle.getOffsetWidth(); - } - } else { - int[] margins = style.getMargin(); - int desiredLeftPosition = calendarToggle.getAbsoluteLeft() - - width - margins[1] - margins[3]; - if (desiredLeftPosition >= 0) { - return desiredLeftPosition; - } else { - return left; - } - } - } - - private boolean positionRightSide() { - int buttonRightSide = calendarToggle.getAbsoluteLeft() - + calendarToggle.getOffsetWidth(); - int textRightSide = text.getAbsoluteLeft() + text.getOffsetWidth(); - return buttonRightSide >= textRightSide; - } - - private void doSetFocus() { - /* - * We have to wait a while before focusing since the popup needs to - * be opened before we can focus - */ - Timer focusTimer = new Timer() { - @Override - public void run() { - setFocus(true); - } - }; - - focusTimer.schedule(100); - } - } - -} diff --git a/client/src/com/vaadin/client/ui/VPopupView.java b/client/src/com/vaadin/client/ui/VPopupView.java deleted file mode 100644 index 0f4e68acab..0000000000 --- a/client/src/com/vaadin/client/ui/VPopupView.java +++ /dev/null @@ -1,466 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.Focusable; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.HasEnabled; -import com.google.gwt.user.client.ui.HasWidgets; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.DeferredWorker; -import com.vaadin.client.Util; -import com.vaadin.client.VCaptionWrapper; -import com.vaadin.client.VConsole; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; -import com.vaadin.client.ui.popupview.VisibilityChangeEvent; -import com.vaadin.client.ui.popupview.VisibilityChangeHandler; - -public class VPopupView extends HTML implements HasEnabled, Iterable, - DeferredWorker { - - public static final String CLASSNAME = "v-popupview"; - - /** - * For server-client communication. - *

- * For internal use only. May be removed or replaced in the future. - */ - public String uidlId; - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - /** - * Helps to communicate popup visibility to the server. - *

- * For internal use only. May be removed or replaced in the future. - */ - public boolean hostPopupVisible; - - /** For internal use only. May be removed or replaced in the future. */ - public final CustomPopup popup; - private final Label loading = new Label(); - - private boolean popupShowInProgress; - private boolean enabled = true; - - /** - * loading constructor - */ - public VPopupView() { - super(); - popup = new CustomPopup(); - - setStyleName(CLASSNAME); - popup.setStyleName(CLASSNAME + "-popup"); - loading.setStyleName(CLASSNAME + "-loading"); - - setHTML(""); - popup.setWidget(loading); - - // When we click to open the popup... - addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - if (isEnabled()) { - preparePopup(popup); - showPopup(popup); - center(); - fireEvent(new VisibilityChangeEvent(true)); - } - } - }); - - // ..and when we close it - popup.addCloseHandler(new CloseHandler() { - @Override - public void onClose(CloseEvent event) { - fireEvent(new VisibilityChangeEvent(false)); - } - }); - - // TODO: Enable animations once GWT fix has been merged - popup.setAnimationEnabled(false); - - popup.setAutoHideOnHistoryEventsEnabled(false); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void preparePopup(final CustomPopup popup) { - popup.setVisible(true); - popup.setWidget(loading); - popup.show(); - } - - /** - * Determines the correct position for a popup and displays the popup at - * that position. - * - * By default, the popup is shown centered relative to its host component, - * ensuring it is visible on the screen if possible. - * - * Can be overridden to customize the popup position. - * - * @param popup - */ - public void showPopup(final CustomPopup popup) { - popup.setPopupPosition(0, 0); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void center() { - int windowTop = RootPanel.get().getAbsoluteTop(); - int windowLeft = RootPanel.get().getAbsoluteLeft(); - int windowRight = windowLeft + RootPanel.get().getOffsetWidth(); - int windowBottom = windowTop + RootPanel.get().getOffsetHeight(); - - int offsetWidth = popup.getOffsetWidth(); - int offsetHeight = popup.getOffsetHeight(); - - int hostHorizontalCenter = VPopupView.this.getAbsoluteLeft() - + VPopupView.this.getOffsetWidth() / 2; - int hostVerticalCenter = VPopupView.this.getAbsoluteTop() - + VPopupView.this.getOffsetHeight() / 2; - - int left = hostHorizontalCenter - offsetWidth / 2; - int top = hostVerticalCenter - offsetHeight / 2; - - // Don't show the popup outside the screen. - if ((left + offsetWidth) > windowRight) { - left -= (left + offsetWidth) - windowRight; - } - - if ((top + offsetHeight) > windowBottom) { - top -= (top + offsetHeight) - windowBottom; - } - - if (left < 0) { - left = 0; - } - - if (top < 0) { - top = 0; - } - - popup.setPopupPosition(left, top); - } - - /** - * Make sure that we remove the popup when the main widget is removed. - * - * @see com.google.gwt.user.client.ui.Widget#onUnload() - */ - @Override - protected void onDetach() { - popup.hide(); - super.onDetach(); - } - - private static native void nativeBlur(Element e) - /*-{ - if(e && e.blur) { - e.blur(); - } - }-*/; - - /** - * Returns true if the popup is enabled, false if not. - * - * @since 7.3.4 - */ - @Override - public boolean isEnabled() { - return enabled; - } - - /** - * Sets whether this popup is enabled. - * - * @param enabled - * true to enable the popup, false to - * disable it - * @since 7.3.4 - */ - @Override - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - /** - * This class is only public to enable overriding showPopup, and is - * currently not intended to be extended or otherwise used directly. Its API - * (other than it being a VOverlay) is to be considered private and - * potentially subject to change. - */ - public class CustomPopup extends VOverlay implements - StateChangeEvent.StateChangeHandler { - - private ComponentConnector popupComponentConnector = null; - - /** For internal use only. May be removed or replaced in the future. */ - public Widget popupComponentWidget = null; - - /** For internal use only. May be removed or replaced in the future. */ - public VCaptionWrapper captionWrapper = null; - - private boolean hasHadMouseOver = false; - private boolean hideOnMouseOut = true; - private final Set activeChildren = new HashSet(); - - private ShortcutActionHandler shortcutActionHandler; - - public CustomPopup() { - super(true, false, true); // autoHide, not modal, dropshadow - setOwner(VPopupView.this); - // Delegate popup keyboard events to the relevant handler. The - // events do not propagate automatically because the popup is - // directly attached to the RootPanel. - addDomHandler(new KeyDownHandler() { - @Override - public void onKeyDown(KeyDownEvent event) { - if (shortcutActionHandler != null) { - shortcutActionHandler.handleKeyboardEvent(Event - .as(event.getNativeEvent())); - } - } - }, KeyDownEvent.getType()); - } - - // For some reason ONMOUSEOUT events are not always received, so we have - // to use ONMOUSEMOVE that doesn't target the popup - @Override - public boolean onEventPreview(Event event) { - Element target = DOM.eventGetTarget(event); - boolean eventTargetsPopup = DOM.isOrHasChild(getElement(), target); - int type = DOM.eventGetType(event); - - // Catch children that use keyboard, so we can unfocus them when - // hiding - if (eventTargetsPopup && type == Event.ONKEYPRESS) { - activeChildren.add(target); - } - - if (eventTargetsPopup && type == Event.ONMOUSEMOVE) { - hasHadMouseOver = true; - } - - if (!eventTargetsPopup && type == Event.ONMOUSEMOVE) { - if (hasHadMouseOver && hideOnMouseOut) { - hide(); - return true; - } - } - - // Was the TAB key released outside of our popup? - if (!eventTargetsPopup && type == Event.ONKEYUP - && event.getKeyCode() == KeyCodes.KEY_TAB) { - // Should we hide on focus out (mouse out)? - if (hideOnMouseOut) { - hide(); - return true; - } - } - - return super.onEventPreview(event); - } - - @Override - public void hide(boolean autoClosed) { - VConsole.log("Hiding popupview"); - syncChildren(); - clearPopupComponentConnector(); - hasHadMouseOver = false; - shortcutActionHandler = null; - super.hide(autoClosed); - } - - @Override - public void show() { - popupShowInProgress = true; - // Find the shortcut action handler that should handle keyboard - // events from the popup. The events do not propagate automatically - // because the popup is directly attached to the RootPanel. - - super.show(); - - /* - * Shortcut actions could be set (and currently in 7.2 they ARE SET - * via old style "updateFromUIDL" method, see f.e. UIConnector) - * AFTER method show() has been invoked (which is called as a - * reaction on change in component hierarchy). As a result there - * could be no shortcutActionHandler set yet. So let's postpone - * search of shortcutActionHandler. - */ - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - @Override - public void execute() { - try { - if (shortcutActionHandler == null) { - shortcutActionHandler = findShortcutActionHandler(); - } - } finally { - popupShowInProgress = false; - } - } - }); - } - - /** - * Try to sync all known active child widgets to server - */ - public void syncChildren() { - // Notify children with focus - if ((popupComponentWidget instanceof Focusable)) { - ((Focusable) popupComponentWidget).setFocus(false); - } else { - - checkForRTE(popupComponentWidget); - } - - // Notify children that have used the keyboard - for (Element e : activeChildren) { - try { - nativeBlur(e); - } catch (Exception ignored) { - } - } - activeChildren.clear(); - } - - private void checkForRTE(Widget popupComponentWidget2) { - if (popupComponentWidget2 instanceof VRichTextArea) { - ComponentConnector rtaConnector = Util - .findConnectorFor(popupComponentWidget2); - if (rtaConnector != null) { - rtaConnector.flush(); - } - } else if (popupComponentWidget2 instanceof HasWidgets) { - HasWidgets hw = (HasWidgets) popupComponentWidget2; - Iterator iterator = hw.iterator(); - while (iterator.hasNext()) { - checkForRTE(iterator.next()); - } - } - } - - private void clearPopupComponentConnector() { - if (popupComponentConnector != null) { - popupComponentConnector.removeStateChangeHandler(this); - } - popupComponentConnector = null; - popupComponentWidget = null; - captionWrapper = null; - } - - @Override - public boolean remove(Widget w) { - clearPopupComponentConnector(); - return super.remove(w); - } - - public void setPopupConnector(ComponentConnector newPopupComponent) { - - if (newPopupComponent != popupComponentConnector) { - if (popupComponentConnector != null) { - popupComponentConnector.removeStateChangeHandler(this); - } - Widget newWidget = newPopupComponent.getWidget(); - setWidget(newWidget); - popupComponentWidget = newWidget; - popupComponentConnector = newPopupComponent; - popupComponentConnector.addStateChangeHandler("height", this); - popupComponentConnector.addStateChangeHandler("width", this); - } - - } - - public void setHideOnMouseOut(boolean hideOnMouseOut) { - this.hideOnMouseOut = hideOnMouseOut; - } - - @Override - public com.google.gwt.user.client.Element getContainerElement() { - return super.getContainerElement(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - positionOrSizeUpdated(); - } - - private ShortcutActionHandler findShortcutActionHandler() { - Widget widget = VPopupView.this; - ShortcutActionHandler handler = null; - while (handler == null && widget != null) { - if (widget instanceof ShortcutActionHandlerOwner) { - handler = ((ShortcutActionHandlerOwner) widget) - .getShortcutActionHandler(); - } - widget = widget.getParent(); - } - return handler; - } - }// class CustomPopup - - public HandlerRegistration addVisibilityChangeHandler( - final VisibilityChangeHandler visibilityChangeHandler) { - return addHandler(visibilityChangeHandler, - VisibilityChangeEvent.getType()); - } - - @Override - public Iterator iterator() { - return Collections.singleton((Widget) popup).iterator(); - } - - /** - * Checks whether there are operations pending for this widget that must be - * executed before reaching a steady state. - * - * @returns true iff there are operations pending which must be - * executed before reaching a steady state - * @since 7.3.4 - */ - @Override - public boolean isWorkPending() { - return popupShowInProgress; - } - -}// class VPopupView diff --git a/client/src/com/vaadin/client/ui/VProgressBar.java b/client/src/com/vaadin/client/ui/VProgressBar.java deleted file mode 100644 index 348791728f..0000000000 --- a/client/src/com/vaadin/client/ui/VProgressBar.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.HasEnabled; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.StyleConstants; - -/** - * Widget for showing the current progress of a long running task. - *

- * The default mode is to show the current progress internally represented by a - * floating point value between 0 and 1 (inclusive). The progress bar can also - * be in an indeterminate mode showing an animation indicating that the task is - * running but without providing any information about the current progress. - * - * @since 7.1 - * @author Vaadin Ltd - */ -public class VProgressBar extends Widget implements HasEnabled { - - public static final String PRIMARY_STYLE_NAME = "v-progressbar"; - - Element wrapper = DOM.createDiv(); - Element indicator = DOM.createDiv(); - - private boolean indeterminate = false; - private float state = 0.0f; - private boolean enabled; - - public VProgressBar() { - setElement(DOM.createDiv()); - getElement().appendChild(wrapper); - wrapper.appendChild(indicator); - - setStylePrimaryName(PRIMARY_STYLE_NAME); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.UIObject#setStylePrimaryName(java.lang. - * String) - */ - @Override - public void setStylePrimaryName(String style) { - super.setStylePrimaryName(style); - indicator.setClassName(getStylePrimaryName() + "-indicator"); - wrapper.setClassName(getStylePrimaryName() + "-wrapper"); - - } - - public void setIndeterminate(boolean indeterminate) { - this.indeterminate = indeterminate; - setStyleName(getStylePrimaryName() + "-indeterminate", indeterminate); - } - - public void setState(float state) { - final int size = Math.round(100 * state); - indicator.getStyle().setWidth(size, Unit.PCT); - } - - public boolean isIndeterminate() { - return indeterminate; - } - - public float getState() { - return state; - } - - @Override - public boolean isEnabled() { - return enabled; - } - - @Override - public void setEnabled(boolean enabled) { - if (this.enabled != enabled) { - this.enabled = enabled; - setStyleName(StyleConstants.DISABLED, !enabled); - } - } -} diff --git a/client/src/com/vaadin/client/ui/VProgressIndicator.java b/client/src/com/vaadin/client/ui/VProgressIndicator.java deleted file mode 100644 index f93fa37af6..0000000000 --- a/client/src/com/vaadin/client/ui/VProgressIndicator.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.vaadin.shared.ui.progressindicator.ProgressIndicatorState; - -/** - * - * @author Vaadin Ltd - * - * @deprecated as of 7.1, renamed to VProgressBar - */ -@Deprecated -public class VProgressIndicator extends VProgressBar { - - public VProgressIndicator() { - super(); - setStylePrimaryName(ProgressIndicatorState.PRIMARY_STYLE_NAME); - } -} diff --git a/client/src/com/vaadin/client/ui/VRichTextArea.java b/client/src/com/vaadin/client/ui/VRichTextArea.java deleted file mode 100644 index cb4482f7cb..0000000000 --- a/client/src/com/vaadin/client/ui/VRichTextArea.java +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.KeyPressEvent; -import com.google.gwt.event.dom.client.KeyPressHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.Focusable; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.RichTextArea; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ConnectorMap; -import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; -import com.vaadin.client.ui.richtextarea.VRichTextToolbar; - -/** - * This class implements a basic client side rich text editor component. - * - * @author Vaadin Ltd. - * - */ -public class VRichTextArea extends Composite implements Field, KeyPressHandler, - KeyDownHandler, Focusable { - - /** - * The input node CSS classname. - */ - public static final String CLASSNAME = "v-richtextarea"; - - /** For internal use only. May be removed or replaced in the future. */ - public String id; - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean immediate = false; - - /** For internal use only. May be removed or replaced in the future. */ - public RichTextArea rta; - - /** For internal use only. May be removed or replaced in the future. */ - public VRichTextToolbar formatter; - - /** For internal use only. May be removed or replaced in the future. */ - public HTML html = new HTML(); - - private final FlowPanel fp = new FlowPanel(); - - private boolean enabled = true; - - /** For internal use only. May be removed or replaced in the future. */ - public int maxLength = -1; - - private int toolbarNaturalWidth = 500; - - /** For internal use only. May be removed or replaced in the future. */ - public HandlerRegistration keyPressHandler; - - private ShortcutActionHandlerOwner hasShortcutActionHandler; - - private boolean readOnly = false; - - private final Map blurHandlers = new HashMap(); - - public VRichTextArea() { - createRTAComponents(); - fp.add(formatter); - fp.add(rta); - - initWidget(fp); - setStyleName(CLASSNAME); - - TouchScrollDelegate.enableTouchScrolling(html, html.getElement()); - } - - private void createRTAComponents() { - rta = new RichTextArea(); - rta.setWidth("100%"); - rta.addKeyDownHandler(this); - formatter = new VRichTextToolbar(rta); - - // Add blur handlers - for (Entry handler : blurHandlers - .entrySet()) { - - // Remove old registration - handler.getValue().removeHandler(); - - // Add blur handlers - addBlurHandler(handler.getKey()); - } - } - - public void setEnabled(boolean enabled) { - if (this.enabled != enabled) { - // rta.setEnabled(enabled); - swapEditableArea(); - this.enabled = enabled; - } - } - - /** - * Swaps html to rta and visa versa. - */ - private void swapEditableArea() { - String value = getValue(); - if (html.isAttached()) { - fp.remove(html); - if (BrowserInfo.get().isWebkit()) { - fp.remove(formatter); - createRTAComponents(); // recreate new RTA to bypass #5379 - fp.add(formatter); - } - fp.add(rta); - } else { - fp.remove(rta); - fp.add(html); - } - setValue(value); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void selectAll() { - /* - * There is a timing issue if trying to select all immediately on first - * render. Simple deferred command is not enough. Using Timer with - * moderated timeout. If this appears to fail on many (most likely slow) - * environments, consider increasing the timeout. - * - * FF seems to require the most time to stabilize its RTA. On Vaadin - * tiergarden test machines, 200ms was not enough always (about 50% - * success rate) - 300 ms was 100% successful. This however was not - * enough on a sluggish old non-virtualized XP test machine. A bullet - * proof solution would be nice, GWT 2.1 might however solve these. At - * least setFocus has a workaround for this kind of issue. - */ - new Timer() { - @Override - public void run() { - rta.getFormatter().selectAll(); - } - }.schedule(320); - } - - public void setReadOnly(boolean b) { - if (isReadOnly() != b) { - swapEditableArea(); - readOnly = b; - } - // reset visibility in case enabled state changed and the formatter was - // recreated - formatter.setVisible(!readOnly); - } - - private boolean isReadOnly() { - return readOnly; - } - - @Override - public void setHeight(String height) { - super.setHeight(height); - if (height == null || height.equals("")) { - rta.setHeight(""); - } - } - - @Override - public void setWidth(String width) { - if (width.equals("")) { - /* - * IE cannot calculate the width of the 100% iframe correctly if - * there is no width specified for the parent. In this case we would - * use the toolbar but IE cannot calculate the width of that one - * correctly either in all cases. So we end up using a default width - * for a RichTextArea with no width definition in all browsers (for - * compatibility). - */ - - super.setWidth(toolbarNaturalWidth + "px"); - } else { - super.setWidth(width); - } - } - - @Override - public void onKeyPress(KeyPressEvent event) { - if (maxLength >= 0) { - Scheduler.get().scheduleDeferred(new Command() { - @Override - public void execute() { - if (rta.getHTML().length() > maxLength) { - rta.setHTML(rta.getHTML().substring(0, maxLength)); - } - } - }); - } - } - - @Override - public void onKeyDown(KeyDownEvent event) { - // delegate to closest shortcut action handler - // throw event from the iframe forward to the shortcuthandler - ShortcutActionHandler shortcutHandler = getShortcutHandlerOwner() - .getShortcutActionHandler(); - if (shortcutHandler != null) { - shortcutHandler - .handleKeyboardEvent(com.google.gwt.user.client.Event - .as(event.getNativeEvent()), - ConnectorMap.get(client).getConnector(this)); - } - } - - private ShortcutActionHandlerOwner getShortcutHandlerOwner() { - if (hasShortcutActionHandler == null) { - Widget parent = getParent(); - while (parent != null) { - if (parent instanceof ShortcutActionHandlerOwner) { - break; - } - parent = parent.getParent(); - } - hasShortcutActionHandler = (ShortcutActionHandlerOwner) parent; - } - return hasShortcutActionHandler; - } - - @Override - public int getTabIndex() { - return rta.getTabIndex(); - } - - @Override - public void setAccessKey(char key) { - rta.setAccessKey(key); - } - - @Override - public void setFocus(boolean focused) { - /* - * Similar issue as with selectAll. Focusing must happen before possible - * selectall, so keep the timeout here lower. - */ - new Timer() { - - @Override - public void run() { - rta.setFocus(true); - } - }.schedule(300); - } - - @Override - public void setTabIndex(int index) { - rta.setTabIndex(index); - } - - /** - * Set the value of the text area - * - * @param value - * The text value. Can be html. - */ - public void setValue(String value) { - if (rta.isAttached()) { - rta.setHTML(value); - } else { - html.setHTML(value); - } - } - - /** - * Get the value the text area - */ - public String getValue() { - if (rta.isAttached()) { - return rta.getHTML(); - } else { - return html.getHTML(); - } - } - - /** - * Browsers differ in what they return as the content of a visually empty - * rich text area. This method is used to normalize these to an empty - * string. See #8004. - * - * @return cleaned html string - */ - public String getSanitizedValue() { - BrowserInfo browser = BrowserInfo.get(); - String result = getValue(); - if (browser.isFirefox()) { - if ("
".equals(result)) { - result = ""; - } - } else if (browser.isWebkit() || browser.isEdge()) { - if ("
".equals(result) || "


".equals(result)) { - result = ""; - } - } else if (browser.isIE()) { - if ("

 

".equals(result)) { - result = ""; - } - } else if (browser.isOpera()) { - if ("
".equals(result) || "


".equals(result)) { - result = ""; - } - } - return result; - } - - /** - * Adds a blur handler to the component. - * - * @param blurHandler - * the blur handler to add - */ - public void addBlurHandler(BlurHandler blurHandler) { - blurHandlers.put(blurHandler, rta.addBlurHandler(blurHandler)); - } - - /** - * Removes a blur handler. - * - * @param blurHandler - * the handler to remove - */ - public void removeBlurHandler(BlurHandler blurHandler) { - HandlerRegistration registration = blurHandlers.remove(blurHandler); - if (registration != null) { - registration.removeHandler(); - } - } -} diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java deleted file mode 100644 index 5e5ae8a259..0000000000 --- a/client/src/com/vaadin/client/ui/VScrollTable.java +++ /dev/null @@ -1,8370 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.dom.client.Style.TextAlign; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.Style.Visibility; -import com.google.gwt.dom.client.TableCellElement; -import com.google.gwt.dom.client.TableRowElement; -import com.google.gwt.dom.client.TableSectionElement; -import com.google.gwt.dom.client.Touch; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.KeyPressEvent; -import com.google.gwt.event.dom.client.KeyPressHandler; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; -import com.google.gwt.event.dom.client.ScrollEvent; -import com.google.gwt.event.dom.client.ScrollHandler; -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.regexp.shared.MatchResult; -import com.google.gwt.regexp.shared.RegExp; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Event.NativePreviewEvent; -import com.google.gwt.user.client.Event.NativePreviewHandler; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HasWidgets; -import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.UIObject; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorMap; -import com.vaadin.client.DeferredWorker; -import com.vaadin.client.Focusable; -import com.vaadin.client.HasChildMeasurementHintConnector.ChildMeasurementHint; -import com.vaadin.client.MouseEventDetailsBuilder; -import com.vaadin.client.StyleConstants; -import com.vaadin.client.TooltipInfo; -import com.vaadin.client.UIDL; -import com.vaadin.client.Util; -import com.vaadin.client.VConsole; -import com.vaadin.client.VTooltip; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; -import com.vaadin.client.ui.dd.DDUtil; -import com.vaadin.client.ui.dd.VAbstractDropHandler; -import com.vaadin.client.ui.dd.VAcceptCallback; -import com.vaadin.client.ui.dd.VDragAndDropManager; -import com.vaadin.client.ui.dd.VDragEvent; -import com.vaadin.client.ui.dd.VHasDropHandler; -import com.vaadin.client.ui.dd.VTransferable; -import com.vaadin.shared.AbstractComponentState; -import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.ui.dd.VerticalDropLocation; -import com.vaadin.shared.ui.table.CollapseMenuContent; -import com.vaadin.shared.ui.table.TableConstants; - -/** - * VScrollTable - * - * VScrollTable is a FlowPanel having two widgets in it: * TableHead component * - * ScrollPanel - * - * TableHead contains table's header and widgets + logic for resizing, - * reordering and hiding columns. - * - * ScrollPanel contains VScrollTableBody object which handles content. To save - * some bandwidth and to improve clients responsiveness with loads of data, in - * VScrollTableBody all rows are not necessary rendered. There are "spacers" in - * VScrollTableBody to use the exact same space as non-rendered rows would use. - * This way we can use seamlessly traditional scrollbars and scrolling to fetch - * more rows instead of "paging". - * - * In VScrollTable we listen to scroll events. On horizontal scrolling we also - * update TableHeads scroll position which has its scrollbars hidden. On - * vertical scroll events we will check if we are reaching the end of area where - * we have rows rendered and - * - * TODO implement unregistering for child components in Cells - */ -public class VScrollTable extends FlowPanel implements HasWidgets, - ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable, - ActionOwner, SubPartAware, DeferredWorker { - - /** - * Simple interface for parts of the table capable of owning a context menu. - * - * @since 7.2 - * @author Vaadin Ltd - */ - private interface ContextMenuOwner { - public void showContextMenu(Event event); - } - - /** - * Handles showing context menu on "long press" from a touch screen. - * - * @since 7.2 - * @author Vaadin Ltd - */ - private class TouchContextProvider { - private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500; - private Timer contextTouchTimeout; - - private Event touchStart; - private int touchStartY; - private int touchStartX; - - private ContextMenuOwner target; - - /** - * Initializes a handler for a certain context menu owner. - * - * @param target - * the owner of the context menu - */ - public TouchContextProvider(ContextMenuOwner target) { - this.target = target; - } - - /** - * Cancels the current context touch timeout. - */ - public void cancel() { - if (contextTouchTimeout != null) { - contextTouchTimeout.cancel(); - contextTouchTimeout = null; - } - touchStart = null; - } - - /** - * A function to handle touch context events in a table. - * - * @param event - * browser event to handle - */ - public void handleTouchEvent(final Event event) { - int type = event.getTypeInt(); - - switch (type) { - case Event.ONCONTEXTMENU: - target.showContextMenu(event); - break; - case Event.ONTOUCHSTART: - // save position to fields, touches in events are same - // instance during the operation. - touchStart = event; - - Touch touch = event.getChangedTouches().get(0); - touchStartX = touch.getClientX(); - touchStartY = touch.getClientY(); - - if (contextTouchTimeout == null) { - contextTouchTimeout = new Timer() { - - @Override - public void run() { - if (touchStart != null) { - // Open the context menu if finger - // is held in place long enough. - target.showContextMenu(touchStart); - event.preventDefault(); - touchStart = null; - } - } - }; - } - contextTouchTimeout.schedule(TOUCH_CONTEXT_MENU_TIMEOUT); - break; - case Event.ONTOUCHCANCEL: - case Event.ONTOUCHEND: - cancel(); - break; - case Event.ONTOUCHMOVE: - if (isSignificantMove(event)) { - // Moved finger before the context menu timer - // expired, so let the browser handle the event. - cancel(); - } - } - } - - /** - * Calculates how many pixels away the user's finger has traveled. This - * reduces the chance of small non-intentional movements from canceling - * the long press detection. - * - * @param event - * the Event for which to check the move distance - * @return true if this is considered an intentional move by the user - */ - protected boolean isSignificantMove(Event event) { - if (touchStart == null) { - // no touch start - return false; - } - - // Calculate the distance between touch start and the current touch - // position - Touch touch = event.getChangedTouches().get(0); - int deltaX = touch.getClientX() - touchStartX; - int deltaY = touch.getClientY() - touchStartY; - int delta = deltaX * deltaX + deltaY * deltaY; - - // Compare to the square of the significant move threshold to remove - // the need for a square root - if (delta > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD - * TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) { - return true; - } - return false; - } - } - - public static final String STYLENAME = "v-table"; - - public enum SelectMode { - NONE(0), SINGLE(1), MULTI(2); - private int id; - - private SelectMode(int id) { - this.id = id; - } - - public int getId() { - return id; - } - } - - private static final String ROW_HEADER_COLUMN_KEY = "0"; - - private static final double CACHE_RATE_DEFAULT = 2; - - /** - * The default multi select mode where simple left clicks only selects one - * item, CTRL+left click selects multiple items and SHIFT-left click selects - * a range of items. - */ - private static final int MULTISELECT_MODE_DEFAULT = 0; - - /** - * The simple multiselect mode is what the table used to have before - * ctrl/shift selections were added. That is that when this is set clicking - * on an item selects/deselects the item and no ctrl/shift selections are - * available. - */ - private static final int MULTISELECT_MODE_SIMPLE = 1; - - /** - * multiple of pagelength which component will cache when requesting more - * rows - */ - private double cache_rate = CACHE_RATE_DEFAULT; - /** - * fraction of pageLength which can be scrolled without making new request - */ - private double cache_react_rate = 0.75 * cache_rate; - - public static final char ALIGN_CENTER = 'c'; - public static final char ALIGN_LEFT = 'b'; - public static final char ALIGN_RIGHT = 'e'; - private static final int CHARCODE_SPACE = 32; - private int firstRowInViewPort = 0; - private int pageLength = 15; - private int lastRequestedFirstvisible = 0; // to detect "serverside scroll" - private int firstvisibleOnLastPage = -1; // To detect if the first visible - // is on the last page - - /** For internal use only. May be removed or replaced in the future. */ - public boolean showRowHeaders = false; - - private String[] columnOrder; - - protected ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - public String paintableId; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean immediate; - - private boolean updatedReqRows = true; - - private boolean nullSelectionAllowed = true; - - private SelectMode selectMode = SelectMode.NONE; - - public final HashSet selectedRowKeys = new HashSet(); - - /* - * When scrolling and selecting at the same time, the selections are not in - * sync with the server while retrieving new rows (until key is released). - */ - private HashSet unSyncedselectionsBeforeRowFetch; - - /* - * These are used when jumping between pages when pressing Home and End - */ - - /** For internal use only. May be removed or replaced in the future. */ - public boolean selectLastItemInNextRender = false; - /** For internal use only. May be removed or replaced in the future. */ - public boolean selectFirstItemInNextRender = false; - /** For internal use only. May be removed or replaced in the future. */ - public boolean focusFirstItemInNextRender = false; - /** For internal use only. May be removed or replaced in the future. */ - public boolean focusLastItemInNextRender = false; - - /** - * The currently focused row. - *

- * For internal use only. May be removed or replaced in the future. - */ - public VScrollTableRow focusedRow; - - /** - * Helper to store selection range start in when using the keyboard - *

- * For internal use only. May be removed or replaced in the future. - */ - public VScrollTableRow selectionRangeStart; - - /** - * Flag for notifying when the selection has changed and should be sent to - * the server - *

- * For internal use only. May be removed or replaced in the future. - */ - public boolean selectionChanged = false; - - /* - * The speed (in pixels) which the scrolling scrolls vertically/horizontally - */ - private int scrollingVelocity = 10; - - private Timer scrollingVelocityTimer = null; - - /** For internal use only. May be removed or replaced in the future. */ - public String[] bodyActionKeys; - - private boolean enableDebug = false; - - private static final boolean hasNativeTouchScrolling = BrowserInfo.get() - .isTouchDevice() - && !BrowserInfo.get().requiresTouchScrollDelegate(); - - private Set noncollapsibleColumns; - - /** - * The last known row height used to preserve the height of a table with - * custom row heights and a fixed page length after removing the last row - * from the table. - * - * A new VScrollTableBody instance is created every time the number of rows - * changes causing {@link VScrollTableBody#rowHeight} to be discarded and - * the height recalculated by {@link VScrollTableBody#getRowHeight(boolean)} - * to avoid some rounding problems, e.g. round(2 * 19.8) / 2 = 20 but - * round(3 * 19.8) / 3 = 19.66. - */ - private double lastKnownRowHeight = Double.NaN; - - /** - * Remember scroll position when getting detached to properly scroll back to - * the location that there is data for if getting attached again. - */ - private int detachedScrollPosition = 0; - - /** - * Represents a select range of rows - */ - private class SelectionRange { - private VScrollTableRow startRow; - private final int length; - - /** - * Constuctor. - */ - public SelectionRange(VScrollTableRow row1, VScrollTableRow row2) { - VScrollTableRow endRow; - if (row2.isBefore(row1)) { - startRow = row2; - endRow = row1; - } else { - startRow = row1; - endRow = row2; - } - length = endRow.getIndex() - startRow.getIndex() + 1; - } - - public SelectionRange(VScrollTableRow row, int length) { - startRow = row; - this.length = length; - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#toString() - */ - - @Override - public String toString() { - return startRow.getKey() + "-" + length; - } - - private boolean inRange(VScrollTableRow row) { - return row.getIndex() >= startRow.getIndex() - && row.getIndex() < startRow.getIndex() + length; - } - - public Collection split(VScrollTableRow row) { - assert row.isAttached(); - ArrayList ranges = new ArrayList(2); - - int endOfFirstRange = row.getIndex() - 1; - if (endOfFirstRange >= startRow.getIndex()) { - // create range of first part unless its length is < 1 - ranges.add(new SelectionRange(startRow, endOfFirstRange - - startRow.getIndex() + 1)); - } - int startOfSecondRange = row.getIndex() + 1; - if (getEndIndex() >= startOfSecondRange) { - // create range of second part unless its length is < 1 - VScrollTableRow startOfRange = scrollBody - .getRowByRowIndex(startOfSecondRange); - if (startOfRange != null) { - ranges.add(new SelectionRange(startOfRange, getEndIndex() - - startOfSecondRange + 1)); - } - } - return ranges; - } - - private int getEndIndex() { - return startRow.getIndex() + length - 1; - } - - } - - private final HashSet selectedRowRanges = new HashSet(); - - /** For internal use only. May be removed or replaced in the future. */ - public boolean initializedAndAttached = false; - - /** - * Flag to indicate if a column width recalculation is needed due update. - *

- * For internal use only. May be removed or replaced in the future. - */ - public boolean headerChangedDuringUpdate = false; - - /** For internal use only. May be removed or replaced in the future. */ - public final TableHead tHead = new TableHead(); - - /** For internal use only. May be removed or replaced in the future. */ - public final TableFooter tFoot = new TableFooter(); - - /** Handles context menu for table body */ - private ContextMenuOwner contextMenuOwner = new ContextMenuOwner() { - - @Override - public void showContextMenu(Event event) { - int left = WidgetUtil.getTouchOrMouseClientX(event); - int top = WidgetUtil.getTouchOrMouseClientY(event); - boolean menuShown = handleBodyContextMenu(left, top); - if (menuShown) { - event.stopPropagation(); - event.preventDefault(); - } - } - }; - - /** Handles touch events to display a context menu for table body */ - private TouchContextProvider touchContextProvider = new TouchContextProvider( - contextMenuOwner); - - /** - * For internal use only. May be removed or replaced in the future. - * - * Overwrites onBrowserEvent function on FocusableScrollPanel to give event - * access to touchContextProvider. Has to be public to give TableConnector - * access to the scrollBodyPanel field. - * - * @since 7.2 - * @author Vaadin Ltd - */ - public class FocusableScrollContextPanel extends FocusableScrollPanel { - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - touchContextProvider.handleTouchEvent(event); - }; - - public FocusableScrollContextPanel(boolean useFakeFocusElement) { - super(useFakeFocusElement); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public final FocusableScrollContextPanel scrollBodyPanel = new FocusableScrollContextPanel( - true); - - private KeyPressHandler navKeyPressHandler = new KeyPressHandler() { - - @Override - public void onKeyPress(KeyPressEvent keyPressEvent) { - // This is used for Firefox only, since Firefox auto-repeat - // works correctly only if we use a key press handler, other - // browsers handle it correctly when using a key down handler - if (!BrowserInfo.get().isGecko()) { - return; - } - - NativeEvent event = keyPressEvent.getNativeEvent(); - if (!enabled) { - // Cancel default keyboard events on a disabled Table - // (prevents scrolling) - event.preventDefault(); - } else if (hasFocus) { - // Key code in Firefox/onKeyPress is present only for - // special keys, otherwise 0 is returned - int keyCode = event.getKeyCode(); - if (keyCode == 0 && event.getCharCode() == ' ') { - // Provide a keyCode for space to be compatible with - // FireFox keypress event - keyCode = CHARCODE_SPACE; - } - - if (handleNavigation(keyCode, - event.getCtrlKey() || event.getMetaKey(), - event.getShiftKey())) { - event.preventDefault(); - } - - startScrollingVelocityTimer(); - } - } - - }; - - private KeyUpHandler navKeyUpHandler = new KeyUpHandler() { - - @Override - public void onKeyUp(KeyUpEvent keyUpEvent) { - NativeEvent event = keyUpEvent.getNativeEvent(); - int keyCode = event.getKeyCode(); - - if (!isFocusable()) { - cancelScrollingVelocityTimer(); - } else if (isNavigationKey(keyCode)) { - if (keyCode == getNavigationDownKey() - || keyCode == getNavigationUpKey()) { - /* - * in multiselect mode the server may still have value from - * previous page. Clear it unless doing multiselection or - * just moving focus. - */ - if (!event.getShiftKey() && !event.getCtrlKey()) { - instructServerToForgetPreviousSelections(); - } - sendSelectedRows(); - } - cancelScrollingVelocityTimer(); - navKeyDown = false; - } - } - }; - - private KeyDownHandler navKeyDownHandler = new KeyDownHandler() { - - @Override - public void onKeyDown(KeyDownEvent keyDownEvent) { - NativeEvent event = keyDownEvent.getNativeEvent(); - // This is not used for Firefox - if (BrowserInfo.get().isGecko()) { - return; - } - - if (!enabled) { - // Cancel default keyboard events on a disabled Table - // (prevents scrolling) - event.preventDefault(); - } else if (hasFocus) { - if (handleNavigation(event.getKeyCode(), event.getCtrlKey() - || event.getMetaKey(), event.getShiftKey())) { - navKeyDown = true; - event.preventDefault(); - } - - startScrollingVelocityTimer(); - } - } - }; - - /** For internal use only. May be removed or replaced in the future. */ - public int totalRows; - - private Set collapsedColumns; - - /** For internal use only. May be removed or replaced in the future. */ - public final RowRequestHandler rowRequestHandler; - - /** For internal use only. May be removed or replaced in the future. */ - public VScrollTableBody scrollBody; - - private int firstvisible = 0; - private boolean sortAscending; - private String sortColumn; - private String oldSortColumn; - private boolean columnReordering; - - /** - * This map contains captions and icon urls for actions like: * "33_c" -> - * "Edit" * "33_i" -> "http://dom.com/edit.png" - */ - private final HashMap actionMap = new HashMap(); - private String[] visibleColOrder; - private boolean initialContentReceived = false; - private Element scrollPositionElement; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean enabled; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean showColHeaders; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean showColFooters; - - /** flag to indicate that table body has changed */ - private boolean isNewBody = true; - - /** - * Read from the "recalcWidths" -attribute. When it is true, the table will - * recalculate the widths for columns - desirable in some cases. For #1983, - * marked experimental. See also variable refreshContentWidths - * in method {@link TableHead#updateCellsFromUIDL(UIDL)}. - *

- * For internal use only. May be removed or replaced in the future. - */ - public boolean recalcWidths = false; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean rendering = false; - - private boolean hasFocus = false; - private int dragmode; - - private int multiselectmode; - - /** - * Hint for how to handle measurement of child components - */ - private ChildMeasurementHint childMeasurementHint = ChildMeasurementHint.MEASURE_ALWAYS; - - /** For internal use only. May be removed or replaced in the future. */ - public int tabIndex; - - private TouchScrollDelegate touchScrollDelegate; - - /** For internal use only. May be removed or replaced in the future. */ - public int lastRenderedHeight; - - /** - * Values (serverCacheFirst+serverCacheLast) sent by server that tells which - * rows (indexes) are in the server side cache (page buffer). -1 means - * unknown. The server side cache row MUST MATCH the client side cache rows. - * - * If the client side cache contains additional rows with e.g. buttons, it - * will cause out of sync when such a button is pressed. - * - * If the server side cache contains additional rows with e.g. buttons, - * scrolling in the client will cause empty buttons to be rendered - * (cached=true request for non-existing components) - * - * For internal use only. May be removed or replaced in the future. - */ - public int serverCacheFirst = -1; - public int serverCacheLast = -1; - - /** - * In several cases TreeTable depends on the scrollBody.lastRendered being - * 'out of sync' while the update is being done. In those cases the sanity - * check must be performed afterwards. - */ - public boolean postponeSanityCheckForLastRendered; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean sizeNeedsInit = true; - - /** - * Used to recall the position of an open context menu if we need to close - * and reopen it during a row update. - *

- * For internal use only. May be removed or replaced in the future. - */ - public class ContextMenuDetails implements CloseHandler { - public String rowKey; - public int left; - public int top; - HandlerRegistration closeRegistration; - - public ContextMenuDetails(VContextMenu menu, String rowKey, int left, - int top) { - this.rowKey = rowKey; - this.left = left; - this.top = top; - closeRegistration = menu.addCloseHandler(this); - } - - @Override - public void onClose(CloseEvent event) { - contextMenu = null; - closeRegistration.removeHandler(); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public ContextMenuDetails contextMenu = null; - - private boolean hadScrollBars = false; - - private HandlerRegistration addCloseHandler; - - /** - * Changes to manage mouseDown and mouseUp - */ - /** - * The element where the last mouse down event was registered. - */ - private Element lastMouseDownTarget; - - /** - * Set to true by {@link #mouseUpPreviewHandler} if it gets a mouseup at the - * same element as {@link #lastMouseDownTarget}. - */ - private boolean mouseUpPreviewMatched = false; - - private HandlerRegistration mouseUpEventPreviewRegistration; - - /** - * Previews events after a mousedown to detect where the following mouseup - * hits. - */ - private final NativePreviewHandler mouseUpPreviewHandler = new NativePreviewHandler() { - - @Override - public void onPreviewNativeEvent(NativePreviewEvent event) { - if (event.getTypeInt() == Event.ONMOUSEUP) { - mouseUpEventPreviewRegistration.removeHandler(); - - // Event's reported target not always correct if event - // capture is in use - Element elementUnderMouse = WidgetUtil - .getElementUnderMouse(event.getNativeEvent()); - if (lastMouseDownTarget != null - && lastMouseDownTarget.isOrHasChild(elementUnderMouse)) { - mouseUpPreviewMatched = true; - } else { - getLogger().log( - Level.FINEST, - "Ignoring mouseup from " + elementUnderMouse - + " when mousedown was on " - + lastMouseDownTarget); - } - } - } - }; - - public VScrollTable() { - setMultiSelectMode(MULTISELECT_MODE_DEFAULT); - - scrollBodyPanel.addFocusHandler(this); - scrollBodyPanel.addBlurHandler(this); - - scrollBodyPanel.addScrollHandler(this); - - /* - * Firefox auto-repeat works correctly only if we use a key press - * handler, other browsers handle it correctly when using a key down - * handler - */ - if (BrowserInfo.get().isGecko()) { - scrollBodyPanel.addKeyPressHandler(navKeyPressHandler); - } else { - scrollBodyPanel.addKeyDownHandler(navKeyDownHandler); - } - scrollBodyPanel.addKeyUpHandler(navKeyUpHandler); - - scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS | Event.ONCONTEXTMENU); - - setStyleName(STYLENAME); - - add(tHead); - add(scrollBodyPanel); - add(tFoot); - - rowRequestHandler = new RowRequestHandler(); - } - - @Override - public void setStyleName(String style) { - updateStyleNames(style, false); - } - - @Override - public void setStylePrimaryName(String style) { - updateStyleNames(style, true); - } - - private void updateStyleNames(String newStyle, boolean isPrimary) { - scrollBodyPanel - .removeStyleName(getStylePrimaryName() + "-body-wrapper"); - scrollBodyPanel.removeStyleName(getStylePrimaryName() + "-body"); - - if (scrollBody != null) { - scrollBody.removeStyleName(getStylePrimaryName() - + "-body-noselection"); - } - - if (isPrimary) { - super.setStylePrimaryName(newStyle); - } else { - super.setStyleName(newStyle); - } - - scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body-wrapper"); - scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body"); - - tHead.updateStyleNames(getStylePrimaryName()); - tFoot.updateStyleNames(getStylePrimaryName()); - - if (scrollBody != null) { - scrollBody.updateStyleNames(getStylePrimaryName()); - } - } - - public void init(ApplicationConnection client) { - this.client = client; - // Add a handler to clear saved context menu details when the menu - // closes. See #8526. - addCloseHandler = client.getContextMenu().addCloseHandler( - new CloseHandler() { - - @Override - public void onClose(CloseEvent event) { - contextMenu = null; - } - }); - } - - /** - * Handles a context menu event on table body. - * - * @param left - * left position of the context menu - * @param top - * top position of the context menu - * @return true if a context menu was shown, otherwise false - */ - private boolean handleBodyContextMenu(int left, int top) { - if (enabled && bodyActionKeys != null) { - top += Window.getScrollTop(); - left += Window.getScrollLeft(); - client.getContextMenu().showAt(this, left, top); - return true; - } - return false; - } - - /** - * Fires a column resize event which sends the resize information to the - * server. - * - * @param columnId - * The columnId of the column which was resized - * @param originalWidth - * The width in pixels of the column before the resize event - * @param newWidth - * The width in pixels of the column after the resize event - */ - private void fireColumnResizeEvent(String columnId, int originalWidth, - int newWidth) { - client.updateVariable(paintableId, "columnResizeEventColumn", columnId, - false); - client.updateVariable(paintableId, "columnResizeEventPrev", - originalWidth, false); - client.updateVariable(paintableId, "columnResizeEventCurr", newWidth, - immediate); - - } - - /** - * Non-immediate variable update of column widths for a collection of - * columns. - * - * @param columns - * the columns to trigger the events for. - */ - private void sendColumnWidthUpdates(Collection columns) { - String[] newSizes = new String[columns.size()]; - int ix = 0; - for (HeaderCell cell : columns) { - newSizes[ix++] = cell.getColKey() + ":" + cell.getWidth(); - } - client.updateVariable(paintableId, "columnWidthUpdates", newSizes, - false); - } - - /** - * Moves the focus one step down - * - * @return Returns true if succeeded - */ - private boolean moveFocusDown() { - return moveFocusDown(0); - } - - /** - * Moves the focus down by 1+offset rows - * - * @return Returns true if succeeded, else false if the selection could not - * be move downwards - */ - private boolean moveFocusDown(int offset) { - if (isSelectable()) { - if (focusedRow == null && scrollBody.iterator().hasNext()) { - // FIXME should focus first visible from top, not first rendered - // ?? - return setRowFocus((VScrollTableRow) scrollBody.iterator() - .next()); - } else { - VScrollTableRow next = getNextRow(focusedRow, offset); - if (next != null) { - return setRowFocus(next); - } - } - } - - return false; - } - - /** - * Moves the selection one step up - * - * @return Returns true if succeeded - */ - private boolean moveFocusUp() { - return moveFocusUp(0); - } - - /** - * Moves the focus row upwards - * - * @return Returns true if succeeded, else false if the selection could not - * be move upwards - * - */ - private boolean moveFocusUp(int offset) { - if (isSelectable()) { - if (focusedRow == null && scrollBody.iterator().hasNext()) { - // FIXME logic is exactly the same as in moveFocusDown, should - // be the opposite?? - return setRowFocus((VScrollTableRow) scrollBody.iterator() - .next()); - } else { - VScrollTableRow prev = getPreviousRow(focusedRow, offset); - if (prev != null) { - return setRowFocus(prev); - } else { - VConsole.log("no previous available"); - } - } - } - - return false; - } - - /** - * Selects a row where the current selection head is - * - * @param ctrlSelect - * Is the selection a ctrl+selection - * @param shiftSelect - * Is the selection a shift+selection - * @return Returns truw - */ - private void selectFocusedRow(boolean ctrlSelect, boolean shiftSelect) { - if (focusedRow != null) { - // Arrows moves the selection and clears previous selections - if (isSelectable() && !ctrlSelect && !shiftSelect) { - deselectAll(); - focusedRow.toggleSelection(); - selectionRangeStart = focusedRow; - } else if (isSelectable() && ctrlSelect && !shiftSelect) { - // Ctrl+arrows moves selection head - selectionRangeStart = focusedRow; - // No selection, only selection head is moved - } else if (isMultiSelectModeAny() && !ctrlSelect && shiftSelect) { - // Shift+arrows selection selects a range - focusedRow.toggleShiftSelection(shiftSelect); - } - } - } - - /** - * Sends the selection to the server if changed since the last update/visit. - */ - protected void sendSelectedRows() { - sendSelectedRows(immediate); - } - - private void updateFirstVisibleAndSendSelectedRows() { - updateFirstVisibleRow(); - sendSelectedRows(immediate); - } - - /** - * Sends the selection to the server if it has been changed since the last - * update/visit. - * - * @param immediately - * set to true to immediately send the rows - */ - protected void sendSelectedRows(boolean immediately) { - // Don't send anything if selection has not changed - if (!selectionChanged) { - return; - } - - // Reset selection changed flag - selectionChanged = false; - - // Note: changing the immediateness of this might require changes to - // "clickEvent" immediateness also. - if (isMultiSelectModeDefault()) { - // Convert ranges to a set of strings - Set ranges = new HashSet(); - for (SelectionRange range : selectedRowRanges) { - ranges.add(range.toString()); - } - - // Send the selected row ranges - client.updateVariable(paintableId, "selectedRanges", - ranges.toArray(new String[selectedRowRanges.size()]), false); - selectedRowRanges.clear(); - - // clean selectedRowKeys so that they don't contain excess values - for (Iterator iterator = selectedRowKeys.iterator(); iterator - .hasNext();) { - String key = iterator.next(); - VScrollTableRow renderedRowByKey = getRenderedRowByKey(key); - if (renderedRowByKey != null) { - for (SelectionRange range : selectedRowRanges) { - if (range.inRange(renderedRowByKey)) { - iterator.remove(); - } - } - } else { - // orphaned selected key, must be in a range, ignore - iterator.remove(); - } - - } - } - - // Send the selected rows - client.updateVariable(paintableId, "selected", - selectedRowKeys.toArray(new String[selectedRowKeys.size()]), - immediately); - - } - - /** - * Get the key that moves the selection head upwards. By default it is the - * up arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationUpKey() { - return KeyCodes.KEY_UP; - } - - /** - * Get the key that moves the selection head downwards. By default it is the - * down arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationDownKey() { - return KeyCodes.KEY_DOWN; - } - - /** - * Get the key that scrolls to the left in the table. By default it is the - * left arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationLeftKey() { - return KeyCodes.KEY_LEFT; - } - - /** - * Get the key that scroll to the right on the table. By default it is the - * right arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationRightKey() { - return KeyCodes.KEY_RIGHT; - } - - /** - * Get the key that selects an item in the table. By default it is the space - * bar key but by overriding this you can change the key to whatever you - * want. - * - * @return - */ - protected int getNavigationSelectKey() { - return CHARCODE_SPACE; - } - - /** - * Get the key the moves the selection one page up in the table. By default - * this is the Page Up key but by overriding this you can change the key to - * whatever you want. - * - * @return - */ - protected int getNavigationPageUpKey() { - return KeyCodes.KEY_PAGEUP; - } - - /** - * Get the key the moves the selection one page down in the table. By - * default this is the Page Down key but by overriding this you can change - * the key to whatever you want. - * - * @return - */ - protected int getNavigationPageDownKey() { - return KeyCodes.KEY_PAGEDOWN; - } - - /** - * Get the key the moves the selection to the beginning of the table. By - * default this is the Home key but by overriding this you can change the - * key to whatever you want. - * - * @return - */ - protected int getNavigationStartKey() { - return KeyCodes.KEY_HOME; - } - - /** - * Get the key the moves the selection to the end of the table. By default - * this is the End key but by overriding this you can change the key to - * whatever you want. - * - * @return - */ - protected int getNavigationEndKey() { - return KeyCodes.KEY_END; - } - - /** For internal use only. May be removed or replaced in the future. */ - public void initializeRows(UIDL uidl, UIDL rowData) { - if (scrollBody != null) { - scrollBody.removeFromParent(); - } - - // Without this call the scroll position is messed up in IE even after - // the lazy scroller has set the scroll position to the first visible - // item - int pos = scrollBodyPanel.getScrollPosition(); - - // Reset first row in view port so client requests correct last row. - if (pos == 0) { - firstRowInViewPort = 0; - } - - scrollBody = createScrollBody(); - - scrollBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"), - uidl.getIntAttribute("rows")); - scrollBodyPanel.add(scrollBody); - - initialContentReceived = true; - sizeNeedsInit = true; - scrollBody.restoreRowVisibility(); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateColumnProperties(UIDL uidl) { - updateColumnOrder(uidl); - - updateCollapsedColumns(uidl); - - UIDL vc = uidl.getChildByTagName("visiblecolumns"); - if (vc != null) { - tHead.updateCellsFromUIDL(vc); - tFoot.updateCellsFromUIDL(vc); - } - - updateHeader(uidl.getStringArrayAttribute("vcolorder")); - updateFooter(uidl.getStringArrayAttribute("vcolorder")); - if (uidl.hasVariable("noncollapsiblecolumns")) { - noncollapsibleColumns = uidl - .getStringArrayVariableAsSet("noncollapsiblecolumns"); - } - } - - private void updateCollapsedColumns(UIDL uidl) { - if (uidl.hasVariable("collapsedcolumns")) { - tHead.setColumnCollapsingAllowed(true); - collapsedColumns = uidl - .getStringArrayVariableAsSet("collapsedcolumns"); - } else { - tHead.setColumnCollapsingAllowed(false); - } - } - - private void updateColumnOrder(UIDL uidl) { - if (uidl.hasVariable("columnorder")) { - columnReordering = true; - columnOrder = uidl.getStringArrayVariable("columnorder"); - } else { - columnReordering = false; - columnOrder = null; - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public boolean selectSelectedRows(UIDL uidl) { - boolean keyboardSelectionOverRowFetchInProgress = false; - - if (uidl.hasVariable("selected")) { - final Set selectedKeys = uidl - .getStringArrayVariableAsSet("selected"); - // Do not update focus if there is a single selected row - // that is the same as the previous selection. This prevents - // unwanted scrolling (#18247). - boolean rowsUnSelected = removeUnselectedRowKeys(selectedKeys); - boolean updateFocus = rowsUnSelected || selectedRowKeys.size() == 0 - || focusedRow == null; - if (scrollBody != null) { - Iterator iterator = scrollBody.iterator(); - while (iterator.hasNext()) { - /* - * Make the focus reflect to the server side state unless we - * are currently selecting multiple rows with keyboard. - */ - VScrollTableRow row = (VScrollTableRow) iterator.next(); - boolean selected = selectedKeys.contains(row.getKey()); - if (!selected - && unSyncedselectionsBeforeRowFetch != null - && unSyncedselectionsBeforeRowFetch.contains(row - .getKey())) { - selected = true; - keyboardSelectionOverRowFetchInProgress = true; - } - if (selected && selectedKeys.size() == 1 && updateFocus) { - /* - * If a single item is selected, move focus to the - * selected row. (#10522) - */ - setRowFocus(row); - } - - if (selected != row.isSelected()) { - row.toggleSelection(); - - if (!isSingleSelectMode() && !selected) { - // Update selection range in case a row is - // unselected from the middle of a range - #8076 - removeRowFromUnsentSelectionRanges(row); - } - } - } - - } - } - unSyncedselectionsBeforeRowFetch = null; - return keyboardSelectionOverRowFetchInProgress; - } - - private boolean removeUnselectedRowKeys(final Set selectedKeys) { - List unselectedKeys = new ArrayList(0); - for (String key : selectedRowKeys) { - if (!selectedKeys.contains(key)) { - unselectedKeys.add(key); - } - } - return selectedRowKeys.removeAll(unselectedKeys); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateSortingProperties(UIDL uidl) { - oldSortColumn = sortColumn; - if (uidl.hasVariable("sortascending")) { - sortAscending = uidl.getBooleanVariable("sortascending"); - sortColumn = uidl.getStringVariable("sortcolumn"); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void resizeSortedColumnForSortIndicator() { - // Force recalculation of the captionContainer element inside the header - // cell to accomodate for the size of the sort arrow. - HeaderCell sortedHeader = tHead.getHeaderCell(sortColumn); - if (sortedHeader != null) { - // Mark header as sorted now. Any earlier marking would lead to - // columns with wrong sizes - sortedHeader.setSorted(true); - tHead.resizeCaptionContainer(sortedHeader); - } - // Also recalculate the width of the captionContainer element in the - // previously sorted header, since this now has more room. - HeaderCell oldSortedHeader = tHead.getHeaderCell(oldSortColumn); - if (oldSortedHeader != null) { - tHead.resizeCaptionContainer(oldSortedHeader); - } - } - - private boolean lazyScrollerIsActive; - - private void disableLazyScroller() { - lazyScrollerIsActive = false; - scrollBodyPanel.getElement().getStyle().clearOverflowX(); - scrollBodyPanel.getElement().getStyle().clearOverflowY(); - } - - private void enableLazyScroller() { - Scheduler.get().scheduleDeferred(lazyScroller); - lazyScrollerIsActive = true; - // prevent scrolling to jump in IE11 - scrollBodyPanel.getElement().getStyle().setOverflowX(Overflow.HIDDEN); - scrollBodyPanel.getElement().getStyle().setOverflowY(Overflow.HIDDEN); - } - - private boolean isLazyScrollerActive() { - return lazyScrollerIsActive; - } - - private ScheduledCommand lazyScroller = new ScheduledCommand() { - - @Override - public void execute() { - if (firstvisible >= 0) { - firstRowInViewPort = firstvisible; - if (firstvisibleOnLastPage > -1) { - scrollBodyPanel - .setScrollPosition(measureRowHeightOffset(firstvisibleOnLastPage)); - } else { - scrollBodyPanel - .setScrollPosition(measureRowHeightOffset(firstvisible)); - } - } - disableLazyScroller(); - } - }; - - /** For internal use only. May be removed or replaced in the future. */ - public void updateFirstVisibleAndScrollIfNeeded(UIDL uidl) { - firstvisible = uidl.hasVariable("firstvisible") ? uidl - .getIntVariable("firstvisible") : 0; - firstvisibleOnLastPage = uidl.hasVariable("firstvisibleonlastpage") ? uidl - .getIntVariable("firstvisibleonlastpage") : -1; - if (firstvisible != lastRequestedFirstvisible && scrollBody != null) { - - // Update lastRequestedFirstvisible right away here - // (don't rely on update in the timer which could be cancelled). - lastRequestedFirstvisible = firstRowInViewPort; - - // Only scroll if the first visible changes from the server side. - // Else we might unintentionally scroll even when the scroll - // position has not changed. - enableLazyScroller(); - } - } - - protected int measureRowHeightOffset(int rowIx) { - return (int) (rowIx * scrollBody.getRowHeight()); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updatePageLength(UIDL uidl) { - int oldPageLength = pageLength; - if (uidl.hasAttribute("pagelength")) { - pageLength = uidl.getIntAttribute("pagelength"); - } else { - // pagelength is "0" meaning scrolling is turned off - pageLength = totalRows; - } - - if (oldPageLength != pageLength && initializedAndAttached) { - // page length changed, need to update size - sizeNeedsInit = true; - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateSelectionProperties(UIDL uidl, - AbstractComponentState state, boolean readOnly) { - setMultiSelectMode(uidl.hasAttribute("multiselectmode") ? uidl - .getIntAttribute("multiselectmode") : MULTISELECT_MODE_DEFAULT); - - nullSelectionAllowed = uidl.hasAttribute("nsa") ? uidl - .getBooleanAttribute("nsa") : true; - - if (uidl.hasAttribute("selectmode")) { - if (readOnly) { - selectMode = SelectMode.NONE; - } else if (uidl.getStringAttribute("selectmode").equals("multi")) { - selectMode = SelectMode.MULTI; - } else if (uidl.getStringAttribute("selectmode").equals("single")) { - selectMode = SelectMode.SINGLE; - } else { - selectMode = SelectMode.NONE; - } - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateDragMode(UIDL uidl) { - dragmode = uidl.hasAttribute("dragmode") ? uidl - .getIntAttribute("dragmode") : 0; - if (BrowserInfo.get().isIE()) { - if (dragmode > 0) { - getElement().setPropertyJSO("onselectstart", - getPreventTextSelectionIEHack()); - } else { - getElement().setPropertyJSO("onselectstart", null); - } - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateTotalRows(UIDL uidl) { - int newTotalRows = uidl.getIntAttribute("totalrows"); - if (newTotalRows != getTotalRows()) { - if (scrollBody != null) { - if (getTotalRows() == 0) { - tHead.clear(); - tFoot.clear(); - } - initializedAndAttached = false; - initialContentReceived = false; - isNewBody = true; - } - setTotalRows(newTotalRows); - } - } - - protected void setTotalRows(int newTotalRows) { - totalRows = newTotalRows; - } - - public int getTotalRows() { - return totalRows; - } - - /** - * Returns the extra space that is given to the header column when column - * width is determined by header text. - * - * @return extra space in pixels - */ - private int getHeaderPadding() { - return scrollBody.getCellExtraWidth(); - } - - /** - * This method exists for the needs of {@link VTreeTable} only. Not part of - * the official API, extend at your own risk. May be removed or - * replaced in the future. - * - * @return index of TreeTable's hierarchy column, or -1 if not applicable - */ - protected int getHierarchyColumnIndex() { - return -1; - } - - /** - * For internal use only. May be removed or replaced in the future. - */ - public void updateMaxIndent() { - int oldIndent = scrollBody.getMaxIndent(); - scrollBody.calculateMaxIndent(); - if (oldIndent != scrollBody.getMaxIndent()) { - // indent updated, headers might need adjusting - triggerLazyColumnAdjustment(true); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void focusRowFromBody() { - if (selectedRowKeys.size() == 1) { - // try to focus a row currently selected and in viewport - String selectedRowKey = selectedRowKeys.iterator().next(); - if (selectedRowKey != null) { - VScrollTableRow renderedRow = getRenderedRowByKey(selectedRowKey); - if (renderedRow == null || !renderedRow.isInViewPort()) { - setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort)); - } else { - setRowFocus(renderedRow); - } - } - } else { - // multiselect mode - setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort)); - } - } - - protected VScrollTableBody createScrollBody() { - return new VScrollTableBody(); - } - - /** - * Selects the last row visible in the table - *

- * For internal use only. May be removed or replaced in the future. - * - * @param focusOnly - * Should the focus only be moved to the last row - */ - public void selectLastRenderedRowInViewPort(boolean focusOnly) { - int index = firstRowInViewPort + getFullyVisibleRowCount(); - VScrollTableRow lastRowInViewport = scrollBody.getRowByRowIndex(index); - if (lastRowInViewport == null) { - // this should not happen in normal situations (white space at the - // end of viewport). Select the last rendered as a fallback. - lastRowInViewport = scrollBody.getRowByRowIndex(scrollBody - .getLastRendered()); - if (lastRowInViewport == null) { - return; // empty table - } - } - setRowFocus(lastRowInViewport); - if (!focusOnly) { - selectFocusedRow(false, multiselectPending); - sendSelectedRows(); - } - } - - /** - * Selects the first row visible in the table - *

- * For internal use only. May be removed or replaced in the future. - * - * @param focusOnly - * Should the focus only be moved to the first row - */ - public void selectFirstRenderedRowInViewPort(boolean focusOnly) { - int index = firstRowInViewPort; - VScrollTableRow firstInViewport = scrollBody.getRowByRowIndex(index); - if (firstInViewport == null) { - // this should not happen in normal situations - return; - } - setRowFocus(firstInViewport); - if (!focusOnly) { - selectFocusedRow(false, multiselectPending); - sendSelectedRows(); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void setCacheRateFromUIDL(UIDL uidl) { - setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr") - : CACHE_RATE_DEFAULT); - } - - private void setCacheRate(double d) { - if (cache_rate != d) { - cache_rate = d; - cache_react_rate = 0.75 * d; - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateActionMap(UIDL mainUidl) { - UIDL actionsUidl = mainUidl.getChildByTagName("actions"); - if (actionsUidl == null) { - return; - } - - final Iterator it = actionsUidl.getChildIterator(); - while (it.hasNext()) { - final UIDL action = (UIDL) it.next(); - final String key = action.getStringAttribute("key"); - final String caption = action.getStringAttribute("caption"); - actionMap.put(key + "_c", caption); - if (action.hasAttribute("icon")) { - // TODO need some uri handling ?? - actionMap.put(key + "_i", action.getStringAttribute("icon")); - } else { - actionMap.remove(key + "_i"); - } - } - - } - - public String getActionCaption(String actionKey) { - return actionMap.get(actionKey + "_c"); - } - - public String getActionIcon(String actionKey) { - return client.translateVaadinUri(actionMap.get(actionKey + "_i")); - } - - private void updateHeader(String[] strings) { - if (strings == null) { - return; - } - - int visibleCols = strings.length; - int colIndex = 0; - if (showRowHeaders) { - tHead.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex); - visibleCols++; - visibleColOrder = new String[visibleCols]; - visibleColOrder[colIndex] = ROW_HEADER_COLUMN_KEY; - colIndex++; - } else { - visibleColOrder = new String[visibleCols]; - tHead.removeCell(ROW_HEADER_COLUMN_KEY); - } - - int i; - for (i = 0; i < strings.length; i++) { - final String cid = strings[i]; - visibleColOrder[colIndex] = cid; - tHead.enableColumn(cid, colIndex); - colIndex++; - } - - tHead.setVisible(showColHeaders); - setContainerHeight(); - - } - - /** - * Updates footers. - *

- * Update headers whould be called before this method is called! - *

- * - * @param strings - */ - private void updateFooter(String[] strings) { - if (strings == null) { - return; - } - - // Add dummy column if row headers are present - int colIndex = 0; - if (showRowHeaders) { - tFoot.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex); - colIndex++; - } else { - tFoot.removeCell(ROW_HEADER_COLUMN_KEY); - } - - int i; - for (i = 0; i < strings.length; i++) { - final String cid = strings[i]; - tFoot.enableColumn(cid, colIndex); - colIndex++; - } - - tFoot.setVisible(showColFooters); - } - - /** - * For internal use only. May be removed or replaced in the future. - * - * @param uidl - * which contains row data - * @param firstRow - * first row in data set - * @param reqRows - * amount of rows in data set - */ - public void updateBody(UIDL uidl, int firstRow, int reqRows) { - int oldIndent = scrollBody.getMaxIndent(); - if (uidl == null || reqRows < 1) { - // container is empty, remove possibly existing rows - if (firstRow <= 0) { - postponeSanityCheckForLastRendered = true; - while (scrollBody.getLastRendered() > scrollBody - .getFirstRendered()) { - scrollBody.unlinkRow(false); - } - postponeSanityCheckForLastRendered = false; - scrollBody.unlinkRow(false); - } - return; - } - - scrollBody.renderRows(uidl, firstRow, reqRows); - - discardRowsOutsideCacheWindow(); - scrollBody.calculateMaxIndent(); - if (oldIndent != scrollBody.getMaxIndent()) { - // indent updated, headers might need adjusting - headerChangedDuringUpdate = true; - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateRowsInBody(UIDL partialRowUpdates) { - if (partialRowUpdates == null) { - return; - } - int firstRowIx = partialRowUpdates.getIntAttribute("firsturowix"); - int count = partialRowUpdates.getIntAttribute("numurows"); - scrollBody.unlinkRows(firstRowIx, count); - scrollBody.insertRows(partialRowUpdates, firstRowIx, count); - } - - /** - * Updates the internal cache by unlinking rows that fall outside of the - * caching window. - */ - protected void discardRowsOutsideCacheWindow() { - int firstRowToKeep = (int) (firstRowInViewPort - pageLength - * cache_rate); - int lastRowToKeep = (int) (firstRowInViewPort + pageLength + pageLength - * cache_rate); - // sanity checks: - if (firstRowToKeep < 0) { - firstRowToKeep = 0; - } - if (lastRowToKeep > totalRows) { - lastRowToKeep = totalRows - 1; - } - debug("Client side calculated cache rows to keep: " + firstRowToKeep - + "-" + lastRowToKeep); - - if (serverCacheFirst != -1) { - firstRowToKeep = serverCacheFirst; - lastRowToKeep = serverCacheLast; - debug("Server cache rows that override: " + serverCacheFirst + "-" - + serverCacheLast); - if (firstRowToKeep < scrollBody.getFirstRendered() - || lastRowToKeep > scrollBody.getLastRendered()) { - debug("*** Server wants us to keep " + serverCacheFirst + "-" - + serverCacheLast + " but we only have rows " - + scrollBody.getFirstRendered() + "-" - + scrollBody.getLastRendered() + " rendered!"); - } - } - discardRowsOutsideOf(firstRowToKeep, lastRowToKeep); - - scrollBody.fixSpacers(); - - scrollBody.restoreRowVisibility(); - } - - private void discardRowsOutsideOf(int optimalFirstRow, int optimalLastRow) { - /* - * firstDiscarded and lastDiscarded are only calculated for debug - * purposes - */ - int firstDiscarded = -1, lastDiscarded = -1; - boolean cont = true; - while (cont && scrollBody.getLastRendered() > optimalFirstRow - && scrollBody.getFirstRendered() < optimalFirstRow) { - if (firstDiscarded == -1) { - firstDiscarded = scrollBody.getFirstRendered(); - } - - // removing row from start - cont = scrollBody.unlinkRow(true); - } - if (firstDiscarded != -1) { - lastDiscarded = scrollBody.getFirstRendered() - 1; - debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded); - } - firstDiscarded = lastDiscarded = -1; - - cont = true; - while (cont && scrollBody.getLastRendered() > optimalLastRow) { - if (lastDiscarded == -1) { - lastDiscarded = scrollBody.getLastRendered(); - } - - // removing row from the end - cont = scrollBody.unlinkRow(false); - } - if (lastDiscarded != -1) { - firstDiscarded = scrollBody.getLastRendered() + 1; - debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded); - } - - debug("Now in cache: " + scrollBody.getFirstRendered() + "-" - + scrollBody.getLastRendered()); - } - - /** - * Inserts rows in the table body or removes them from the table body based - * on the commands in the UIDL. - *

- * For internal use only. May be removed or replaced in the future. - * - * @param partialRowAdditions - * the UIDL containing row updates. - */ - public void addAndRemoveRows(UIDL partialRowAdditions) { - if (partialRowAdditions == null) { - return; - } - if (partialRowAdditions.hasAttribute("hide")) { - scrollBody.unlinkAndReindexRows( - partialRowAdditions.getIntAttribute("firstprowix"), - partialRowAdditions.getIntAttribute("numprows")); - scrollBody.ensureCacheFilled(); - } else { - if (partialRowAdditions.hasAttribute("delbelow")) { - scrollBody.insertRowsDeleteBelow(partialRowAdditions, - partialRowAdditions.getIntAttribute("firstprowix"), - partialRowAdditions.getIntAttribute("numprows")); - } else { - scrollBody.insertAndReindexRows(partialRowAdditions, - partialRowAdditions.getIntAttribute("firstprowix"), - partialRowAdditions.getIntAttribute("numprows")); - } - } - - discardRowsOutsideCacheWindow(); - } - - /** - * Gives correct column index for given column key ("cid" in UIDL). - * - * @param colKey - * @return column index of visible columns, -1 if column not visible - */ - private int getColIndexByKey(String colKey) { - // return 0 if asked for rowHeaders - if (ROW_HEADER_COLUMN_KEY.equals(colKey)) { - return 0; - } - for (int i = 0; i < visibleColOrder.length; i++) { - if (visibleColOrder[i].equals(colKey)) { - return i; - } - } - return -1; - } - - private boolean isMultiSelectModeSimple() { - return selectMode == SelectMode.MULTI - && multiselectmode == MULTISELECT_MODE_SIMPLE; - } - - private boolean isSingleSelectMode() { - return selectMode == SelectMode.SINGLE; - } - - private boolean isMultiSelectModeAny() { - return selectMode == SelectMode.MULTI; - } - - private boolean isMultiSelectModeDefault() { - return selectMode == SelectMode.MULTI - && multiselectmode == MULTISELECT_MODE_DEFAULT; - } - - private void setMultiSelectMode(int multiselectmode) { - if (BrowserInfo.get().isTouchDevice()) { - // Always use the simple mode for touch devices that do not have - // shift/ctrl keys - this.multiselectmode = MULTISELECT_MODE_SIMPLE; - } else { - this.multiselectmode = multiselectmode; - } - - } - - /** For internal use only. May be removed or replaced in the future. */ - public boolean isSelectable() { - return selectMode.getId() > SelectMode.NONE.getId(); - } - - private boolean isCollapsedColumn(String colKey) { - if (collapsedColumns == null) { - return false; - } - if (collapsedColumns.contains(colKey)) { - return true; - } - return false; - } - - private String getColKeyByIndex(int index) { - return tHead.getHeaderCell(index).getColKey(); - } - - /** - * Note: not part of the official API, extend at your own risk. May be - * removed or replaced in the future. - * - * Sets the indicated column's width for headers and scrollBody alike. - * - * @param colIndex - * index of the modified column - * @param w - * new width (may be subject to modifications if doesn't meet - * minimum requirements) - * @param isDefinedWidth - * disables expand ratio if set true - */ - protected void setColWidth(int colIndex, int w, boolean isDefinedWidth) { - final HeaderCell hcell = tHead.getHeaderCell(colIndex); - - // Make sure that the column grows to accommodate the sort indicator if - // necessary. - // get min width with no indent or padding - int minWidth = hcell.getMinWidth(false, false); - if (w < minWidth) { - w = minWidth; - } - - // Set header column width WITHOUT INDENT - hcell.setWidth(w, isDefinedWidth); - - // Set footer column width likewise - FooterCell fcell = tFoot.getFooterCell(colIndex); - fcell.setWidth(w, isDefinedWidth); - - // Ensure indicators have been taken into account - tHead.resizeCaptionContainer(hcell); - - // Make sure that the body column grows to accommodate the indent if - // necessary. - // get min width with indent, no padding - minWidth = hcell.getMinWidth(true, false); - if (w < minWidth) { - w = minWidth; - } - - // Set body column width - scrollBody.setColWidth(colIndex, w); - } - - private int getColWidth(String colKey) { - return tHead.getHeaderCell(colKey).getWidthWithIndent(); - } - - /** - * Get a rendered row by its key - * - * @param key - * The key to search with - * @return - */ - public VScrollTableRow getRenderedRowByKey(String key) { - if (scrollBody != null) { - final Iterator it = scrollBody.iterator(); - VScrollTableRow r = null; - while (it.hasNext()) { - r = (VScrollTableRow) it.next(); - if (r.getKey().equals(key)) { - return r; - } - } - } - return null; - } - - /** - * Returns the next row to the given row - * - * @param row - * The row to calculate from - * - * @return The next row or null if no row exists - */ - private VScrollTableRow getNextRow(VScrollTableRow row, int offset) { - final Iterator it = scrollBody.iterator(); - VScrollTableRow r = null; - while (it.hasNext()) { - r = (VScrollTableRow) it.next(); - if (r == row) { - r = null; - while (offset >= 0 && it.hasNext()) { - r = (VScrollTableRow) it.next(); - offset--; - } - return r; - } - } - - return null; - } - - /** - * Returns the previous row from the given row - * - * @param row - * The row to calculate from - * @return The previous row or null if no row exists - */ - private VScrollTableRow getPreviousRow(VScrollTableRow row, int offset) { - final Iterator it = scrollBody.iterator(); - final Iterator offsetIt = scrollBody.iterator(); - VScrollTableRow r = null; - VScrollTableRow prev = null; - while (it.hasNext()) { - r = (VScrollTableRow) it.next(); - if (offset < 0) { - prev = (VScrollTableRow) offsetIt.next(); - } - if (r == row) { - return prev; - } - offset--; - } - - return null; - } - - protected void reOrderColumn(String columnKey, int newIndex) { - - final int oldIndex = getColIndexByKey(columnKey); - - // Change header order - tHead.moveCell(oldIndex, newIndex); - - // Change body order - scrollBody.moveCol(oldIndex, newIndex); - - // Change footer order - tFoot.moveCell(oldIndex, newIndex); - - /* - * Build new columnOrder and update it to server Note that columnOrder - * also contains collapsed columns so we cannot directly build it from - * cells vector Loop the old columnOrder and append in order to new - * array unless on moved columnKey. On new index also put the moved key - * i == index on columnOrder, j == index on newOrder - */ - final String oldKeyOnNewIndex = visibleColOrder[newIndex]; - if (showRowHeaders) { - newIndex--; // columnOrder don't have rowHeader - } - // add back hidden rows, - for (int i = 0; i < columnOrder.length; i++) { - if (columnOrder[i].equals(oldKeyOnNewIndex)) { - break; // break loop at target - } - if (isCollapsedColumn(columnOrder[i])) { - newIndex++; - } - } - // finally we can build the new columnOrder for server - final String[] newOrder = new String[columnOrder.length]; - for (int i = 0, j = 0; j < newOrder.length; i++) { - if (j == newIndex) { - newOrder[j] = columnKey; - j++; - } - if (i == columnOrder.length) { - break; - } - if (columnOrder[i].equals(columnKey)) { - continue; - } - newOrder[j] = columnOrder[i]; - j++; - } - columnOrder = newOrder; - // also update visibleColumnOrder - int i = showRowHeaders ? 1 : 0; - for (int j = 0; j < newOrder.length; j++) { - final String cid = newOrder[j]; - if (!isCollapsedColumn(cid)) { - visibleColOrder[i++] = cid; - } - } - client.updateVariable(paintableId, "columnorder", columnOrder, false); - if (client.hasEventListeners(this, - TableConstants.COLUMN_REORDER_EVENT_ID)) { - client.sendPendingVariableChanges(); - } - } - - @Override - protected void onDetach() { - detachedScrollPosition = scrollBodyPanel.getScrollPosition(); - rowRequestHandler.cancel(); - super.onDetach(); - // ensure that scrollPosElement will be detached - if (scrollPositionElement != null) { - final Element parent = DOM.getParent(scrollPositionElement); - if (parent != null) { - DOM.removeChild(parent, scrollPositionElement); - } - } - } - - @Override - public void onAttach() { - super.onAttach(); - scrollBodyPanel.setScrollPosition(detachedScrollPosition); - } - - /** - * Run only once when component is attached and received its initial - * content. This function: - * - * * Syncs headers and bodys "natural widths and saves the values. - * - * * Sets proper width and height - * - * * Makes deferred request to get some cache rows - * - * For internal use only. May be removed or replaced in the future. - */ - public void sizeInit() { - sizeNeedsInit = false; - - scrollBody.setContainerHeight(); - - /* - * We will use browsers table rendering algorithm to find proper column - * widths. If content and header take less space than available, we will - * divide extra space relatively to each column which has not width set. - * - * Overflow pixels are added to last column. - */ - - Iterator headCells = tHead.iterator(); - Iterator footCells = tFoot.iterator(); - int i = 0; - int totalExplicitColumnsWidths = 0; - int total = 0; - float expandRatioDivider = 0; - - final int[] widths = new int[tHead.visibleCells.size()]; - - tHead.enableBrowserIntelligence(); - tFoot.enableBrowserIntelligence(); - - int hierarchyColumnIndent = scrollBody != null ? scrollBody - .getMaxIndent() : 0; - HeaderCell hierarchyHeaderWithExpandRatio = null; - - // first loop: collect natural widths - while (headCells.hasNext()) { - final HeaderCell hCell = (HeaderCell) headCells.next(); - final FooterCell fCell = (FooterCell) footCells.next(); - boolean needsIndent = hierarchyColumnIndent > 0 - && hCell.isHierarchyColumn(); - hCell.saveNaturalColumnWidthIfNotSaved(i); - fCell.saveNaturalColumnWidthIfNotSaved(i); - int w = hCell.getWidth(); - if (hCell.isDefinedWidth()) { - // server has defined column width explicitly - if (needsIndent && w < hierarchyColumnIndent) { - // hierarchy indent overrides explicitly set width - w = hierarchyColumnIndent; - } - totalExplicitColumnsWidths += w; - } else { - if (hCell.getExpandRatio() > 0) { - expandRatioDivider += hCell.getExpandRatio(); - w = 0; - if (needsIndent && w < hierarchyColumnIndent) { - hierarchyHeaderWithExpandRatio = hCell; - // don't add to widths here, because will be included in - // the expand ratio space if there's enough of it - } - } else { - // get and store greater of header width and column width, - // and store it as a minimum natural column width (these - // already contain the indent if any) - int headerWidth = hCell.getNaturalColumnWidth(i); - int footerWidth = fCell.getNaturalColumnWidth(i); - w = headerWidth > footerWidth ? headerWidth : footerWidth; - } - if (w != 0) { - hCell.setNaturalMinimumColumnWidth(w); - fCell.setNaturalMinimumColumnWidth(w); - } - } - widths[i] = w; - total += w; - i++; - } - if (hierarchyHeaderWithExpandRatio != null) { - total += hierarchyColumnIndent; - } - - tHead.disableBrowserIntelligence(); - tFoot.disableBrowserIntelligence(); - - boolean willHaveScrollbarz = willHaveScrollbars(); - - // fix "natural" width if width not set - if (isDynamicWidth()) { - int w = total; - w += scrollBody.getCellExtraWidth() * visibleColOrder.length; - if (willHaveScrollbarz) { - w += WidgetUtil.getNativeScrollbarSize(); - } - setContentWidth(w); - } - - int availW = scrollBody.getAvailableWidth(); - if (BrowserInfo.get().isIE()) { - // Hey IE, are you really sure about this? - availW = scrollBody.getAvailableWidth(); - } - availW -= scrollBody.getCellExtraWidth() * visibleColOrder.length; - - if (willHaveScrollbarz) { - availW -= WidgetUtil.getNativeScrollbarSize(); - } - - // TODO refactor this code to be the same as in resize timer - - if (availW > total) { - // natural size is smaller than available space - int extraSpace = availW - total; - if (hierarchyHeaderWithExpandRatio != null) { - /* - * add the indent's space back to ensure each column gets an - * even share according to the expand ratios (note: if the - * allocated space isn't enough for the hierarchy column it - * shall be treated like a defined width column and the indent - * space gets removed from the extra space again) - */ - extraSpace += hierarchyColumnIndent; - } - final int totalWidthR = total - totalExplicitColumnsWidths; - int checksum = 0; - - if (extraSpace == 1) { - // We cannot divide one single pixel so we give it the first - // undefined column - // no need to worry about indent here - headCells = tHead.iterator(); - i = 0; - checksum = availW; - while (headCells.hasNext()) { - HeaderCell hc = (HeaderCell) headCells.next(); - if (!hc.isDefinedWidth()) { - widths[i]++; - break; - } - i++; - } - - } else if (expandRatioDivider > 0) { - boolean setIndentToHierarchyHeader = false; - if (hierarchyHeaderWithExpandRatio != null) { - // ensure first that the hierarchyColumn gets at least the - // space allocated for indent - final int newSpace = Math - .round((extraSpace * (hierarchyHeaderWithExpandRatio - .getExpandRatio() / expandRatioDivider))); - if (newSpace < hierarchyColumnIndent) { - // not enough space for indent, remove indent from the - // extraSpace again and handle hierarchy column's header - // separately - setIndentToHierarchyHeader = true; - extraSpace -= hierarchyColumnIndent; - } - } - - // visible columns have some active expand ratios, excess - // space is divided according to them - headCells = tHead.iterator(); - i = 0; - while (headCells.hasNext()) { - HeaderCell hCell = (HeaderCell) headCells.next(); - if (hCell.getExpandRatio() > 0) { - int w = widths[i]; - if (setIndentToHierarchyHeader - && hierarchyHeaderWithExpandRatio.equals(hCell)) { - // hierarchy column's header is no longer part of - // the expansion divide and only gets indent - w += hierarchyColumnIndent; - } else { - final int newSpace = Math - .round((extraSpace * (hCell - .getExpandRatio() / expandRatioDivider))); - w += newSpace; - } - widths[i] = w; - } - checksum += widths[i]; - i++; - } - } else if (totalWidthR > 0) { - // no expand ratios defined, we will share extra space - // relatively to "natural widths" among those without - // explicit width - // no need to worry about indent here, it's already included - headCells = tHead.iterator(); - i = 0; - while (headCells.hasNext()) { - HeaderCell hCell = (HeaderCell) headCells.next(); - if (!hCell.isDefinedWidth()) { - int w = widths[i]; - final int newSpace = Math.round((float) extraSpace - * (float) w / totalWidthR); - w += newSpace; - widths[i] = w; - } - checksum += widths[i]; - i++; - } - } - - if (extraSpace > 0 && checksum != availW) { - /* - * There might be in some cases a rounding error of 1px when - * extra space is divided so if there is one then we give the - * first undefined column 1 more pixel - */ - headCells = tHead.iterator(); - i = 0; - while (headCells.hasNext()) { - HeaderCell hc = (HeaderCell) headCells.next(); - if (!hc.isDefinedWidth()) { - widths[i] += availW - checksum; - break; - } - i++; - } - } - - } else { - // body's size will be more than available and scrollbar will appear - } - - // last loop: set possibly modified values or reset if new tBody - i = 0; - headCells = tHead.iterator(); - while (headCells.hasNext()) { - final HeaderCell hCell = (HeaderCell) headCells.next(); - if (isNewBody || hCell.getWidth() == -1) { - final int w = widths[i]; - setColWidth(i, w, false); - } - i++; - } - - initializedAndAttached = true; - - updatePageLength(); - - /* - * Fix "natural" height if height is not set. This must be after width - * fixing so the components' widths have been adjusted. - */ - if (isDynamicHeight()) { - /* - * We must force an update of the row height as this point as it - * might have been (incorrectly) calculated earlier - */ - - /* - * TreeTable updates stuff in a funky order, so we must set the - * height as zero here before doing the real update to make it - * realize that there is no content, - */ - if (pageLength == totalRows && pageLength == 0) { - scrollBody.setHeight("0px"); - } - - int bodyHeight; - if (pageLength == totalRows) { - /* - * A hack to support variable height rows when paging is off. - * Generally this is not supported by scrolltable. We want to - * show all rows so the bodyHeight should be equal to the table - * height. - */ - // int bodyHeight = scrollBody.getOffsetHeight(); - bodyHeight = scrollBody.getRequiredHeight(); - } else { - bodyHeight = (int) Math.round(scrollBody.getRowHeight(true) - * pageLength); - } - boolean needsSpaceForHorizontalSrollbar = (total > availW); - if (needsSpaceForHorizontalSrollbar) { - bodyHeight += WidgetUtil.getNativeScrollbarSize(); - } - scrollBodyPanel.setHeight(bodyHeight + "px"); - WidgetUtil.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); - } - - isNewBody = false; - - if (firstvisible > 0) { - enableLazyScroller(); - } - - if (enabled) { - // Do we need cache rows - if (scrollBody.getLastRendered() + 1 < firstRowInViewPort - + pageLength + (int) cache_react_rate * pageLength) { - if (totalRows - 1 > scrollBody.getLastRendered()) { - // fetch cache rows - int firstInNewSet = scrollBody.getLastRendered() + 1; - int lastInNewSet = (int) (firstRowInViewPort + pageLength + cache_rate - * pageLength); - if (lastInNewSet > totalRows - 1) { - lastInNewSet = totalRows - 1; - } - rowRequestHandler.triggerRowFetch(firstInNewSet, - lastInNewSet - firstInNewSet + 1, 1); - } - } - } - - /* - * Ensures the column alignments are correct at initial loading.
- * (child components widths are correct) - */ - WidgetUtil.runWebkitOverflowAutoFixDeferred(scrollBodyPanel - .getElement()); - - hadScrollBars = willHaveScrollbarz; - } - - /** - * Note: this method is not part of official API although declared as - * protected. Extend at your own risk. - * - * @return true if content area will have scrollbars visible. - */ - protected boolean willHaveScrollbars() { - if (isDynamicHeight()) { - if (pageLength < totalRows) { - return true; - } - } else { - int fakeheight = (int) Math.round(scrollBody.getRowHeight() - * totalRows); - int availableHeight = scrollBodyPanel.getElement().getPropertyInt( - "clientHeight"); - if (fakeheight > availableHeight) { - return true; - } - } - return false; - } - - private void announceScrollPosition() { - if (scrollPositionElement == null) { - scrollPositionElement = DOM.createDiv(); - scrollPositionElement.setClassName(getStylePrimaryName() - + "-scrollposition"); - scrollPositionElement.getStyle().setPosition(Position.ABSOLUTE); - scrollPositionElement.getStyle().setDisplay(Display.NONE); - getElement().appendChild(scrollPositionElement); - } - - Style style = scrollPositionElement.getStyle(); - style.setMarginLeft(getElement().getOffsetWidth() / 2 - 80, Unit.PX); - style.setMarginTop(-scrollBodyPanel.getOffsetHeight(), Unit.PX); - - // indexes go from 1-totalRows, as rowheaders in index-mode indicate - int last = (firstRowInViewPort + pageLength); - if (last > totalRows) { - last = totalRows; - } - scrollPositionElement.setInnerHTML("" + (firstRowInViewPort + 1) - + " – " + (last) + "..." + ""); - style.setDisplay(Display.BLOCK); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void hideScrollPositionAnnotation() { - if (scrollPositionElement != null) { - scrollPositionElement.getStyle().setDisplay(Display.NONE); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public boolean isScrollPositionVisible() { - return scrollPositionElement != null - && !scrollPositionElement.getStyle().getDisplay() - .equals(Display.NONE.toString()); - } - - /** For internal use only. May be removed or replaced in the future. */ - public class RowRequestHandler extends Timer { - - private int reqFirstRow = 0; - private int reqRows = 0; - private boolean isRequestHandlerRunning = false; - - public void triggerRowFetch(int first, int rows) { - setReqFirstRow(first); - setReqRows(rows); - deferRowFetch(); - } - - public void triggerRowFetch(int first, int rows, int delay) { - setReqFirstRow(first); - setReqRows(rows); - deferRowFetch(delay); - } - - public void deferRowFetch() { - deferRowFetch(250); - } - - public boolean isRequestHandlerRunning() { - return isRequestHandlerRunning; - } - - public void deferRowFetch(int msec) { - isRequestHandlerRunning = true; - if (reqRows > 0 && reqFirstRow < totalRows) { - schedule(msec); - - // tell scroll position to user if currently "visible" rows are - // not rendered - if (totalRows > pageLength - && ((firstRowInViewPort + pageLength > scrollBody - .getLastRendered()) || (firstRowInViewPort < scrollBody - .getFirstRendered()))) { - announceScrollPosition(); - } else { - hideScrollPositionAnnotation(); - } - } - } - - public int getReqFirstRow() { - return reqFirstRow; - } - - public void setReqFirstRow(int reqFirstRow) { - if (reqFirstRow < 0) { - this.reqFirstRow = 0; - } else if (reqFirstRow >= totalRows) { - this.reqFirstRow = totalRows - 1; - } else { - this.reqFirstRow = reqFirstRow; - } - } - - public void setReqRows(int reqRows) { - if (reqRows < 0) { - this.reqRows = 0; - } else if (reqFirstRow + reqRows > totalRows) { - this.reqRows = totalRows - reqFirstRow; - } else { - this.reqRows = reqRows; - } - } - - @Override - public void run() { - - if (client.getMessageSender().hasActiveRequest() || navKeyDown) { - // if client connection is busy, don't bother loading it more - VConsole.log("Postponed rowfetch"); - schedule(250); - } else if (allRenderedRowsAreNew() && !updatedReqRows) { - - /* - * If all rows are new, there might have been a server-side call - * to Table.setCurrentPageFirstItemIndex(int) In this case, - * scrolling event takes way too late, and all the rows from - * previous viewport to this one were requested. - * - * This should prevent requesting unneeded rows by updating - * reqFirstRow and reqRows before needing them. See (#14135) - */ - - setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate))); - int last = firstRowInViewPort + (int) (cache_rate * pageLength) - + pageLength - 1; - if (last >= totalRows) { - last = totalRows - 1; - } - setReqRows(last - getReqFirstRow() + 1); - updatedReqRows = true; - schedule(250); - - } else { - - int firstRendered = scrollBody.getFirstRendered(); - int lastRendered = scrollBody.getLastRendered(); - if (lastRendered > totalRows) { - lastRendered = totalRows - 1; - } - boolean rendered = firstRendered >= 0 && lastRendered >= 0; - - int firstToBeRendered = firstRendered; - - if (reqFirstRow < firstToBeRendered) { - firstToBeRendered = reqFirstRow; - } else if (firstRowInViewPort - (int) (cache_rate * pageLength) > firstToBeRendered) { - firstToBeRendered = firstRowInViewPort - - (int) (cache_rate * pageLength); - if (firstToBeRendered < 0) { - firstToBeRendered = 0; - } - } else if (rendered && firstRendered + 1 < reqFirstRow - && lastRendered + 1 < reqFirstRow) { - // requested rows must fall within the requested rendering - // area - firstToBeRendered = reqFirstRow; - } - if (firstToBeRendered + reqRows < firstRendered) { - // must increase the required row count accordingly, - // otherwise may leave a gap and the rows beyond will get - // removed - setReqRows(firstRendered - firstToBeRendered); - } - - int lastToBeRendered = lastRendered; - int lastReqRow = reqFirstRow + reqRows - 1; - - if (lastReqRow > lastToBeRendered) { - lastToBeRendered = lastReqRow; - } else if (firstRowInViewPort + pageLength + pageLength - * cache_rate < lastToBeRendered) { - lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * cache_rate)); - if (lastToBeRendered >= totalRows) { - lastToBeRendered = totalRows - 1; - } - // due Safari 3.1 bug (see #2607), verify reqrows, original - // problem unknown, but this should catch the issue - if (lastReqRow > lastToBeRendered) { - setReqRows(lastToBeRendered - reqFirstRow); - } - } else if (rendered && lastRendered - 1 > lastReqRow - && firstRendered - 1 > lastReqRow) { - // requested rows must fall within the requested rendering - // area - lastToBeRendered = lastReqRow; - } - - if (lastToBeRendered > totalRows) { - lastToBeRendered = totalRows - 1; - } - if (reqFirstRow < firstToBeRendered - || (reqFirstRow > firstToBeRendered && (reqFirstRow < firstRendered || reqFirstRow > lastRendered + 1))) { - setReqFirstRow(firstToBeRendered); - } - if (lastRendered < lastToBeRendered - && lastRendered + reqRows < lastToBeRendered) { - // must increase the required row count accordingly, - // otherwise may leave a gap and the rows after will get - // removed - setReqRows(lastToBeRendered - lastRendered); - } else if (lastToBeRendered >= firstRendered - && reqFirstRow + reqRows < firstRendered) { - setReqRows(lastToBeRendered - lastRendered); - } - - client.updateVariable(paintableId, "firstToBeRendered", - firstToBeRendered, false); - client.updateVariable(paintableId, "lastToBeRendered", - lastToBeRendered, false); - - // don't request server to update page first index in case it - // has not been changed - if (firstRowInViewPort != firstvisible) { - // remember which firstvisible we requested, in case the - // server has a differing opinion - lastRequestedFirstvisible = firstRowInViewPort; - client.updateVariable(paintableId, "firstvisible", - firstRowInViewPort, false); - } - - client.updateVariable(paintableId, "reqfirstrow", reqFirstRow, - false); - client.updateVariable(paintableId, "reqrows", reqRows, true); - - if (selectionChanged) { - unSyncedselectionsBeforeRowFetch = new HashSet( - selectedRowKeys); - } - isRequestHandlerRunning = false; - } - } - - /** - * Sends request to refresh content at this position. - */ - public void refreshContent() { - isRequestHandlerRunning = true; - int first = (int) (firstRowInViewPort - pageLength * cache_rate); - int reqRows = (int) (2 * pageLength * cache_rate + pageLength); - if (first < 0) { - reqRows = reqRows + first; - first = 0; - } - setReqFirstRow(first); - setReqRows(reqRows); - run(); - } - } - - public class HeaderCell extends Widget { - - Element td = DOM.createTD(); - - Element captionContainer = DOM.createDiv(); - - Element sortIndicator = DOM.createDiv(); - - Element colResizeWidget = DOM.createDiv(); - - Element floatingCopyOfHeaderCell; - - private boolean sortable = false; - private final String cid; - - private boolean dragging; - private Integer currentDragX = null; // is used to resolve #14796 - - private int dragStartX; - private int colIndex; - private int originalWidth; - - private boolean isResizing; - - private int headerX; - - private boolean moved; - - private int closestSlot; - - private int width = -1; - - private int naturalWidth = -1; - - private char align = ALIGN_LEFT; - - boolean definedWidth = false; - - private float expandRatio = 0; - - private boolean sorted; - - public void setSortable(boolean b) { - sortable = b; - /* - * Should in theory call updateStyleNames here, but that would just - * be a waste of time since this method is only called from - * updateCellsFromUIDL which immediately afterwards calls setAlign - * which also updates the style names. - */ - } - - /** - * Makes room for the sorting indicator in case the column that the - * header cell belongs to is sorted. This is done by resizing the width - * of the caption container element by the correct amount - */ - public void resizeCaptionContainer(int rightSpacing) { - int captionContainerWidth = width - - colResizeWidget.getOffsetWidth() - rightSpacing; - - if (td.getClassName().contains("-asc") - || td.getClassName().contains("-desc")) { - // Leave room for the sort indicator - captionContainerWidth -= sortIndicator.getOffsetWidth(); - } - - if (captionContainerWidth < 0) { - rightSpacing += captionContainerWidth; - captionContainerWidth = 0; - } - - captionContainer.getStyle().setPropertyPx("width", - captionContainerWidth); - - // Apply/Remove spacing if defined - if (rightSpacing > 0) { - colResizeWidget.getStyle().setMarginLeft(rightSpacing, Unit.PX); - } else { - colResizeWidget.getStyle().clearMarginLeft(); - } - } - - public void setNaturalMinimumColumnWidth(int w) { - naturalWidth = w; - } - - public HeaderCell(String colId, String headerText) { - cid = colId; - - setText(headerText); - - td.appendChild(colResizeWidget); - - // ensure no clipping initially (problem on column additions) - captionContainer.getStyle().setOverflow(Overflow.VISIBLE); - - td.appendChild(sortIndicator); - td.appendChild(captionContainer); - - DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK - | Event.ONCONTEXTMENU | Event.TOUCHEVENTS); - - setElement(td); - - setAlign(ALIGN_LEFT); - } - - protected void updateStyleNames(String primaryStyleName) { - colResizeWidget.setClassName(primaryStyleName + "-resizer"); - sortIndicator.setClassName(primaryStyleName + "-sort-indicator"); - captionContainer.setClassName(primaryStyleName - + "-caption-container"); - if (sorted) { - if (sortAscending) { - setStyleName(primaryStyleName + "-header-cell-asc"); - } else { - setStyleName(primaryStyleName + "-header-cell-desc"); - } - } else { - setStyleName(primaryStyleName + "-header-cell"); - } - - if (sortable) { - addStyleName(primaryStyleName + "-header-sortable"); - } - - final String ALIGN_PREFIX = primaryStyleName - + "-caption-container-align-"; - - switch (align) { - case ALIGN_CENTER: - captionContainer.addClassName(ALIGN_PREFIX + "center"); - break; - case ALIGN_RIGHT: - captionContainer.addClassName(ALIGN_PREFIX + "right"); - break; - default: - captionContainer.addClassName(ALIGN_PREFIX + "left"); - break; - } - - } - - public void disableAutoWidthCalculation() { - definedWidth = true; - expandRatio = 0; - } - - /** - * Sets width to the header cell. This width should not include any - * possible indent modifications that are present in - * {@link VScrollTableBody#getMaxIndent()}. - * - * @param w - * required width of the cell sans indentations - * @param ensureDefinedWidth - * disables expand ratio if required - */ - public void setWidth(int w, boolean ensureDefinedWidth) { - if (ensureDefinedWidth) { - definedWidth = true; - // on column resize expand ratio becomes zero - expandRatio = 0; - } - if (width == -1) { - // go to default mode, clip content if necessary - captionContainer.getStyle().clearOverflow(); - } - width = w; - if (w == -1) { - captionContainer.getStyle().clearWidth(); - setWidth(""); - } else { - tHead.resizeCaptionContainer(this); - - /* - * if we already have tBody, set the header width properly, if - * not defer it. IE will fail with complex float in table header - * unless TD width is not explicitly set. - */ - if (scrollBody != null) { - int maxIndent = scrollBody.getMaxIndent(); - if (w < maxIndent && isHierarchyColumn()) { - w = maxIndent; - } - int tdWidth = w + scrollBody.getCellExtraWidth(); - setWidth(tdWidth + "px"); - } else { - Scheduler.get().scheduleDeferred(new Command() { - - @Override - public void execute() { - int maxIndent = scrollBody.getMaxIndent(); - int tdWidth = width; - if (tdWidth < maxIndent && isHierarchyColumn()) { - tdWidth = maxIndent; - } - tdWidth += scrollBody.getCellExtraWidth(); - setWidth(tdWidth + "px"); - } - }); - } - } - } - - public void setUndefinedWidth() { - definedWidth = false; - if (!isResizing) { - setWidth(-1, false); - } - } - - private void setUndefinedWidthFlagOnly() { - definedWidth = false; - } - - /** - * Detects if width is fixed by developer on server side or resized to - * current width by user. - * - * @return true if defined, false if "natural" width - */ - public boolean isDefinedWidth() { - return definedWidth && width >= 0; - } - - /** - * This method exists for the needs of {@link VTreeTable} only. - * - * Returns the pixels width of the header cell. This includes the - * indent, if applicable. - * - * @return The width in pixels - */ - protected int getWidthWithIndent() { - if (scrollBody != null && isHierarchyColumn()) { - int maxIndent = scrollBody.getMaxIndent(); - if (maxIndent > width) { - return maxIndent; - } - } - return width; - } - - /** - * Returns the pixels width of the header cell. - * - * @return The width in pixels - */ - public int getWidth() { - return width; - } - - /** - * This method exists for the needs of {@link VTreeTable} only. - * - * @return true if this is hierarcyColumn's header cell, - * false otherwise - */ - private boolean isHierarchyColumn() { - int hierarchyColumnIndex = getHierarchyColumnIndex(); - return hierarchyColumnIndex >= 0 - && tHead.visibleCells.indexOf(this) == hierarchyColumnIndex; - } - - public void setText(String headerText) { - DOM.setInnerHTML(captionContainer, headerText); - } - - public String getColKey() { - return cid; - } - - private void setSorted(boolean sorted) { - this.sorted = sorted; - updateStyleNames(VScrollTable.this.getStylePrimaryName()); - } - - /** - * Handle column reordering. - */ - - @Override - public void onBrowserEvent(Event event) { - if (enabled && event != null) { - if (isResizing - || event.getEventTarget().cast() == colResizeWidget) { - if (dragging - && (event.getTypeInt() == Event.ONMOUSEUP || event - .getTypeInt() == Event.ONTOUCHEND)) { - // Handle releasing column header on spacer #5318 - handleCaptionEvent(event); - } else { - onResizeEvent(event); - } - } else { - /* - * Ensure focus before handling caption event. Otherwise - * variables changed from caption event may be before - * variables from other components that fire variables when - * they lose focus. - */ - if (event.getTypeInt() == Event.ONMOUSEDOWN - || event.getTypeInt() == Event.ONTOUCHSTART) { - scrollBodyPanel.setFocus(true); - } - handleCaptionEvent(event); - boolean stopPropagation = true; - if (event.getTypeInt() == Event.ONCONTEXTMENU - && !client.hasEventListeners(VScrollTable.this, - TableConstants.HEADER_CLICK_EVENT_ID)) { - // Prevent showing the browser's context menu only when - // there is a header click listener. - stopPropagation = false; - } - if (stopPropagation) { - event.stopPropagation(); - event.preventDefault(); - } - } - } - } - - private void createFloatingCopy() { - floatingCopyOfHeaderCell = DOM.createDiv(); - DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td)); - floatingCopyOfHeaderCell = DOM - .getChild(floatingCopyOfHeaderCell, 2); - // #12714 the shown "ghost element" should be inside - // v-overlay-container, and it should contain the same styles as the - // table to enable theming (except v-table & v-widget). - String stylePrimaryName = VScrollTable.this.getStylePrimaryName(); - StringBuilder sb = new StringBuilder(); - for (String s : VScrollTable.this.getStyleName().split(" ")) { - if (!s.equals(StyleConstants.UI_WIDGET)) { - sb.append(s); - if (s.equals(stylePrimaryName)) { - sb.append("-header-drag "); - } else { - sb.append(" "); - } - } - } - floatingCopyOfHeaderCell.setClassName(sb.toString().trim()); - // otherwise might wrap or be cut if narrow column - floatingCopyOfHeaderCell.getStyle().setProperty("width", "auto"); - updateFloatingCopysPosition(DOM.getAbsoluteLeft(td), - DOM.getAbsoluteTop(td)); - DOM.appendChild(VOverlay.getOverlayContainer(client), - floatingCopyOfHeaderCell); - } - - private void updateFloatingCopysPosition(int x, int y) { - x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell, - "offsetWidth") / 2; - floatingCopyOfHeaderCell.getStyle().setLeft(x, Unit.PX); - if (y > 0) { - floatingCopyOfHeaderCell.getStyle().setTop(y + 7, Unit.PX); - } - } - - private void hideFloatingCopy() { - floatingCopyOfHeaderCell.removeFromParent(); - floatingCopyOfHeaderCell = null; - } - - /** - * Fires a header click event after the user has clicked a column header - * cell - * - * @param event - * The click event - */ - private void fireHeaderClickedEvent(Event event) { - if (client.hasEventListeners(VScrollTable.this, - TableConstants.HEADER_CLICK_EVENT_ID)) { - MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(event); - client.updateVariable(paintableId, "headerClickEvent", - details.toString(), false); - client.updateVariable(paintableId, "headerClickCID", cid, true); - } - } - - protected void handleCaptionEvent(Event event) { - switch (DOM.eventGetType(event)) { - case Event.ONTOUCHSTART: - case Event.ONMOUSEDOWN: - if (columnReordering - && WidgetUtil.isTouchEventOrLeftMouseButton(event)) { - if (event.getTypeInt() == Event.ONTOUCHSTART) { - /* - * prevent using this event in e.g. scrolling - */ - event.stopPropagation(); - } - dragging = true; - currentDragX = WidgetUtil.getTouchOrMouseClientX(event); - moved = false; - colIndex = getColIndexByKey(cid); - DOM.setCapture(getElement()); - headerX = tHead.getAbsoluteLeft(); - event.preventDefault(); // prevent selecting text && - // generated touch events - } - break; - case Event.ONMOUSEUP: - case Event.ONTOUCHEND: - case Event.ONTOUCHCANCEL: - if (columnReordering - && WidgetUtil.isTouchEventOrLeftMouseButton(event)) { - dragging = false; - currentDragX = null; - DOM.releaseCapture(getElement()); - - if (WidgetUtil.isTouchEvent(event)) { - /* - * Prevent using in e.g. scrolling and prevent generated - * events. - */ - event.preventDefault(); - event.stopPropagation(); - } - if (moved) { - hideFloatingCopy(); - tHead.removeSlotFocus(); - if (closestSlot != colIndex - && closestSlot != (colIndex + 1)) { - if (closestSlot > colIndex) { - reOrderColumn(cid, closestSlot - 1); - } else { - reOrderColumn(cid, closestSlot); - } - } - moved = false; - break; - } - } - - if (!moved) { - // mouse event was a click to header -> sort column - if (sortable - && WidgetUtil.isTouchEventOrLeftMouseButton(event)) { - if (sortColumn.equals(cid)) { - // just toggle order - client.updateVariable(paintableId, "sortascending", - !sortAscending, false); - } else { - // set table sorted by this column - client.updateVariable(paintableId, "sortcolumn", - cid, false); - } - // get also cache columns at the same request - scrollBodyPanel.setScrollPosition(0); - firstvisible = 0; - rowRequestHandler.setReqFirstRow(0); - rowRequestHandler.setReqRows((int) (2 * pageLength - * cache_rate + pageLength)); - rowRequestHandler.deferRowFetch(); // some validation + - // defer 250ms - rowRequestHandler.cancel(); // instead of waiting - rowRequestHandler.run(); // run immediately - } - fireHeaderClickedEvent(event); - if (WidgetUtil.isTouchEvent(event)) { - /* - * Prevent using in e.g. scrolling and prevent generated - * events. - */ - event.preventDefault(); - event.stopPropagation(); - } - break; - } - break; - case Event.ONDBLCLICK: - fireHeaderClickedEvent(event); - break; - case Event.ONTOUCHMOVE: - case Event.ONMOUSEMOVE: - // only start the drag if the mouse / touch has moved a minimum - // distance in x-axis (the same idea as in #13381) - int currentX = WidgetUtil.getTouchOrMouseClientX(event); - - if (currentDragX == null - || Math.abs(currentDragX - currentX) > VDragAndDropManager.MINIMUM_DISTANCE_TO_START_DRAG) { - if (dragging - && WidgetUtil.isTouchEventOrLeftMouseButton(event)) { - if (event.getTypeInt() == Event.ONTOUCHMOVE) { - /* - * prevent using this event in e.g. scrolling - */ - event.stopPropagation(); - } - if (!moved) { - createFloatingCopy(); - moved = true; - } - - final int clientX = WidgetUtil - .getTouchOrMouseClientX(event); - final int x = clientX - + tHead.hTableWrapper.getScrollLeft(); - int slotX = headerX; - closestSlot = colIndex; - int closestDistance = -1; - int start = 0; - if (showRowHeaders) { - start++; - } - final int visibleCellCount = tHead - .getVisibleCellCount(); - for (int i = start; i <= visibleCellCount; i++) { - if (i > 0) { - final String colKey = getColKeyByIndex(i - 1); - // getColWidth only returns the internal width - // without padding, not the offset width of the - // whole td (#10890) - slotX += getColWidth(colKey) - + scrollBody.getCellExtraWidth(); - } - final int dist = Math.abs(x - slotX); - if (closestDistance == -1 || dist < closestDistance) { - closestDistance = dist; - closestSlot = i; - } - } - tHead.focusSlot(closestSlot); - - updateFloatingCopysPosition(clientX, -1); - } - } - break; - default: - break; - } - } - - private void onResizeEvent(Event event) { - switch (DOM.eventGetType(event)) { - case Event.ONMOUSEDOWN: - if (!WidgetUtil.isTouchEventOrLeftMouseButton(event)) { - return; - } - isResizing = true; - DOM.setCapture(getElement()); - dragStartX = DOM.eventGetClientX(event); - colIndex = getColIndexByKey(cid); - originalWidth = getWidthWithIndent(); - DOM.eventPreventDefault(event); - break; - case Event.ONMOUSEUP: - if (!WidgetUtil.isTouchEventOrLeftMouseButton(event)) { - return; - } - isResizing = false; - DOM.releaseCapture(getElement()); - tHead.disableAutoColumnWidthCalculation(this); - - // Ensure last header cell is taking into account possible - // column selector - HeaderCell lastCell = tHead.getHeaderCell(tHead - .getVisibleCellCount() - 1); - tHead.resizeCaptionContainer(lastCell); - triggerLazyColumnAdjustment(true); - - fireColumnResizeEvent(cid, originalWidth, getColWidth(cid)); - break; - case Event.ONMOUSEMOVE: - if (!WidgetUtil.isTouchEventOrLeftMouseButton(event)) { - return; - } - if (isResizing) { - final int deltaX = DOM.eventGetClientX(event) - dragStartX; - if (deltaX == 0) { - return; - } - tHead.disableAutoColumnWidthCalculation(this); - - int newWidth = originalWidth + deltaX; - // get min width with indent, no padding - int minWidth = getMinWidth(true, false); - if (newWidth < minWidth) { - // already includes indent if any - newWidth = minWidth; - } - setColWidth(colIndex, newWidth, true); - triggerLazyColumnAdjustment(false); - forceRealignColumnHeaders(); - } - break; - default: - break; - } - } - - /** - * Returns the smallest possible cell width in pixels. - * - * @param includeIndent - * - width should include hierarchy column indent if - * applicable (VTreeTable only) - * @param includeCellExtraWidth - * - width should include paddings etc. - * @return - */ - private int getMinWidth(boolean includeIndent, - boolean includeCellExtraWidth) { - int minWidth = sortIndicator.getOffsetWidth(); - if (scrollBody != null) { - // check the need for indent before adding paddings etc. - if (includeIndent && isHierarchyColumn()) { - int maxIndent = scrollBody.getMaxIndent(); - if (minWidth < maxIndent) { - minWidth = maxIndent; - } - } - if (includeCellExtraWidth) { - minWidth += scrollBody.getCellExtraWidth(); - } - } - return minWidth; - } - - public int getMinWidth() { - // get min width with padding, no indent - return getMinWidth(false, true); - } - - public String getCaption() { - return DOM.getInnerText(captionContainer); - } - - public boolean isEnabled() { - return getParent() != null; - } - - public void setAlign(char c) { - align = c; - updateStyleNames(VScrollTable.this.getStylePrimaryName()); - } - - public char getAlign() { - return align; - } - - /** - * Saves natural column width if it hasn't been saved already. - * - * @param columnIndex - * @since 7.3.9 - */ - protected void saveNaturalColumnWidthIfNotSaved(int columnIndex) { - if (naturalWidth < 0) { - // This is recently revealed column. Try to detect a proper - // value (greater of header and data columns) - - int hw = captionContainer.getOffsetWidth() + getHeaderPadding(); - if (BrowserInfo.get().isGecko()) { - hw += sortIndicator.getOffsetWidth(); - } - if (columnIndex < 0) { - columnIndex = 0; - for (Iterator it = tHead.iterator(); it.hasNext(); columnIndex++) { - if (it.next() == this) { - break; - } - } - } - final int cw = scrollBody.getColWidth(columnIndex); - naturalWidth = (hw > cw ? hw : cw); - } - } - - /** - * Detects the natural minimum width for the column of this header cell. - * If column is resized by user or the width is defined by server the - * actual width is returned. Else the natural min width is returned. - * - * @param columnIndex - * column index hint, if -1 (unknown) it will be detected - * - * @return - */ - public int getNaturalColumnWidth(int columnIndex) { - final int iw = columnIndex == getHierarchyColumnIndex() ? scrollBody - .getMaxIndent() : 0; - saveNaturalColumnWidthIfNotSaved(columnIndex); - if (isDefinedWidth()) { - if (iw > width) { - return iw; - } - return width; - } else { - if (iw > naturalWidth) { - // indent is temporary value, naturalWidth shouldn't be - // updated - return iw; - } else { - return naturalWidth; - } - } - } - - public void setExpandRatio(float floatAttribute) { - if (floatAttribute != expandRatio) { - triggerLazyColumnAdjustment(false); - } - expandRatio = floatAttribute; - } - - public float getExpandRatio() { - return expandRatio; - } - - public boolean isSorted() { - return sorted; - } - } - - /** - * HeaderCell that is header cell for row headers. - * - * Reordering disabled and clicking on it resets sorting. - */ - public class RowHeadersHeaderCell extends HeaderCell { - - RowHeadersHeaderCell() { - super(ROW_HEADER_COLUMN_KEY, ""); - updateStyleNames(VScrollTable.this.getStylePrimaryName()); - } - - @Override - protected void updateStyleNames(String primaryStyleName) { - super.updateStyleNames(primaryStyleName); - setStyleName(primaryStyleName + "-header-cell-rowheader"); - } - - @Override - protected void handleCaptionEvent(Event event) { - // NOP: RowHeaders cannot be reordered - // TODO It'd be nice to reset sorting here - } - } - - public class TableHead extends Panel implements ActionOwner { - - private static final int WRAPPER_WIDTH = 900000; - - ArrayList visibleCells = new ArrayList(); - - HashMap availableCells = new HashMap(); - - Element div = DOM.createDiv(); - Element hTableWrapper = DOM.createDiv(); - Element hTableContainer = DOM.createDiv(); - Element table = DOM.createTable(); - Element headerTableBody = DOM.createTBody(); - Element tr = DOM.createTR(); - - private final Element columnSelector = DOM.createDiv(); - - private int focusedSlot = -1; - - public TableHead() { - if (BrowserInfo.get().isIE()) { - table.setPropertyInt("cellSpacing", 0); - } - - hTableWrapper.getStyle().setOverflow(Overflow.HIDDEN); - columnSelector.getStyle().setDisplay(Display.NONE); - - DOM.appendChild(table, headerTableBody); - DOM.appendChild(headerTableBody, tr); - DOM.appendChild(hTableContainer, table); - DOM.appendChild(hTableWrapper, hTableContainer); - DOM.appendChild(div, hTableWrapper); - DOM.appendChild(div, columnSelector); - setElement(div); - - DOM.sinkEvents(columnSelector, Event.ONCLICK); - - availableCells.put(ROW_HEADER_COLUMN_KEY, - new RowHeadersHeaderCell()); - } - - protected void updateStyleNames(String primaryStyleName) { - hTableWrapper.setClassName(primaryStyleName + "-header"); - columnSelector.setClassName(primaryStyleName + "-column-selector"); - setStyleName(primaryStyleName + "-header-wrap"); - for (HeaderCell c : availableCells.values()) { - c.updateStyleNames(primaryStyleName); - } - } - - public void resizeCaptionContainer(HeaderCell cell) { - HeaderCell lastcell = getHeaderCell(visibleCells.size() - 1); - int columnSelectorOffset = columnSelector.getOffsetWidth(); - - if (cell == lastcell && columnSelectorOffset > 0 - && !hasVerticalScrollbar()) { - - // Measure column widths - int columnTotalWidth = 0; - for (Widget w : visibleCells) { - int cellExtraWidth = w.getOffsetWidth(); - if (scrollBody != null - && visibleCells.indexOf(w) == getHierarchyColumnIndex() - && cellExtraWidth < scrollBody.getMaxIndent()) { - // indent must be taken into consideration even if it - // hasn't been applied yet - columnTotalWidth += scrollBody.getMaxIndent(); - } else { - columnTotalWidth += cellExtraWidth; - } - } - - int divOffset = div.getOffsetWidth(); - if (columnTotalWidth >= divOffset - columnSelectorOffset) { - /* - * Ensure column caption is visible when placed under the - * column selector widget by shifting and resizing the - * caption. - */ - int offset = 0; - int diff = divOffset - columnTotalWidth; - if (diff < columnSelectorOffset && diff > 0) { - /* - * If the difference is less than the column selectors - * width then just offset by the difference - */ - offset = columnSelectorOffset - diff; - } else { - // Else offset by the whole column selector - offset = columnSelectorOffset; - } - lastcell.resizeCaptionContainer(offset); - } else { - cell.resizeCaptionContainer(0); - } - } else { - cell.resizeCaptionContainer(0); - } - } - - @Override - public void clear() { - for (String cid : availableCells.keySet()) { - removeCell(cid); - } - availableCells.clear(); - availableCells.put(ROW_HEADER_COLUMN_KEY, - new RowHeadersHeaderCell()); - } - - public void updateCellsFromUIDL(UIDL uidl) { - Iterator it = uidl.getChildIterator(); - HashSet updated = new HashSet(); - boolean refreshContentWidths = initializedAndAttached - && hadScrollBars != willHaveScrollbars(); - while (it.hasNext()) { - final UIDL col = (UIDL) it.next(); - final String cid = col.getStringAttribute("cid"); - updated.add(cid); - - String caption = buildCaptionHtmlSnippet(col); - HeaderCell c = getHeaderCell(cid); - if (c == null) { - c = new HeaderCell(cid, caption); - availableCells.put(cid, c); - if (initializedAndAttached) { - // we will need a column width recalculation - initializedAndAttached = false; - initialContentReceived = false; - isNewBody = true; - } - } else { - c.setText(caption); - if (BrowserInfo.get().isIE10()) { - // IE10 can some times define min-height to include - // padding when setting the text... - // See https://dev.vaadin.com/ticket/15169 - WidgetUtil.forceIERedraw(c.getElement()); - } - } - - c.setSorted(false); - if (col.hasAttribute("sortable")) { - c.setSortable(true); - } else { - c.setSortable(false); - } - - // The previous call to setSortable relies on c.setAlign calling - // c.updateStyleNames - if (col.hasAttribute("align")) { - c.setAlign(col.getStringAttribute("align").charAt(0)); - } else { - c.setAlign(ALIGN_LEFT); - - } - if (col.hasAttribute("width") && !c.isResizing) { - // Make sure to accomodate for the sort indicator if - // necessary. - int width = col.getIntAttribute("width"); - int widthWithoutAddedIndent = width; - - // get min width with indent, no padding - int minWidth = c.getMinWidth(true, false); - if (width < minWidth) { - width = minWidth; - } - if (scrollBody != null && width != c.getWidthWithIndent()) { - // Do a more thorough update if a column is resized from - // the server *after* the header has been properly - // initialized - final int newWidth = width; - Scheduler.get().scheduleFinally(new ScheduledCommand() { - - @Override - public void execute() { - final int colIx = getColIndexByKey(cid); - setColWidth(colIx, newWidth, true); - } - }); - refreshContentWidths = true; - } else { - // get min width with no indent or padding - minWidth = c.getMinWidth(false, false); - if (widthWithoutAddedIndent < minWidth) { - widthWithoutAddedIndent = minWidth; - } - // save min width without indent - c.setWidth(widthWithoutAddedIndent, true); - } - } else if (col.hasAttribute("er")) { - c.setExpandRatio(col.getFloatAttribute("er")); - c.setUndefinedWidthFlagOnly(); - } else if (recalcWidths) { - c.setUndefinedWidth(); - - } else { - boolean hadExpandRatio = c.getExpandRatio() > 0; - boolean hadDefinedWidth = c.isDefinedWidth(); - if (hadExpandRatio || hadDefinedWidth) { - // Someone has removed a expand width or the defined - // width on the server side (setting it to -1), make the - // column undefined again and measure columns again. - c.setUndefinedWidth(); - c.setExpandRatio(0); - refreshContentWidths = true; - } - } - - if (col.hasAttribute("collapsed")) { - // ensure header is properly removed from parent (case when - // collapsing happens via servers side api) - if (c.isAttached()) { - c.removeFromParent(); - headerChangedDuringUpdate = true; - } - } - } - - if (refreshContentWidths) { - // Recalculate the column sizings if any column has changed - Scheduler.get().scheduleFinally(new ScheduledCommand() { - - @Override - public void execute() { - triggerLazyColumnAdjustment(true); - } - }); - } - - // check for orphaned header cells - for (Iterator cit = availableCells.keySet().iterator(); cit - .hasNext();) { - String cid = cit.next(); - if (!updated.contains(cid)) { - removeCell(cid); - cit.remove(); - // we will need a column width recalculation, since columns - // with expand ratios should expand to fill the void. - initializedAndAttached = false; - initialContentReceived = false; - isNewBody = true; - } - } - } - - public void enableColumn(String cid, int index) { - final HeaderCell c = getHeaderCell(cid); - if (!c.isEnabled() || getHeaderCell(index) != c) { - setHeaderCell(index, c); - if (initializedAndAttached) { - headerChangedDuringUpdate = true; - } - } - } - - public int getVisibleCellCount() { - return visibleCells.size(); - } - - public void setHorizontalScrollPosition(int scrollLeft) { - hTableWrapper.setScrollLeft(scrollLeft); - } - - public void setColumnCollapsingAllowed(boolean cc) { - if (cc) { - columnSelector.getStyle().setDisplay(Display.BLOCK); - } else { - columnSelector.getStyle().setDisplay(Display.NONE); - } - } - - public void disableBrowserIntelligence() { - hTableContainer.getStyle().setWidth(WRAPPER_WIDTH, Unit.PX); - } - - public void enableBrowserIntelligence() { - hTableContainer.getStyle().clearWidth(); - } - - public void setHeaderCell(int index, HeaderCell cell) { - if (cell.isEnabled()) { - // we're moving the cell - DOM.removeChild(tr, cell.getElement()); - orphan(cell); - visibleCells.remove(cell); - } - if (index < visibleCells.size()) { - // insert to right slot - DOM.insertChild(tr, cell.getElement(), index); - adopt(cell); - visibleCells.add(index, cell); - } else if (index == visibleCells.size()) { - // simply append - DOM.appendChild(tr, cell.getElement()); - adopt(cell); - visibleCells.add(cell); - } else { - throw new RuntimeException( - "Header cells must be appended in order"); - } - } - - public HeaderCell getHeaderCell(int index) { - if (index >= 0 && index < visibleCells.size()) { - return (HeaderCell) visibleCells.get(index); - } else { - return null; - } - } - - /** - * Get's HeaderCell by it's column Key. - * - * Note that this returns HeaderCell even if it is currently collapsed. - * - * @param cid - * Column key of accessed HeaderCell - * @return HeaderCell - */ - public HeaderCell getHeaderCell(String cid) { - return availableCells.get(cid); - } - - public void moveCell(int oldIndex, int newIndex) { - final HeaderCell hCell = getHeaderCell(oldIndex); - final Element cell = hCell.getElement(); - - visibleCells.remove(oldIndex); - DOM.removeChild(tr, cell); - - DOM.insertChild(tr, cell, newIndex); - visibleCells.add(newIndex, hCell); - } - - @Override - public Iterator iterator() { - return visibleCells.iterator(); - } - - @Override - public boolean remove(Widget w) { - if (visibleCells.contains(w)) { - visibleCells.remove(w); - orphan(w); - DOM.removeChild(DOM.getParent(w.getElement()), w.getElement()); - return true; - } - return false; - } - - public void removeCell(String colKey) { - final HeaderCell c = getHeaderCell(colKey); - remove(c); - } - - private void focusSlot(int index) { - removeSlotFocus(); - if (index > 0) { - Element child = tr.getChild(index - 1).getFirstChild().cast(); - child.setClassName(VScrollTable.this.getStylePrimaryName() - + "-resizer"); - child.addClassName(VScrollTable.this.getStylePrimaryName() - + "-focus-slot-right"); - } else { - Element child = tr.getChild(index).getFirstChild().cast(); - child.setClassName(VScrollTable.this.getStylePrimaryName() - + "-resizer"); - child.addClassName(VScrollTable.this.getStylePrimaryName() - + "-focus-slot-left"); - } - focusedSlot = index; - } - - private void removeSlotFocus() { - if (focusedSlot < 0) { - return; - } - if (focusedSlot == 0) { - Element child = tr.getChild(focusedSlot).getFirstChild().cast(); - child.setClassName(VScrollTable.this.getStylePrimaryName() - + "-resizer"); - } else if (focusedSlot > 0) { - Element child = tr.getChild(focusedSlot - 1).getFirstChild() - .cast(); - child.setClassName(VScrollTable.this.getStylePrimaryName() - + "-resizer"); - } - focusedSlot = -1; - } - - @Override - public void onBrowserEvent(Event event) { - if (enabled) { - if (event.getEventTarget().cast() == columnSelector) { - final int left = DOM.getAbsoluteLeft(columnSelector); - final int top = DOM.getAbsoluteTop(columnSelector) - + DOM.getElementPropertyInt(columnSelector, - "offsetHeight"); - client.getContextMenu().showAt(this, left, top); - } - } - } - - @Override - protected void onDetach() { - super.onDetach(); - if (client != null) { - client.getContextMenu().ensureHidden(this); - } - } - - class VisibleColumnAction extends Action { - - String colKey; - private boolean collapsed; - private boolean noncollapsible = false; - private VScrollTableRow currentlyFocusedRow; - - public VisibleColumnAction(String colKey) { - super(VScrollTable.TableHead.this); - this.colKey = colKey; - caption = tHead.getHeaderCell(colKey).getCaption(); - currentlyFocusedRow = focusedRow; - } - - @Override - public void execute() { - if (noncollapsible) { - return; - } - client.getContextMenu().hide(); - // toggle selected column - if (collapsedColumns.contains(colKey)) { - collapsedColumns.remove(colKey); - } else { - tHead.removeCell(colKey); - collapsedColumns.add(colKey); - triggerLazyColumnAdjustment(true); - } - - // update variable to server - client.updateVariable(paintableId, "collapsedcolumns", - collapsedColumns.toArray(new String[collapsedColumns - .size()]), false); - // let rowRequestHandler determine proper rows - rowRequestHandler.refreshContent(); - lazyRevertFocusToRow(currentlyFocusedRow); - } - - public void setCollapsed(boolean b) { - collapsed = b; - } - - public void setNoncollapsible(boolean b) { - noncollapsible = b; - } - - /** - * Override default method to distinguish on/off columns - */ - - @Override - public String getHTML() { - final StringBuffer buf = new StringBuffer(); - buf.append(""); - - buf.append(super.getHTML()); - buf.append(""); - - return buf.toString(); - } - - } - - /* - * Returns columns as Action array for column select popup - */ - - @Override - public Action[] getActions() { - Object[] cols; - if (columnReordering && columnOrder != null) { - cols = columnOrder; - } else { - // if columnReordering is disabled, we need different way to get - // all available columns - cols = visibleColOrder; - cols = new Object[visibleColOrder.length - + collapsedColumns.size()]; - int i; - for (i = 0; i < visibleColOrder.length; i++) { - cols[i] = visibleColOrder[i]; - } - for (final Iterator it = collapsedColumns.iterator(); it - .hasNext();) { - cols[i++] = it.next(); - } - } - List actions = new ArrayList(cols.length); - - for (int i = 0; i < cols.length; i++) { - final String cid = (String) cols[i]; - boolean noncollapsible = noncollapsibleColumns.contains(cid); - - if (noncollapsible - && collapsibleMenuContent == CollapseMenuContent.COLLAPSIBLE_COLUMNS) { - continue; - } - - final HeaderCell c = getHeaderCell(cid); - final VisibleColumnAction a = new VisibleColumnAction( - c.getColKey()); - a.setCaption(c.getCaption()); - if (!c.isEnabled()) { - a.setCollapsed(true); - } - if (noncollapsible) { - a.setNoncollapsible(true); - } - actions.add(a); - } - return actions.toArray(new Action[actions.size()]); - } - - @Override - public ApplicationConnection getClient() { - return client; - } - - @Override - public String getPaintableId() { - return paintableId; - } - - /** - * Returns column alignments for visible columns - */ - public char[] getColumnAlignments() { - final Iterator it = visibleCells.iterator(); - final char[] aligns = new char[visibleCells.size()]; - int colIndex = 0; - while (it.hasNext()) { - aligns[colIndex++] = ((HeaderCell) it.next()).getAlign(); - } - return aligns; - } - - /** - * Disables the automatic calculation of all column widths by forcing - * the widths to be "defined" thus turning off expand ratios and such. - */ - public void disableAutoColumnWidthCalculation(HeaderCell source) { - for (HeaderCell cell : availableCells.values()) { - cell.disableAutoWidthCalculation(); - } - // fire column resize events for all columns but the source of the - // resize action, since an event will fire separately for this. - ArrayList columns = new ArrayList( - availableCells.values()); - columns.remove(source); - sendColumnWidthUpdates(columns); - forceRealignColumnHeaders(); - } - } - - /** - * A cell in the footer - */ - public class FooterCell extends Widget { - private final Element td = DOM.createTD(); - private final Element captionContainer = DOM.createDiv(); - private char align = ALIGN_LEFT; - private int width = -1; - private float expandRatio = 0; - private final String cid; - boolean definedWidth = false; - private int naturalWidth = -1; - - public FooterCell(String colId, String headerText) { - cid = colId; - - setText(headerText); - - // ensure no clipping initially (problem on column additions) - captionContainer.getStyle().setOverflow(Overflow.VISIBLE); - - DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS); - - DOM.appendChild(td, captionContainer); - - DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK - | Event.ONCONTEXTMENU); - - setElement(td); - - updateStyleNames(VScrollTable.this.getStylePrimaryName()); - } - - protected void updateStyleNames(String primaryStyleName) { - captionContainer.setClassName(primaryStyleName - + "-footer-container"); - } - - /** - * Sets the text of the footer - * - * @param footerText - * The text in the footer - */ - public void setText(String footerText) { - if (footerText == null || footerText.equals("")) { - footerText = " "; - } - - DOM.setInnerHTML(captionContainer, footerText); - } - - /** - * Set alignment of the text in the cell - * - * @param c - * The alignment which can be ALIGN_CENTER, ALIGN_LEFT, - * ALIGN_RIGHT - */ - public void setAlign(char c) { - if (align != c) { - switch (c) { - case ALIGN_CENTER: - captionContainer.getStyle().setTextAlign(TextAlign.CENTER); - break; - case ALIGN_RIGHT: - captionContainer.getStyle().setTextAlign(TextAlign.RIGHT); - break; - default: - captionContainer.getStyle().setTextAlign(TextAlign.LEFT); - break; - } - } - align = c; - } - - /** - * Get the alignment of the text int the cell - * - * @return Returns either ALIGN_CENTER, ALIGN_LEFT or ALIGN_RIGHT - */ - public char getAlign() { - return align; - } - - /** - * Sets the width of the cell. This width should not include any - * possible indent modifications that are present in - * {@link VScrollTableBody#getMaxIndent()}. - * - * @param w - * The width of the cell - * @param ensureDefinedWidth - * Ensures that the given width is not recalculated - */ - public void setWidth(int w, boolean ensureDefinedWidth) { - - if (ensureDefinedWidth) { - definedWidth = true; - // on column resize expand ratio becomes zero - expandRatio = 0; - } - if (width == w) { - return; - } - if (width == -1) { - // go to default mode, clip content if necessary - captionContainer.getStyle().clearOverflow(); - } - width = w; - if (w == -1) { - captionContainer.getStyle().clearWidth(); - setWidth(""); - } else { - /* - * Reduce width with one pixel for the right border since the - * footers does not have any spacers between them. - */ - final int borderWidths = 1; - - // Set the container width (check for negative value) - captionContainer.getStyle().setPropertyPx("width", - Math.max(w - borderWidths, 0)); - - /* - * if we already have tBody, set the header width properly, if - * not defer it. IE will fail with complex float in table header - * unless TD width is not explicitly set. - */ - if (scrollBody != null) { - int maxIndent = scrollBody.getMaxIndent(); - if (w < maxIndent - && tFoot.visibleCells.indexOf(this) == getHierarchyColumnIndex()) { - // ensure there's room for the indent - w = maxIndent; - } - int tdWidth = w + scrollBody.getCellExtraWidth() - - borderWidths; - setWidth(Math.max(tdWidth, 0) + "px"); - } else { - Scheduler.get().scheduleDeferred(new Command() { - - @Override - public void execute() { - int tdWidth = width; - int maxIndent = scrollBody.getMaxIndent(); - if (tdWidth < maxIndent - && tFoot.visibleCells.indexOf(this) == getHierarchyColumnIndex()) { - // ensure there's room for the indent - tdWidth = maxIndent; - } - tdWidth += scrollBody.getCellExtraWidth() - - borderWidths; - setWidth(Math.max(tdWidth, 0) + "px"); - } - }); - } - } - } - - /** - * Sets the width to undefined - */ - public void setUndefinedWidth() { - definedWidth = false; - setWidth(-1, false); - } - - /** - * Detects if width is fixed by developer on server side or resized to - * current width by user. - * - * @return true if defined, false if "natural" width - */ - public boolean isDefinedWidth() { - return definedWidth && width >= 0; - } - - /** - * Returns the pixels width of the footer cell. - * - * @return The width in pixels - */ - public int getWidth() { - return width; - } - - /** - * Sets the expand ratio of the cell - * - * @param floatAttribute - * The expand ratio - */ - public void setExpandRatio(float floatAttribute) { - expandRatio = floatAttribute; - } - - /** - * Returns the expand ratio of the cell - * - * @return The expand ratio - */ - public float getExpandRatio() { - return expandRatio; - } - - /** - * Is the cell enabled? - * - * @return True if enabled else False - */ - public boolean isEnabled() { - return getParent() != null; - } - - /** - * Handle column clicking - */ - - @Override - public void onBrowserEvent(Event event) { - if (enabled && event != null) { - handleCaptionEvent(event); - - if (DOM.eventGetType(event) == Event.ONMOUSEUP) { - scrollBodyPanel.setFocus(true); - } - boolean stopPropagation = true; - if (event.getTypeInt() == Event.ONCONTEXTMENU - && !client.hasEventListeners(VScrollTable.this, - TableConstants.FOOTER_CLICK_EVENT_ID)) { - // Show browser context menu if a footer click listener is - // not present - stopPropagation = false; - } - if (stopPropagation) { - event.stopPropagation(); - event.preventDefault(); - } - } - } - - /** - * Handles a event on the captions - * - * @param event - * The event to handle - */ - protected void handleCaptionEvent(Event event) { - if (event.getTypeInt() == Event.ONMOUSEUP - || event.getTypeInt() == Event.ONDBLCLICK) { - fireFooterClickedEvent(event); - } - } - - /** - * Fires a footer click event after the user has clicked a column footer - * cell - * - * @param event - * The click event - */ - private void fireFooterClickedEvent(Event event) { - if (client.hasEventListeners(VScrollTable.this, - TableConstants.FOOTER_CLICK_EVENT_ID)) { - MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(event); - client.updateVariable(paintableId, "footerClickEvent", - details.toString(), false); - client.updateVariable(paintableId, "footerClickCID", cid, true); - } - } - - /** - * Returns the column key of the column - * - * @return The column key - */ - public String getColKey() { - return cid; - } - - /** - * Saves natural column width if it hasn't been saved already. - * - * @param columnIndex - * @since 7.3.9 - */ - protected void saveNaturalColumnWidthIfNotSaved(int columnIndex) { - if (naturalWidth < 0) { - // This is recently revealed column. Try to detect a proper - // value (greater of header and data cols) - - final int hw = ((Element) getElement().getLastChild()) - .getOffsetWidth() + getHeaderPadding(); - if (columnIndex < 0) { - columnIndex = 0; - for (Iterator it = tHead.iterator(); it.hasNext(); columnIndex++) { - if (it.next() == this) { - break; - } - } - } - final int cw = scrollBody.getColWidth(columnIndex); - naturalWidth = (hw > cw ? hw : cw); - } - } - - /** - * Detects the natural minimum width for the column of this header cell. - * If column is resized by user or the width is defined by server the - * actual width is returned. Else the natural min width is returned. - * - * @param columnIndex - * column index hint, if -1 (unknown) it will be detected - * - * @return - */ - public int getNaturalColumnWidth(int columnIndex) { - final int iw = columnIndex == getHierarchyColumnIndex() ? scrollBody - .getMaxIndent() : 0; - saveNaturalColumnWidthIfNotSaved(columnIndex); - if (isDefinedWidth()) { - if (iw > width) { - return iw; - } - return width; - } else { - if (iw > naturalWidth) { - return iw; - } else { - return naturalWidth; - } - } - } - - public void setNaturalMinimumColumnWidth(int w) { - naturalWidth = w; - } - } - - /** - * HeaderCell that is header cell for row headers. - * - * Reordering disabled and clicking on it resets sorting. - */ - public class RowHeadersFooterCell extends FooterCell { - - RowHeadersFooterCell() { - super(ROW_HEADER_COLUMN_KEY, ""); - } - - @Override - protected void handleCaptionEvent(Event event) { - // NOP: RowHeaders cannot be reordered - // TODO It'd be nice to reset sorting here - } - } - - /** - * The footer of the table which can be seen in the bottom of the Table. - */ - public class TableFooter extends Panel { - - private static final int WRAPPER_WIDTH = 900000; - - ArrayList visibleCells = new ArrayList(); - HashMap availableCells = new HashMap(); - - Element div = DOM.createDiv(); - Element hTableWrapper = DOM.createDiv(); - Element hTableContainer = DOM.createDiv(); - Element table = DOM.createTable(); - Element headerTableBody = DOM.createTBody(); - Element tr = DOM.createTR(); - - public TableFooter() { - - hTableWrapper.getStyle().setOverflow(Overflow.HIDDEN); - - DOM.appendChild(table, headerTableBody); - DOM.appendChild(headerTableBody, tr); - DOM.appendChild(hTableContainer, table); - DOM.appendChild(hTableWrapper, hTableContainer); - DOM.appendChild(div, hTableWrapper); - setElement(div); - - availableCells.put(ROW_HEADER_COLUMN_KEY, - new RowHeadersFooterCell()); - - updateStyleNames(VScrollTable.this.getStylePrimaryName()); - } - - protected void updateStyleNames(String primaryStyleName) { - hTableWrapper.setClassName(primaryStyleName + "-footer"); - setStyleName(primaryStyleName + "-footer-wrap"); - for (FooterCell c : availableCells.values()) { - c.updateStyleNames(primaryStyleName); - } - } - - @Override - public void clear() { - for (String cid : availableCells.keySet()) { - removeCell(cid); - } - availableCells.clear(); - availableCells.put(ROW_HEADER_COLUMN_KEY, - new RowHeadersFooterCell()); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client - * .ui.Widget) - */ - - @Override - public boolean remove(Widget w) { - if (visibleCells.contains(w)) { - visibleCells.remove(w); - orphan(w); - DOM.removeChild(DOM.getParent(w.getElement()), w.getElement()); - return true; - } - return false; - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.HasWidgets#iterator() - */ - - @Override - public Iterator iterator() { - return visibleCells.iterator(); - } - - /** - * Gets a footer cell which represents the given columnId - * - * @param cid - * The columnId - * - * @return The cell - */ - public FooterCell getFooterCell(String cid) { - return availableCells.get(cid); - } - - /** - * Gets a footer cell by using a column index - * - * @param index - * The index of the column - * @return The Cell - */ - public FooterCell getFooterCell(int index) { - if (index < visibleCells.size()) { - return (FooterCell) visibleCells.get(index); - } else { - return null; - } - } - - /** - * Updates the cells contents when updateUIDL request is received - * - * @param uidl - * The UIDL - */ - public void updateCellsFromUIDL(UIDL uidl) { - Iterator columnIterator = uidl.getChildIterator(); - HashSet updated = new HashSet(); - while (columnIterator.hasNext()) { - final UIDL col = (UIDL) columnIterator.next(); - final String cid = col.getStringAttribute("cid"); - updated.add(cid); - - String caption = col.hasAttribute("fcaption") ? col - .getStringAttribute("fcaption") : ""; - FooterCell c = getFooterCell(cid); - if (c == null) { - c = new FooterCell(cid, caption); - availableCells.put(cid, c); - if (initializedAndAttached) { - // we will need a column width recalculation - initializedAndAttached = false; - initialContentReceived = false; - isNewBody = true; - } - } else { - c.setText(caption); - } - - if (col.hasAttribute("align")) { - c.setAlign(col.getStringAttribute("align").charAt(0)); - } else { - c.setAlign(ALIGN_LEFT); - - } - if (col.hasAttribute("width")) { - if (scrollBody == null || isNewBody) { - // Already updated by setColWidth called from - // TableHeads.updateCellsFromUIDL in case of a server - // side resize - final int width = col.getIntAttribute("width"); - c.setWidth(width, true); - } - } else if (recalcWidths) { - c.setUndefinedWidth(); - } - if (col.hasAttribute("er")) { - c.setExpandRatio(col.getFloatAttribute("er")); - } - if (col.hasAttribute("collapsed")) { - // ensure header is properly removed from parent (case when - // collapsing happens via servers side api) - if (c.isAttached()) { - c.removeFromParent(); - headerChangedDuringUpdate = true; - } - } - } - - // check for orphaned header cells - for (Iterator cit = availableCells.keySet().iterator(); cit - .hasNext();) { - String cid = cit.next(); - if (!updated.contains(cid)) { - removeCell(cid); - cit.remove(); - } - } - } - - /** - * Set a footer cell for a specified column index - * - * @param index - * The index - * @param cell - * The footer cell - */ - public void setFooterCell(int index, FooterCell cell) { - if (cell.isEnabled()) { - // we're moving the cell - DOM.removeChild(tr, cell.getElement()); - orphan(cell); - visibleCells.remove(cell); - } - if (index < visibleCells.size()) { - // insert to right slot - DOM.insertChild(tr, cell.getElement(), index); - adopt(cell); - visibleCells.add(index, cell); - } else if (index == visibleCells.size()) { - // simply append - DOM.appendChild(tr, cell.getElement()); - adopt(cell); - visibleCells.add(cell); - } else { - throw new RuntimeException( - "Header cells must be appended in order"); - } - } - - /** - * Remove a cell by using the columnId - * - * @param colKey - * The columnId to remove - */ - public void removeCell(String colKey) { - final FooterCell c = getFooterCell(colKey); - remove(c); - } - - /** - * Enable a column (Sets the footer cell) - * - * @param cid - * The columnId - * @param index - * The index of the column - */ - public void enableColumn(String cid, int index) { - final FooterCell c = getFooterCell(cid); - if (!c.isEnabled() || getFooterCell(index) != c) { - setFooterCell(index, c); - if (initializedAndAttached) { - headerChangedDuringUpdate = true; - } - } - } - - /** - * Disable browser measurement of the table width - */ - public void disableBrowserIntelligence() { - hTableContainer.getStyle().setWidth(WRAPPER_WIDTH, Unit.PX); - } - - /** - * Enable browser measurement of the table width - */ - public void enableBrowserIntelligence() { - hTableContainer.getStyle().clearWidth(); - } - - /** - * Set the horizontal position in the cell in the footer. This is done - * when a horizontal scrollbar is present. - * - * @param scrollLeft - * The value of the leftScroll - */ - public void setHorizontalScrollPosition(int scrollLeft) { - hTableWrapper.setScrollLeft(scrollLeft); - } - - /** - * Swap cells when the column are dragged - * - * @param oldIndex - * The old index of the cell - * @param newIndex - * The new index of the cell - */ - public void moveCell(int oldIndex, int newIndex) { - final FooterCell hCell = getFooterCell(oldIndex); - final Element cell = hCell.getElement(); - - visibleCells.remove(oldIndex); - DOM.removeChild(tr, cell); - - DOM.insertChild(tr, cell, newIndex); - visibleCells.add(newIndex, hCell); - } - } - - /** - * This Panel can only contain VScrollTableRow type of widgets. This - * "simulates" very large table, keeping spacers which take room of - * unrendered rows. - * - */ - public class VScrollTableBody extends Panel { - - public static final int DEFAULT_ROW_HEIGHT = 24; - - private double rowHeight = -1; - - private final LinkedList renderedRows = new LinkedList(); - - /** - * Due some optimizations row height measuring is deferred and initial - * set of rows is rendered detached. Flag set on when table body has - * been attached in dom and rowheight has been measured. - */ - private boolean tBodyMeasurementsDone = false; - - Element preSpacer = DOM.createDiv(); - Element postSpacer = DOM.createDiv(); - - Element container = DOM.createDiv(); - - TableSectionElement tBodyElement = Document.get().createTBodyElement(); - Element table = DOM.createTable(); - - private int firstRendered; - private int lastRendered; - - private char[] aligns; - - protected VScrollTableBody() { - constructDOM(); - setElement(container); - } - - public void setLastRendered(int lastRendered) { - if (totalRows >= 0 && lastRendered > totalRows) { - VConsole.log("setLastRendered: " + this.lastRendered + " -> " - + lastRendered); - this.lastRendered = totalRows - 1; - } else { - this.lastRendered = lastRendered; - } - } - - public int getLastRendered() { - - return lastRendered; - } - - public int getFirstRendered() { - - return firstRendered; - } - - public VScrollTableRow getRowByRowIndex(int indexInTable) { - int internalIndex = indexInTable - firstRendered; - if (internalIndex >= 0 && internalIndex < renderedRows.size()) { - return (VScrollTableRow) renderedRows.get(internalIndex); - } else { - return null; - } - } - - /** - * @return the height of scrollable body, subpixels ceiled. - */ - public int getRequiredHeight() { - return preSpacer.getOffsetHeight() + postSpacer.getOffsetHeight() - + WidgetUtil.getRequiredHeight(table); - } - - private void constructDOM() { - if (BrowserInfo.get().isIE()) { - table.setPropertyInt("cellSpacing", 0); - } - - table.appendChild(tBodyElement); - DOM.appendChild(container, preSpacer); - DOM.appendChild(container, table); - DOM.appendChild(container, postSpacer); - if (BrowserInfo.get().requiresTouchScrollDelegate()) { - NodeList childNodes = container.getChildNodes(); - for (int i = 0; i < childNodes.getLength(); i++) { - Element item = (Element) childNodes.getItem(i); - item.getStyle().setProperty("webkitTransform", - "translate3d(0,0,0)"); - } - } - updateStyleNames(VScrollTable.this.getStylePrimaryName()); - } - - protected void updateStyleNames(String primaryStyleName) { - table.setClassName(primaryStyleName + "-table"); - preSpacer.setClassName(primaryStyleName + "-row-spacer"); - postSpacer.setClassName(primaryStyleName + "-row-spacer"); - for (Widget w : renderedRows) { - VScrollTableRow row = (VScrollTableRow) w; - row.updateStyleNames(primaryStyleName); - } - } - - public int getAvailableWidth() { - int availW = scrollBodyPanel.getOffsetWidth() - getBorderWidth(); - return availW; - } - - public void renderInitialRows(UIDL rowData, int firstIndex, int rows) { - firstRendered = firstIndex; - setLastRendered(firstIndex + rows - 1); - final Iterator it = rowData.getChildIterator(); - aligns = tHead.getColumnAlignments(); - while (it.hasNext()) { - final VScrollTableRow row = createRow((UIDL) it.next(), aligns); - addRow(row); - } - if (isAttached()) { - fixSpacers(); - } - } - - public void renderRows(UIDL rowData, int firstIndex, int rows) { - // FIXME REVIEW - aligns = tHead.getColumnAlignments(); - final Iterator it = rowData.getChildIterator(); - if (firstIndex == lastRendered + 1) { - while (it.hasNext()) { - final VScrollTableRow row = prepareRow((UIDL) it.next()); - addRow(row); - setLastRendered(lastRendered + 1); - } - fixSpacers(); - } else if (firstIndex + rows == firstRendered) { - final VScrollTableRow[] rowArray = new VScrollTableRow[rows]; - int i = rows; - - while (it.hasNext()) { - i--; - rowArray[i] = prepareRow((UIDL) it.next()); - } - - for (i = 0; i < rows; i++) { - addRowBeforeFirstRendered(rowArray[i]); - firstRendered--; - } - } else { - // completely new set of rows - - // there can't be sanity checks for last rendered within this - // while loop regardless of what has been set previously, so - // change it temporarily to true and then return the original - // value - boolean temp = postponeSanityCheckForLastRendered; - postponeSanityCheckForLastRendered = true; - while (lastRendered + 1 > firstRendered) { - unlinkRow(false); - } - postponeSanityCheckForLastRendered = temp; - VScrollTableRow row = prepareRow((UIDL) it.next()); - firstRendered = firstIndex; - setLastRendered(firstIndex - 1); - addRow(row); - setLastRendered(lastRendered + 1); - setContainerHeight(); - fixSpacers(); - - while (it.hasNext()) { - addRow(prepareRow((UIDL) it.next())); - setLastRendered(lastRendered + 1); - } - - fixSpacers(); - } - - // this may be a new set of rows due content change, - // ensure we have proper cache rows - ensureCacheFilled(); - } - - /** - * Ensure we have the correct set of rows on client side, e.g. if the - * content on the server side has changed, or the client scroll position - * has changed since the last request. - */ - protected void ensureCacheFilled() { - - /** - * Fixes cache issue #13576 where unnecessary rows are fetched - */ - if (isLazyScrollerActive()) { - return; - } - - int reactFirstRow = (int) (firstRowInViewPort - pageLength - * cache_react_rate); - int reactLastRow = (int) (firstRowInViewPort + pageLength + pageLength - * cache_react_rate); - if (reactFirstRow < 0) { - reactFirstRow = 0; - } - if (reactLastRow >= totalRows) { - reactLastRow = totalRows - 1; - } - if (lastRendered < reactFirstRow || firstRendered > reactLastRow) { - /* - * #8040 - scroll position is completely changed since the - * latest request, so request a new set of rows. - * - * TODO: We should probably check whether the fetched rows match - * the current scroll position right when they arrive, so as to - * not waste time rendering a set of rows that will never be - * visible... - */ - rowRequestHandler.triggerRowFetch(reactFirstRow, reactLastRow - - reactFirstRow + 1, 1); - } else if (lastRendered < reactLastRow) { - // get some cache rows below visible area - rowRequestHandler.triggerRowFetch(lastRendered + 1, - reactLastRow - lastRendered, 1); - } else if (firstRendered > reactFirstRow) { - /* - * Branch for fetching cache above visible area. - * - * If cache needed for both before and after visible area, this - * will be rendered after-cache is received and rendered. So in - * some rare situations the table may make two cache visits to - * server. - */ - rowRequestHandler.triggerRowFetch(reactFirstRow, firstRendered - - reactFirstRow, 1); - } - } - - /** - * Inserts rows as provided in the rowData starting at firstIndex. - * - * @param rowData - * @param firstIndex - * @param rows - * the number of rows - * @return a list of the rows added. - */ - protected List insertRows(UIDL rowData, - int firstIndex, int rows) { - aligns = tHead.getColumnAlignments(); - final Iterator it = rowData.getChildIterator(); - List insertedRows = new ArrayList(); - - if (firstIndex == lastRendered + 1) { - while (it.hasNext()) { - final VScrollTableRow row = prepareRow((UIDL) it.next()); - addRow(row); - insertedRows.add(row); - if (postponeSanityCheckForLastRendered) { - lastRendered++; - } else { - setLastRendered(lastRendered + 1); - } - } - fixSpacers(); - } else if (firstIndex + rows == firstRendered) { - final VScrollTableRow[] rowArray = new VScrollTableRow[rows]; - int i = rows; - while (it.hasNext()) { - i--; - rowArray[i] = prepareRow((UIDL) it.next()); - } - for (i = 0; i < rows; i++) { - addRowBeforeFirstRendered(rowArray[i]); - insertedRows.add(rowArray[i]); - firstRendered--; - } - } else { - // insert in the middle - int ix = firstIndex; - while (it.hasNext()) { - VScrollTableRow row = prepareRow((UIDL) it.next()); - insertRowAt(row, ix); - insertedRows.add(row); - if (postponeSanityCheckForLastRendered) { - lastRendered++; - } else { - setLastRendered(lastRendered + 1); - } - ix++; - } - fixSpacers(); - } - return insertedRows; - } - - protected List insertAndReindexRows(UIDL rowData, - int firstIndex, int rows) { - List inserted = insertRows(rowData, firstIndex, - rows); - int actualIxOfFirstRowAfterInserted = firstIndex + rows - - firstRendered; - for (int ix = actualIxOfFirstRowAfterInserted; ix < renderedRows - .size(); ix++) { - VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix); - r.setIndex(r.getIndex() + rows); - } - setContainerHeight(); - return inserted; - } - - protected void insertRowsDeleteBelow(UIDL rowData, int firstIndex, - int rows) { - unlinkAllRowsStartingAt(firstIndex); - insertRows(rowData, firstIndex, rows); - setContainerHeight(); - } - - /** - * This method is used to instantiate new rows for this table. It - * automatically sets correct widths to rows cells and assigns correct - * client reference for child widgets. - * - * This method can be called only after table has been initialized - * - * @param uidl - */ - private VScrollTableRow prepareRow(UIDL uidl) { - final VScrollTableRow row = createRow(uidl, aligns); - row.initCellWidths(); - return row; - } - - protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) { - if (uidl.hasAttribute("gen_html")) { - // This is a generated row. - return new VScrollTableGeneratedRow(uidl, aligns2); - } - return new VScrollTableRow(uidl, aligns2); - } - - private void addRowBeforeFirstRendered(VScrollTableRow row) { - row.setIndex(firstRendered - 1); - if (row.isSelected()) { - row.addStyleName("v-selected"); - } - tBodyElement.insertBefore(row.getElement(), - tBodyElement.getFirstChild()); - adopt(row); - renderedRows.add(0, row); - } - - private void addRow(VScrollTableRow row) { - row.setIndex(firstRendered + renderedRows.size()); - if (row.isSelected()) { - row.addStyleName("v-selected"); - } - tBodyElement.appendChild(row.getElement()); - // Add to renderedRows before adopt so iterator() will return also - // this row if called in an attach handler (#9264) - renderedRows.add(row); - adopt(row); - } - - private void insertRowAt(VScrollTableRow row, int index) { - row.setIndex(index); - if (row.isSelected()) { - row.addStyleName("v-selected"); - } - if (index > 0) { - VScrollTableRow sibling = getRowByRowIndex(index - 1); - tBodyElement - .insertAfter(row.getElement(), sibling.getElement()); - } else { - VScrollTableRow sibling = getRowByRowIndex(index); - tBodyElement.insertBefore(row.getElement(), - sibling.getElement()); - } - adopt(row); - int actualIx = index - firstRendered; - renderedRows.add(actualIx, row); - } - - @Override - public Iterator iterator() { - return renderedRows.iterator(); - } - - /** - * @return false if couldn't remove row - */ - protected boolean unlinkRow(boolean fromBeginning) { - if (lastRendered - firstRendered < 0) { - return false; - } - int actualIx; - if (fromBeginning) { - actualIx = 0; - firstRendered++; - } else { - actualIx = renderedRows.size() - 1; - if (postponeSanityCheckForLastRendered) { - --lastRendered; - } else { - setLastRendered(lastRendered - 1); - } - } - if (actualIx >= 0) { - unlinkRowAtActualIndex(actualIx); - fixSpacers(); - return true; - } - return false; - } - - protected void unlinkRows(int firstIndex, int count) { - if (count < 1) { - return; - } - if (firstRendered > firstIndex - && firstRendered < firstIndex + count) { - count = count - (firstRendered - firstIndex); - firstIndex = firstRendered; - } - int lastIndex = firstIndex + count - 1; - if (lastRendered < lastIndex) { - lastIndex = lastRendered; - } - for (int ix = lastIndex; ix >= firstIndex; ix--) { - unlinkRowAtActualIndex(actualIndex(ix)); - if (postponeSanityCheckForLastRendered) { - // partialUpdate handles sanity check later - lastRendered--; - } else { - setLastRendered(lastRendered - 1); - } - } - fixSpacers(); - } - - protected void unlinkAndReindexRows(int firstIndex, int count) { - unlinkRows(firstIndex, count); - int actualFirstIx = firstIndex - firstRendered; - for (int ix = actualFirstIx; ix < renderedRows.size(); ix++) { - VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix); - r.setIndex(r.getIndex() - count); - } - setContainerHeight(); - } - - protected void unlinkAllRowsStartingAt(int index) { - if (firstRendered > index) { - index = firstRendered; - } - for (int ix = renderedRows.size() - 1; ix >= index; ix--) { - unlinkRowAtActualIndex(actualIndex(ix)); - setLastRendered(lastRendered - 1); - } - fixSpacers(); - } - - private int actualIndex(int index) { - return index - firstRendered; - } - - private void unlinkRowAtActualIndex(int index) { - final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows - .get(index); - tBodyElement.removeChild(toBeRemoved.getElement()); - orphan(toBeRemoved); - renderedRows.remove(index); - } - - @Override - public boolean remove(Widget w) { - throw new UnsupportedOperationException(); - } - - /** - * Fix container blocks height according to totalRows to avoid - * "bouncing" when scrolling - */ - private void setContainerHeight() { - fixSpacers(); - container.getStyle().setHeight(measureRowHeightOffset(totalRows), - Unit.PX); - } - - private void fixSpacers() { - int prepx = measureRowHeightOffset(firstRendered); - if (prepx < 0) { - prepx = 0; - } - preSpacer.getStyle().setPropertyPx("height", prepx); - int postpx; - if (pageLength == 0 && totalRows == pageLength) { - /* - * TreeTable depends on having lastRendered out of sync in some - * situations, which makes this method miss the special - * situation in which one row worth of post spacer to be added - * if there are no rows in the table. #9203 - */ - postpx = measureRowHeightOffset(1); - } else { - postpx = measureRowHeightOffset(totalRows - 1) - - measureRowHeightOffset(lastRendered); - } - - if (postpx < 0) { - postpx = 0; - } - postSpacer.getStyle().setPropertyPx("height", postpx); - } - - public double getRowHeight() { - return getRowHeight(false); - } - - public double getRowHeight(boolean forceUpdate) { - if (tBodyMeasurementsDone && !forceUpdate) { - return rowHeight; - } else { - if (tBodyElement.getRows().getLength() > 0) { - int tableHeight = getTableHeight(); - int rowCount = tBodyElement.getRows().getLength(); - rowHeight = tableHeight / (double) rowCount; - } else { - // Special cases if we can't just measure the current rows - if (!Double.isNaN(lastKnownRowHeight)) { - // Use previous value if available - if (BrowserInfo.get().isIE()) { - /* - * IE needs to reflow the table element at this - * point to work correctly (e.g. - * com.vaadin.tests.components.table. - * ContainerSizeChange) - the other code paths - * already trigger reflows, but here it must be done - * explicitly. - */ - getTableHeight(); - } - rowHeight = lastKnownRowHeight; - } else if (isAttached()) { - // measure row height by adding a dummy row - VScrollTableRow scrollTableRow = new VScrollTableRow(); - tBodyElement.appendChild(scrollTableRow.getElement()); - getRowHeight(forceUpdate); - tBodyElement.removeChild(scrollTableRow.getElement()); - } else { - // TODO investigate if this can never happen anymore - return DEFAULT_ROW_HEIGHT; - } - } - lastKnownRowHeight = rowHeight; - tBodyMeasurementsDone = true; - return rowHeight; - } - } - - public int getTableHeight() { - return table.getOffsetHeight(); - } - - /** - * Returns the width available for column content. - * - * @param columnIndex - * @return - */ - public int getColWidth(int columnIndex) { - if (tBodyMeasurementsDone) { - if (renderedRows.isEmpty()) { - // no rows yet rendered - return 0; - } - for (Widget row : renderedRows) { - if (!(row instanceof VScrollTableGeneratedRow)) { - TableRowElement tr = row.getElement().cast(); - // Spanned rows might cause an NPE. - if (columnIndex < tr.getChildCount()) { - Element wrapperdiv = tr.getCells() - .getItem(columnIndex) - .getFirstChildElement().cast(); - return wrapperdiv.getOffsetWidth(); - } - } - } - return 0; - } else { - return 0; - } - } - - /** - * Sets the content width of a column. - * - * Due IE limitation, we must set the width to a wrapper elements inside - * table cells (with overflow hidden, which does not work on td - * elements). - * - * To get this work properly crossplatform, we will also set the width - * of td. - * - * @param colIndex - * @param w - */ - public void setColWidth(int colIndex, int w) { - for (Widget row : renderedRows) { - ((VScrollTableRow) row).setCellWidth(colIndex, w); - } - } - - private int cellExtraWidth = -1; - - /** - * Method to return the space used for cell paddings + border. - */ - private int getCellExtraWidth() { - if (cellExtraWidth < 0) { - detectExtrawidth(); - } - return cellExtraWidth; - } - - /** - * This method exists for the needs of {@link VTreeTable} only. May be - * removed or replaced in the future.

Returns the maximum - * indent of the hierarcyColumn, if applicable. - * - * @see {@link VScrollTable#getHierarchyColumnIndex()} - * - * @return maximum indent in pixels - */ - protected int getMaxIndent() { - return 0; - } - - /** - * This method exists for the needs of {@link VTreeTable} only. May be - * removed or replaced in the future.

Calculates the maximum - * indent of the hierarcyColumn, if applicable. - */ - protected void calculateMaxIndent() { - // NOP - } - - private void detectExtrawidth() { - NodeList rows = tBodyElement.getRows(); - if (rows.getLength() == 0) { - /* need to temporary add empty row and detect */ - VScrollTableRow scrollTableRow = new VScrollTableRow(); - scrollTableRow.updateStyleNames(VScrollTable.this - .getStylePrimaryName()); - tBodyElement.appendChild(scrollTableRow.getElement()); - detectExtrawidth(); - tBodyElement.removeChild(scrollTableRow.getElement()); - } else { - boolean noCells = false; - TableRowElement item = rows.getItem(0); - TableCellElement firstTD = item.getCells().getItem(0); - if (firstTD == null) { - // content is currently empty, we need to add a fake cell - // for measuring - noCells = true; - VScrollTableRow next = (VScrollTableRow) iterator().next(); - boolean sorted = tHead.getHeaderCell(0) != null ? tHead - .getHeaderCell(0).isSorted() : false; - next.addCell(null, "", ALIGN_LEFT, "", true, sorted); - firstTD = item.getCells().getItem(0); - } - com.google.gwt.dom.client.Element wrapper = firstTD - .getFirstChildElement(); - cellExtraWidth = firstTD.getOffsetWidth() - - wrapper.getOffsetWidth(); - if (noCells) { - firstTD.getParentElement().removeChild(firstTD); - } - } - } - - public void moveCol(int oldIndex, int newIndex) { - - // loop all rows and move given index to its new place - final Iterator rows = iterator(); - while (rows.hasNext()) { - final VScrollTableRow row = (VScrollTableRow) rows.next(); - - final Element td = DOM.getChild(row.getElement(), oldIndex); - if (td != null) { - DOM.removeChild(row.getElement(), td); - - DOM.insertChild(row.getElement(), td, newIndex); - } - } - - } - - /** - * Restore row visibility which is set to "none" when the row is - * rendered (due a performance optimization). - */ - private void restoreRowVisibility() { - for (Widget row : renderedRows) { - row.getElement().getStyle().setProperty("visibility", ""); - } - } - - public int indexOf(Widget row) { - int relIx = -1; - for (int ix = 0; ix < renderedRows.size(); ix++) { - if (renderedRows.get(ix) == row) { - relIx = ix; - break; - } - } - if (relIx >= 0) { - return firstRendered + relIx; - } - return -1; - } - - public class VScrollTableRow extends Panel implements ActionOwner, - ContextMenuOwner { - - private static final int TOUCHSCROLL_TIMEOUT = 100; - private static final int DRAGMODE_MULTIROW = 2; - protected ArrayList childWidgets = new ArrayList(); - private boolean selected = false; - protected final int rowKey; - - private String[] actionKeys = null; - private final TableRowElement rowElement; - private int index; - private Event touchStart; - - private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500; - private Timer contextTouchTimeout; - private Timer dragTouchTimeout; - private int touchStartY; - private int touchStartX; - - private TouchContextProvider touchContextProvider = new TouchContextProvider( - this); - - private TooltipInfo tooltipInfo = null; - private Map cellToolTips = new HashMap(); - private boolean isDragging = false; - private String rowStyle = null; - protected boolean applyZeroWidthFix = true; - - private VScrollTableRow(int rowKey) { - this.rowKey = rowKey; - rowElement = Document.get().createTRElement(); - setElement(rowElement); - DOM.sinkEvents(getElement(), Event.MOUSEEVENTS - | Event.TOUCHEVENTS | Event.ONDBLCLICK - | Event.ONCONTEXTMENU | VTooltip.TOOLTIP_EVENTS); - } - - public VScrollTableRow(UIDL uidl, char[] aligns) { - this(uidl.getIntAttribute("key")); - - /* - * Rendering the rows as hidden improves Firefox and Safari - * performance drastically. - */ - getElement().getStyle().setProperty("visibility", "hidden"); - - rowStyle = uidl.getStringAttribute("rowstyle"); - updateStyleNames(VScrollTable.this.getStylePrimaryName()); - - String rowDescription = uidl.getStringAttribute("rowdescr"); - if (rowDescription != null && !rowDescription.equals("")) { - tooltipInfo = new TooltipInfo(rowDescription, null, this); - } else { - tooltipInfo = null; - } - - tHead.getColumnAlignments(); - int col = 0; - int visibleColumnIndex = -1; - - // row header - if (showRowHeaders) { - boolean sorted = tHead.getHeaderCell(col).isSorted(); - addCell(uidl, buildCaptionHtmlSnippet(uidl), aligns[col++], - "rowheader", true, sorted); - visibleColumnIndex++; - } - - if (uidl.hasAttribute("al")) { - actionKeys = uidl.getStringArrayAttribute("al"); - } - - addCellsFromUIDL(uidl, aligns, col, visibleColumnIndex); - - if (uidl.hasAttribute("selected") && !isSelected()) { - toggleSelection(); - } - } - - protected void updateStyleNames(String primaryStyleName) { - - if (getStylePrimaryName().contains("odd")) { - setStyleName(primaryStyleName + "-row-odd"); - } else { - setStyleName(primaryStyleName + "-row"); - } - - if (rowStyle != null) { - addStyleName(primaryStyleName + "-row-" + rowStyle); - } - - for (int i = 0; i < rowElement.getChildCount(); i++) { - TableCellElement cell = (TableCellElement) rowElement - .getChild(i); - updateCellStyleNames(cell, primaryStyleName); - } - } - - public TooltipInfo getTooltipInfo() { - return tooltipInfo; - } - - /** - * Add a dummy row, used for measurements if Table is empty. - */ - public VScrollTableRow() { - this(0); - addCell(null, "_", 'b', "", true, false); - } - - protected void initCellWidths() { - final int cells = tHead.getVisibleCellCount(); - for (int i = 0; i < cells; i++) { - int w = VScrollTable.this.getColWidth(getColKeyByIndex(i)); - if (w < 0) { - w = 0; - } - setCellWidth(i, w); - } - } - - protected void setCellWidth(int cellIx, int width) { - final Element cell = DOM.getChild(getElement(), cellIx); - Style wrapperStyle = cell.getFirstChildElement().getStyle(); - int wrapperWidth = width; - if (BrowserInfo.get().isWebkit() - || BrowserInfo.get().isOpera10()) { - /* - * Some versions of Webkit and Opera ignore the width - * definition of zero width table cells. Instead, use 1px - * and compensate with a negative margin. - */ - if (applyZeroWidthFix && width == 0) { - wrapperWidth = 1; - wrapperStyle.setMarginRight(-1, Unit.PX); - } else { - wrapperStyle.clearMarginRight(); - } - } - wrapperStyle.setPropertyPx("width", wrapperWidth); - cell.getStyle().setPropertyPx("width", width); - } - - protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, - int visibleColumnIndex) { - final Iterator cells = uidl.getChildIterator(); - while (cells.hasNext()) { - final Object cell = cells.next(); - visibleColumnIndex++; - - String columnId = visibleColOrder[visibleColumnIndex]; - - String style = ""; - if (uidl.hasAttribute("style-" + columnId)) { - style = uidl.getStringAttribute("style-" + columnId); - } - - String description = null; - if (uidl.hasAttribute("descr-" + columnId)) { - description = uidl.getStringAttribute("descr-" - + columnId); - } - - boolean sorted = tHead.getHeaderCell(col).isSorted(); - if (cell instanceof String) { - addCell(uidl, cell.toString(), aligns[col++], style, - isRenderHtmlInCells(), sorted, description); - } else { - final ComponentConnector cellContent = client - .getPaintable((UIDL) cell); - - addCell(uidl, cellContent.getWidget(), aligns[col++], - style, sorted, description); - } - } - } - - /** - * Overriding this and returning true causes all text cells to be - * rendered as HTML. - * - * @return always returns false in the default implementation - */ - protected boolean isRenderHtmlInCells() { - return false; - } - - /** - * Detects whether row is visible in tables viewport. - * - * @return - */ - public boolean isInViewPort() { - int absoluteTop = getAbsoluteTop(); - int absoluteBottom = absoluteTop + getOffsetHeight(); - int viewPortTop = scrollBodyPanel.getAbsoluteTop(); - int viewPortBottom = viewPortTop - + scrollBodyPanel.getOffsetHeight(); - return absoluteBottom > viewPortTop - && absoluteTop < viewPortBottom; - } - - /** - * Makes a check based on indexes whether the row is before the - * compared row. - * - * @param row1 - * @return true if this rows index is smaller than in the row1 - */ - public boolean isBefore(VScrollTableRow row1) { - return getIndex() < row1.getIndex(); - } - - /** - * Sets the index of the row in the whole table. Currently used just - * to set even/odd classname - * - * @param indexInWholeTable - */ - private void setIndex(int indexInWholeTable) { - index = indexInWholeTable; - boolean isOdd = indexInWholeTable % 2 == 0; - // Inverted logic to be backwards compatible with earlier 6.4. - // It is very strange because rows 1,3,5 are considered "even" - // and 2,4,6 "odd". - // - // First remove any old styles so that both styles aren't - // applied when indexes are updated. - String primaryStyleName = getStylePrimaryName(); - if (primaryStyleName != null && !primaryStyleName.equals("")) { - removeStyleName(getStylePrimaryName()); - } - if (!isOdd) { - addStyleName(VScrollTable.this.getStylePrimaryName() - + "-row-odd"); - } else { - addStyleName(VScrollTable.this.getStylePrimaryName() - + "-row"); - } - } - - public int getIndex() { - return index; - } - - @Override - protected void onDetach() { - super.onDetach(); - client.getContextMenu().ensureHidden(this); - } - - public String getKey() { - return String.valueOf(rowKey); - } - - public void addCell(UIDL rowUidl, String text, char align, - String style, boolean textIsHTML, boolean sorted) { - addCell(rowUidl, text, align, style, textIsHTML, sorted, null); - } - - public void addCell(UIDL rowUidl, String text, char align, - String style, boolean textIsHTML, boolean sorted, - String description) { - // String only content is optimized by not using Label widget - final TableCellElement td = DOM.createTD().cast(); - initCellWithText(text, align, style, textIsHTML, sorted, - description, td); - } - - protected void initCellWithText(String text, char align, - String style, boolean textIsHTML, boolean sorted, - String description, final TableCellElement td) { - final Element container = DOM.createDiv(); - container.setClassName(VScrollTable.this.getStylePrimaryName() - + "-cell-wrapper"); - - td.setClassName(VScrollTable.this.getStylePrimaryName() - + "-cell-content"); - - if (style != null && !style.equals("")) { - td.addClassName(VScrollTable.this.getStylePrimaryName() - + "-cell-content-" + style); - } - - if (sorted) { - td.addClassName(VScrollTable.this.getStylePrimaryName() - + "-cell-content-sorted"); - } - - if (textIsHTML) { - container.setInnerHTML(text); - } else { - container.setInnerText(text); - } - setAlign(align, container); - setTooltip(td, description); - - td.appendChild(container); - getElement().appendChild(td); - } - - protected void updateCellStyleNames(TableCellElement td, - String primaryStyleName) { - Element container = td.getFirstChild().cast(); - container.setClassName(primaryStyleName + "-cell-wrapper"); - - /* - * Replace old primary style name with new one - */ - String className = td.getClassName(); - String oldPrimaryName = className.split("-cell-content")[0]; - td.setClassName(className.replaceAll(oldPrimaryName, - primaryStyleName)); - } - - public void addCell(UIDL rowUidl, Widget w, char align, - String style, boolean sorted, String description) { - final TableCellElement td = DOM.createTD().cast(); - initCellWithWidget(w, align, style, sorted, td); - setTooltip(td, description); - } - - private void setTooltip(TableCellElement td, String description) { - if (description != null && !description.equals("")) { - TooltipInfo info = new TooltipInfo(description, null, this); - cellToolTips.put(td, info); - } else { - cellToolTips.remove(td); - } - - } - - private void setAlign(char align, final Element container) { - switch (align) { - case ALIGN_CENTER: - container.getStyle().setProperty("textAlign", "center"); - break; - case ALIGN_LEFT: - container.getStyle().setProperty("textAlign", "left"); - break; - case ALIGN_RIGHT: - default: - container.getStyle().setProperty("textAlign", "right"); - break; - } - } - - protected void initCellWithWidget(Widget w, char align, - String style, boolean sorted, final TableCellElement td) { - final Element container = DOM.createDiv(); - String className = VScrollTable.this.getStylePrimaryName() - + "-cell-content"; - if (style != null && !style.equals("")) { - className += " " + VScrollTable.this.getStylePrimaryName() - + "-cell-content-" + style; - } - if (sorted) { - className += " " + VScrollTable.this.getStylePrimaryName() - + "-cell-content-sorted"; - } - td.setClassName(className); - container.setClassName(VScrollTable.this.getStylePrimaryName() - + "-cell-wrapper"); - setAlign(align, container); - td.appendChild(container); - getElement().appendChild(td); - // ensure widget not attached to another element (possible tBody - // change) - w.removeFromParent(); - container.appendChild(w.getElement()); - adopt(w); - childWidgets.add(w); - } - - @Override - public Iterator iterator() { - return childWidgets.iterator(); - } - - @Override - public boolean remove(Widget w) { - if (childWidgets.contains(w)) { - orphan(w); - DOM.removeChild(DOM.getParent(w.getElement()), - w.getElement()); - childWidgets.remove(w); - return true; - } else { - return false; - } - } - - /** - * If there are registered click listeners, sends a click event and - * returns true. Otherwise, does nothing and returns false. - * - * @param event - * @param targetTdOrTr - * @param immediate - * Whether the event is sent immediately - * @return Whether a click event was sent - */ - private boolean handleClickEvent(Event event, Element targetTdOrTr, - boolean immediate) { - if (!client.hasEventListeners(VScrollTable.this, - TableConstants.ITEM_CLICK_EVENT_ID)) { - // Don't send an event if nobody is listening - return false; - } - - // This row was clicked - client.updateVariable(paintableId, "clickedKey", "" + rowKey, - false); - - if (getElement() == targetTdOrTr.getParentElement()) { - // A specific column was clicked - int childIndex = DOM.getChildIndex(getElement(), - targetTdOrTr); - String colKey = null; - colKey = tHead.getHeaderCell(childIndex).getColKey(); - client.updateVariable(paintableId, "clickedColKey", colKey, - false); - } - - MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(event); - - client.updateVariable(paintableId, "clickEvent", - details.toString(), immediate); - - return true; - } - - public TooltipInfo getTooltip( - com.google.gwt.dom.client.Element target) { - - TooltipInfo info = null; - final Element targetTdOrTr = getTdOrTr(target); - if (targetTdOrTr != null - && "td".equals(targetTdOrTr.getTagName().toLowerCase())) { - TableCellElement td = (TableCellElement) targetTdOrTr - .cast(); - info = cellToolTips.get(td); - } - - if (info == null) { - info = tooltipInfo; - } - - return info; - } - - private Element getTdOrTr(Element target) { - Element thisTrElement = getElement(); - if (target == thisTrElement) { - // This was a on the TR element - return target; - } - - // Iterate upwards until we find the TR element - Element element = target; - while (element != null - && element.getParentElement() != thisTrElement) { - element = element.getParentElement(); - } - return element; - } - - /** - * Special handler for touch devices that support native scrolling - * - * @return Whether the event was handled by this method. - */ - private boolean handleTouchEvent(final Event event) { - - boolean touchEventHandled = false; - - if (enabled && hasNativeTouchScrolling) { - touchContextProvider.handleTouchEvent(event); - - final Element targetTdOrTr = getEventTargetTdOrTr(event); - final int type = event.getTypeInt(); - - switch (type) { - case Event.ONTOUCHSTART: - touchEventHandled = true; - touchStart = event; - isDragging = false; - Touch touch = event.getChangedTouches().get(0); - // save position to fields, touches in events are same - // instance during the operation. - touchStartX = touch.getClientX(); - touchStartY = touch.getClientY(); - - if (dragmode != 0) { - if (dragTouchTimeout == null) { - dragTouchTimeout = new Timer() { - - @Override - public void run() { - if (touchStart != null) { - // Start a drag if a finger is held - // in place long enough, then moved - isDragging = true; - } - } - }; - } - dragTouchTimeout.schedule(TOUCHSCROLL_TIMEOUT); - } - - if (actionKeys != null) { - if (contextTouchTimeout == null) { - contextTouchTimeout = new Timer() { - - @Override - public void run() { - if (touchStart != null) { - // Open the context menu if finger - // is held in place long enough. - showContextMenu(touchStart); - event.preventDefault(); - touchStart = null; - } - } - }; - } - contextTouchTimeout - .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); - event.stopPropagation(); - } - break; - case Event.ONTOUCHMOVE: - touchEventHandled = true; - if (isSignificantMove(event)) { - if (contextTouchTimeout != null) { - // Moved finger before the context menu timer - // expired, so let the browser handle this as a - // scroll. - contextTouchTimeout.cancel(); - contextTouchTimeout = null; - } - if (!isDragging && dragTouchTimeout != null) { - // Moved finger before the drag timer expired, - // so let the browser handle this as a scroll. - dragTouchTimeout.cancel(); - dragTouchTimeout = null; - } - - if (dragmode != 0 && touchStart != null - && isDragging) { - event.preventDefault(); - event.stopPropagation(); - startRowDrag(touchStart, type, targetTdOrTr); - } - touchStart = null; - } - break; - case Event.ONTOUCHEND: - case Event.ONTOUCHCANCEL: - touchEventHandled = true; - if (contextTouchTimeout != null) { - contextTouchTimeout.cancel(); - } - if (dragTouchTimeout != null) { - dragTouchTimeout.cancel(); - } - if (touchStart != null) { - if (!BrowserInfo.get().isAndroid()) { - event.preventDefault(); - WidgetUtil.simulateClickFromTouchEvent( - touchStart, this); - } - event.stopPropagation(); - touchStart = null; - } - isDragging = false; - break; - } - } - return touchEventHandled; - } - - /* - * React on click that occur on content cells only - */ - - @Override - public void onBrowserEvent(final Event event) { - - final boolean touchEventHandled = handleTouchEvent(event); - - if (enabled && !touchEventHandled) { - final int type = event.getTypeInt(); - final Element targetTdOrTr = getEventTargetTdOrTr(event); - if (type == Event.ONCONTEXTMENU) { - showContextMenu(event); - if (enabled - && (actionKeys != null || client - .hasEventListeners( - VScrollTable.this, - TableConstants.ITEM_CLICK_EVENT_ID))) { - /* - * Prevent browser context menu only if there are - * action handlers or item click listeners - * registered - */ - event.stopPropagation(); - event.preventDefault(); - } - return; - } - - boolean targetCellOrRowFound = targetTdOrTr != null; - - switch (type) { - case Event.ONDBLCLICK: - if (targetCellOrRowFound) { - handleClickEvent(event, targetTdOrTr, true); - } - break; - case Event.ONMOUSEUP: - /* - * Only fire a click if the mouseup hits the same - * element as the corresponding mousedown. This is first - * checked in the event preview but we can't fire the - * event there as the event might get canceled before it - * gets here. - */ - if (mouseUpPreviewMatched - && lastMouseDownTarget != null - && lastMouseDownTarget == getElementTdOrTr(WidgetUtil - .getElementUnderMouse(event))) { - // "Click" with left, right or middle button - - if (targetCellOrRowFound) { - /* - * Queue here, send at the same time as the - * corresponding value change event - see #7127 - */ - boolean clickEventSent = handleClickEvent( - event, targetTdOrTr, false); - - if (event.getButton() == Event.BUTTON_LEFT - && isSelectable()) { - - // Ctrl+Shift click - if ((event.getCtrlKey() || event - .getMetaKey()) - && event.getShiftKey() - && isMultiSelectModeDefault()) { - toggleShiftSelection(false); - setRowFocus(this); - - // Ctrl click - } else if ((event.getCtrlKey() || event - .getMetaKey()) - && isMultiSelectModeDefault()) { - boolean wasSelected = isSelected(); - toggleSelection(); - setRowFocus(this); - /* - * next possible range select must start - * on this row - */ - selectionRangeStart = this; - if (wasSelected) { - removeRowFromUnsentSelectionRanges(this); - } - - } else if ((event.getCtrlKey() || event - .getMetaKey()) - && isSingleSelectMode()) { - // Ctrl (or meta) click (Single - // selection) - if (!isSelected() - || (isSelected() && nullSelectionAllowed)) { - - if (!isSelected()) { - deselectAll(); - } - - toggleSelection(); - setRowFocus(this); - } - - } else if (event.getShiftKey() - && isMultiSelectModeDefault()) { - // Shift click - toggleShiftSelection(true); - - } else { - // click - boolean currentlyJustThisRowSelected = selectedRowKeys - .size() == 1 - && selectedRowKeys - .contains(getKey()); - - if (!currentlyJustThisRowSelected) { - if (isSingleSelectMode() - || isMultiSelectModeDefault()) { - /* - * For default multi select mode - * (ctrl/shift) and for single - * select mode we need to clear - * the previous selection before - * selecting a new one when the - * user clicks on a row. Only in - * multiselect/simple mode the - * old selection should remain - * after a normal click. - */ - deselectAll(); - } - toggleSelection(); - } else if ((isSingleSelectMode() || isMultiSelectModeSimple()) - && nullSelectionAllowed) { - toggleSelection(); - }/* - * else NOP to avoid excessive server - * visits (selection is removed with - * CTRL/META click) - */ - - selectionRangeStart = this; - setRowFocus(this); - } - - // Remove IE text selection hack - if (BrowserInfo.get().isIE()) { - ((Element) event.getEventTarget() - .cast()).setPropertyJSO( - "onselectstart", null); - } - // Queue value change - sendSelectedRows(false); - } - /* - * Send queued click and value change events if - * any If a click event is sent, send value - * change with it regardless of the immediate - * flag, see #7127 - */ - if (immediate || clickEventSent) { - client.sendPendingVariableChanges(); - } - } - } - mouseUpPreviewMatched = false; - lastMouseDownTarget = null; - break; - case Event.ONTOUCHEND: - case Event.ONTOUCHCANCEL: - if (touchStart != null) { - /* - * Touch has not been handled as neither context or - * drag start, handle it as a click. - */ - WidgetUtil.simulateClickFromTouchEvent(touchStart, - this); - touchStart = null; - } - touchContextProvider.cancel(); - break; - case Event.ONTOUCHMOVE: - if (isSignificantMove(event)) { - /* - * TODO figure out scroll delegate don't eat events - * if row is selected. Null check for active - * delegate is as a workaround. - */ - if (dragmode != 0 - && touchStart != null - && (TouchScrollDelegate - .getActiveScrollDelegate() == null)) { - startRowDrag(touchStart, type, targetTdOrTr); - } - touchContextProvider.cancel(); - /* - * Avoid clicks and drags by clearing touch start - * flag. - */ - touchStart = null; - } - - break; - case Event.ONTOUCHSTART: - touchStart = event; - Touch touch = event.getChangedTouches().get(0); - // save position to fields, touches in events are same - // instance during the operation. - touchStartX = touch.getClientX(); - touchStartY = touch.getClientY(); - /* - * Prevent simulated mouse events. - */ - touchStart.preventDefault(); - if (dragmode != 0 || actionKeys != null) { - new Timer() { - - @Override - public void run() { - TouchScrollDelegate activeScrollDelegate = TouchScrollDelegate - .getActiveScrollDelegate(); - /* - * If there's a scroll delegate, check if - * we're actually scrolling and handle it. - * If no delegate, do nothing here and let - * the row handle potential drag'n'drop or - * context menu. - */ - if (activeScrollDelegate != null) { - if (activeScrollDelegate.isMoved()) { - /* - * Prevent the row from handling - * touch move/end events (the - * delegate handles those) and from - * doing drag'n'drop or opening a - * context menu. - */ - touchStart = null; - } else { - /* - * Scrolling hasn't started, so - * cancel delegate and let the row - * handle potential drag'n'drop or - * context menu. - */ - activeScrollDelegate - .stopScrolling(); - } - } - } - }.schedule(TOUCHSCROLL_TIMEOUT); - - if (contextTouchTimeout == null - && actionKeys != null) { - contextTouchTimeout = new Timer() { - - @Override - public void run() { - if (touchStart != null) { - showContextMenu(touchStart); - touchStart = null; - } - } - }; - } - if (contextTouchTimeout != null) { - contextTouchTimeout.cancel(); - contextTouchTimeout - .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); - } - } - break; - case Event.ONMOUSEDOWN: - /* - * When getting a mousedown event, we must detect where - * the corresponding mouseup event if it's on a - * different part of the page. - */ - lastMouseDownTarget = getElementTdOrTr(WidgetUtil - .getElementUnderMouse(event)); - mouseUpPreviewMatched = false; - mouseUpEventPreviewRegistration = Event - .addNativePreviewHandler(mouseUpPreviewHandler); - - if (targetCellOrRowFound) { - setRowFocus(this); - ensureFocus(); - if (dragmode != 0 - && (event.getButton() == NativeEvent.BUTTON_LEFT)) { - startRowDrag(event, type, targetTdOrTr); - - } else if (event.getCtrlKey() - || event.getShiftKey() - || event.getMetaKey() - && isMultiSelectModeDefault()) { - - // Prevent default text selection in Firefox - event.preventDefault(); - - // Prevent default text selection in IE - if (BrowserInfo.get().isIE()) { - ((Element) event.getEventTarget().cast()) - .setPropertyJSO( - "onselectstart", - getPreventTextSelectionIEHack()); - } - - event.stopPropagation(); - } - } - break; - case Event.ONMOUSEOUT: - break; - default: - break; - } - } - super.onBrowserEvent(event); - } - - private boolean isSignificantMove(Event event) { - if (touchStart == null) { - // no touch start - return false; - } - /* - * TODO calculate based on real distance instead of separate - * axis checks - */ - Touch touch = event.getChangedTouches().get(0); - if (Math.abs(touch.getClientX() - touchStartX) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) { - return true; - } - if (Math.abs(touch.getClientY() - touchStartY) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) { - return true; - } - return false; - } - - /** - * Checks if the row represented by the row key has been selected - * - * @param key - * The generated row key - */ - private boolean rowKeyIsSelected(int rowKey) { - // Check single selections - if (selectedRowKeys.contains("" + rowKey)) { - return true; - } - - // Check range selections - for (SelectionRange r : selectedRowRanges) { - if (r.inRange(getRenderedRowByKey("" + rowKey))) { - return true; - } - } - return false; - } - - protected void startRowDrag(Event event, final int type, - Element targetTdOrTr) { - VTransferable transferable = new VTransferable(); - transferable.setDragSource(ConnectorMap.get(client) - .getConnector(VScrollTable.this)); - transferable.setData("itemId", "" + rowKey); - NodeList cells = rowElement.getCells(); - for (int i = 0; i < cells.getLength(); i++) { - if (cells.getItem(i).isOrHasChild(targetTdOrTr)) { - HeaderCell headerCell = tHead.getHeaderCell(i); - transferable.setData("propertyId", headerCell.cid); - break; - } - } - - VDragEvent ev = VDragAndDropManager.get().startDrag( - transferable, event, true); - if (dragmode == DRAGMODE_MULTIROW && isMultiSelectModeAny() - && rowKeyIsSelected(rowKey)) { - - // Create a drag image of ALL rows - ev.createDragImage(scrollBody.tBodyElement, true); - - // Hide rows which are not selected - Element dragImage = ev.getDragImage(); - int i = 0; - for (Iterator iterator = scrollBody.iterator(); iterator - .hasNext();) { - VScrollTableRow next = (VScrollTableRow) iterator - .next(); - - Element child = (Element) dragImage.getChild(i++); - - if (!rowKeyIsSelected(next.rowKey)) { - child.getStyle().setVisibility(Visibility.HIDDEN); - } - } - } else { - ev.createDragImage(getElement(), true); - } - if (type == Event.ONMOUSEDOWN) { - event.preventDefault(); - } - event.stopPropagation(); - } - - /** - * Finds the TD that the event interacts with. Returns null if the - * target of the event should not be handled. If the event target is - * the row directly this method returns the TR element instead of - * the TD. - * - * @param event - * @return TD or TR element that the event targets (the actual event - * target is this element or a child of it) - */ - private Element getEventTargetTdOrTr(Event event) { - final Element eventTarget = event.getEventTarget().cast(); - return getElementTdOrTr(eventTarget); - } - - private Element getElementTdOrTr(Element element) { - - Widget widget = WidgetUtil.findWidget(element, null); - - if (widget != this) { - /* - * This is a workaround to make Labels, read only TextFields - * and Embedded in a Table clickable (see #2688). It is - * really not a fix as it does not work with a custom read - * only components (not extending VLabel/VEmbedded). - */ - while (widget != null && widget.getParent() != this) { - widget = widget.getParent(); - } - - if (!(widget instanceof VLabel) - && !(widget instanceof VEmbedded) - && !(widget instanceof VTextField && ((VTextField) widget) - .isReadOnly())) { - return null; - } - } - return getTdOrTr(element); - } - - @Override - public void showContextMenu(Event event) { - if (enabled && actionKeys != null) { - // Show context menu if there are registered action handlers - int left = WidgetUtil.getTouchOrMouseClientX(event) - + Window.getScrollLeft(); - int top = WidgetUtil.getTouchOrMouseClientY(event) - + Window.getScrollTop(); - showContextMenu(left, top); - } - } - - public void showContextMenu(int left, int top) { - VContextMenu menu = client.getContextMenu(); - contextMenu = new ContextMenuDetails(menu, getKey(), left, top); - menu.showAt(this, left, top); - } - - /** - * Has the row been selected? - * - * @return Returns true if selected, else false - */ - public boolean isSelected() { - return selected; - } - - /** - * Toggle the selection of the row - */ - public void toggleSelection() { - selected = !selected; - selectionChanged = true; - if (selected) { - selectedRowKeys.add(String.valueOf(rowKey)); - addStyleName("v-selected"); - } else { - removeStyleName("v-selected"); - selectedRowKeys.remove(String.valueOf(rowKey)); - } - } - - /** - * Is called when a user clicks an item when holding SHIFT key down. - * This will select a new range from the last focused row - * - * @param deselectPrevious - * Should the previous selected range be deselected - */ - private void toggleShiftSelection(boolean deselectPrevious) { - - /* - * Ensures that we are in multiselect mode and that we have a - * previous selection which was not a deselection - */ - if (isSingleSelectMode()) { - // No previous selection found - deselectAll(); - toggleSelection(); - return; - } - - // Set the selectable range - VScrollTableRow endRow = this; - VScrollTableRow startRow = selectionRangeStart; - if (startRow == null) { - startRow = focusedRow; - selectionRangeStart = focusedRow; - // If start row is null then we have a multipage selection - // from above - if (startRow == null) { - startRow = (VScrollTableRow) scrollBody.iterator() - .next(); - setRowFocus(endRow); - } - } else if (!startRow.isSelected()) { - // The start row is no longer selected (probably removed) - // and so we select from above - startRow = (VScrollTableRow) scrollBody.iterator().next(); - setRowFocus(endRow); - } - - // Deselect previous items if so desired - if (deselectPrevious) { - deselectAll(); - } - - // we'll ensure GUI state from top down even though selection - // was the opposite way - if (!startRow.isBefore(endRow)) { - VScrollTableRow tmp = startRow; - startRow = endRow; - endRow = tmp; - } - SelectionRange range = new SelectionRange(startRow, endRow); - - for (Widget w : scrollBody) { - VScrollTableRow row = (VScrollTableRow) w; - if (range.inRange(row)) { - if (!row.isSelected()) { - row.toggleSelection(); - } - selectedRowKeys.add(row.getKey()); - } - } - - // Add range - if (startRow != endRow) { - selectedRowRanges.add(range); - } - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.IActionOwner#getActions () - */ - - @Override - public Action[] getActions() { - if (actionKeys == null) { - return new Action[] {}; - } - final Action[] actions = new Action[actionKeys.length]; - for (int i = 0; i < actions.length; i++) { - final String actionKey = actionKeys[i]; - final TreeAction a = new TreeAction(this, - String.valueOf(rowKey), actionKey) { - - @Override - public void execute() { - super.execute(); - lazyRevertFocusToRow(VScrollTableRow.this); - } - }; - a.setCaption(getActionCaption(actionKey)); - a.setIconUrl(getActionIcon(actionKey)); - actions[i] = a; - } - return actions; - } - - @Override - public ApplicationConnection getClient() { - return client; - } - - @Override - public String getPaintableId() { - return paintableId; - } - - private int getColIndexOf(Widget child) { - com.google.gwt.dom.client.Element widgetCell = child - .getElement().getParentElement().getParentElement(); - NodeList cells = rowElement.getCells(); - for (int i = 0; i < cells.getLength(); i++) { - if (cells.getItem(i) == widgetCell) { - return i; - } - } - return -1; - } - - public Widget getWidgetForPaintable() { - return this; - } - } - - protected class VScrollTableGeneratedRow extends VScrollTableRow { - - private boolean spanColumns; - private boolean htmlContentAllowed; - - public VScrollTableGeneratedRow(UIDL uidl, char[] aligns) { - super(uidl, aligns); - addStyleName("v-table-generated-row"); - } - - public boolean isSpanColumns() { - return spanColumns; - } - - @Override - protected void initCellWidths() { - if (spanColumns) { - setSpannedColumnWidthAfterDOMFullyInited(); - } else { - super.initCellWidths(); - } - } - - private void setSpannedColumnWidthAfterDOMFullyInited() { - // Defer setting width on spanned columns to make sure that - // they are added to the DOM before trying to calculate - // widths. - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - - @Override - public void execute() { - if (showRowHeaders) { - setCellWidth(0, tHead.getHeaderCell(0) - .getWidthWithIndent()); - calcAndSetSpanWidthOnCell(1); - } else { - calcAndSetSpanWidthOnCell(0); - } - } - }); - } - - @Override - protected boolean isRenderHtmlInCells() { - return htmlContentAllowed; - } - - @Override - protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, - int visibleColumnIndex) { - htmlContentAllowed = uidl.getBooleanAttribute("gen_html"); - spanColumns = uidl.getBooleanAttribute("gen_span"); - - final Iterator cells = uidl.getChildIterator(); - if (spanColumns) { - int colCount = uidl.getChildCount(); - if (cells.hasNext()) { - final Object cell = cells.next(); - if (cell instanceof String) { - addSpannedCell(uidl, cell.toString(), aligns[0], - "", htmlContentAllowed, false, null, - colCount); - } else { - addSpannedCell(uidl, (Widget) cell, aligns[0], "", - false, colCount); - } - } - } else { - super.addCellsFromUIDL(uidl, aligns, col, - visibleColumnIndex); - } - } - - private void addSpannedCell(UIDL rowUidl, Widget w, char align, - String style, boolean sorted, int colCount) { - TableCellElement td = DOM.createTD().cast(); - td.setColSpan(colCount); - initCellWithWidget(w, align, style, sorted, td); - } - - private void addSpannedCell(UIDL rowUidl, String text, char align, - String style, boolean textIsHTML, boolean sorted, - String description, int colCount) { - // String only content is optimized by not using Label widget - final TableCellElement td = DOM.createTD().cast(); - td.setColSpan(colCount); - initCellWithText(text, align, style, textIsHTML, sorted, - description, td); - } - - @Override - protected void setCellWidth(int cellIx, int width) { - if (isSpanColumns()) { - if (showRowHeaders) { - if (cellIx == 0) { - super.setCellWidth(0, width); - } else { - // We need to recalculate the spanning TDs width for - // every cellIx in order to support column resizing. - calcAndSetSpanWidthOnCell(1); - } - } else { - // Same as above. - calcAndSetSpanWidthOnCell(0); - } - } else { - super.setCellWidth(cellIx, width); - } - } - - private void calcAndSetSpanWidthOnCell(final int cellIx) { - int spanWidth = 0; - for (int ix = (showRowHeaders ? 1 : 0); ix < tHead - .getVisibleCellCount(); ix++) { - spanWidth += tHead.getHeaderCell(ix).getOffsetWidth(); - } - WidgetUtil.setWidthExcludingPaddingAndBorder( - (Element) getElement().getChild(cellIx), spanWidth, 13, - false); - } - } - - /** - * Ensure the component has a focus. - * - * TODO the current implementation simply always calls focus for the - * component. In case the Table at some point implements focus/blur - * listeners, this method needs to be evolved to conditionally call - * focus only if not currently focused. - */ - protected void ensureFocus() { - if (!hasFocus) { - scrollBodyPanel.setFocus(true); - } - - } - - } - - /** - * Deselects all items - */ - public void deselectAll() { - for (Widget w : scrollBody) { - VScrollTableRow row = (VScrollTableRow) w; - if (row.isSelected()) { - row.toggleSelection(); - } - } - // still ensure all selects are removed from (not necessary rendered) - selectedRowKeys.clear(); - selectedRowRanges.clear(); - // also notify server that it clears all previous selections (the client - // side does not know about the invisible ones) - instructServerToForgetPreviousSelections(); - } - - /** - * Used in multiselect mode when the client side knows that all selections - * are in the next request. - */ - private void instructServerToForgetPreviousSelections() { - client.updateVariable(paintableId, "clearSelections", true, false); - } - - /** - * Determines the pagelength when the table height is fixed. - */ - public void updatePageLength() { - // Only update if visible and enabled - if (!isVisible() || !enabled) { - return; - } - - if (scrollBody == null) { - return; - } - - if (isDynamicHeight()) { - return; - } - - int rowHeight = (int) Math.round(scrollBody.getRowHeight()); - int bodyH = scrollBodyPanel.getOffsetHeight(); - int rowsAtOnce = bodyH / rowHeight; - boolean anotherPartlyVisible = ((bodyH % rowHeight) != 0); - if (anotherPartlyVisible) { - rowsAtOnce++; - } - if (pageLength != rowsAtOnce) { - pageLength = rowsAtOnce; - client.updateVariable(paintableId, "pagelength", pageLength, false); - - if (!rendering) { - int currentlyVisible = scrollBody.getLastRendered() - - scrollBody.getFirstRendered(); - if (currentlyVisible < pageLength - && currentlyVisible < totalRows) { - // shake scrollpanel to fill empty space - scrollBodyPanel.setScrollPosition(scrollTop + 1); - scrollBodyPanel.setScrollPosition(scrollTop - 1); - } - - sizeNeedsInit = true; - } - } - - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateWidth() { - if (!isVisible()) { - /* - * Do not update size when the table is hidden as all column widths - * will be set to zero and they won't be recalculated when the table - * is set visible again (until the size changes again) - */ - return; - } - - if (!isDynamicWidth()) { - int innerPixels = getOffsetWidth() - getBorderWidth(); - if (innerPixels < 0) { - innerPixels = 0; - } - setContentWidth(innerPixels); - - // readjust undefined width columns - triggerLazyColumnAdjustment(false); - - } else { - - sizeNeedsInit = true; - - // readjust undefined width columns - triggerLazyColumnAdjustment(false); - } - - /* - * setting width may affect wheter the component has scrollbars -> needs - * scrolling or not - */ - setProperTabIndex(); - } - - private static final int LAZY_COLUMN_ADJUST_TIMEOUT = 300; - - private final Timer lazyAdjustColumnWidths = new Timer() { - /** - * Check for column widths, and available width, to see if we can fix - * column widths "optimally". Doing this lazily to avoid expensive - * calculation when resizing is not yet finished. - */ - - @Override - public void run() { - if (scrollBody == null) { - // Try again later if we get here before scrollBody has been - // initalized - triggerLazyColumnAdjustment(false); - return; - } - - Iterator headCells = tHead.iterator(); - int usedMinimumWidth = 0; - int totalExplicitColumnsWidths = 0; - float expandRatioDivider = 0; - int colIndex = 0; - - int hierarchyColumnIndent = scrollBody.getMaxIndent(); - int hierarchyColumnIndex = getHierarchyColumnIndex(); - HeaderCell hierarchyHeaderInNeedOfFurtherHandling = null; - - while (headCells.hasNext()) { - final HeaderCell hCell = (HeaderCell) headCells.next(); - boolean hasIndent = hierarchyColumnIndent > 0 - && hCell.isHierarchyColumn(); - if (hCell.isDefinedWidth()) { - // get width without indent to find out whether adjustments - // are needed (requires special handling further ahead) - int w = hCell.getWidth(); - if (hasIndent && w < hierarchyColumnIndent) { - // enforce indent if necessary - w = hierarchyColumnIndent; - hierarchyHeaderInNeedOfFurtherHandling = hCell; - } - totalExplicitColumnsWidths += w; - usedMinimumWidth += w; - } else { - // natural width already includes indent if any - int naturalColumnWidth = hCell - .getNaturalColumnWidth(colIndex); - /* - * TODO If there is extra width, expand ratios are for - * additional extra widths, not for absolute column widths. - * Should be fixed in sizeInit(), too. - */ - if (hCell.getExpandRatio() > 0) { - naturalColumnWidth = 0; - } - usedMinimumWidth += naturalColumnWidth; - expandRatioDivider += hCell.getExpandRatio(); - if (hasIndent) { - hierarchyHeaderInNeedOfFurtherHandling = hCell; - } - } - colIndex++; - } - - int availW = scrollBody.getAvailableWidth(); - // Hey IE, are you really sure about this? - availW = scrollBody.getAvailableWidth(); - int visibleCellCount = tHead.getVisibleCellCount(); - int totalExtraWidth = scrollBody.getCellExtraWidth() - * visibleCellCount; - if (willHaveScrollbars()) { - totalExtraWidth += WidgetUtil.getNativeScrollbarSize(); - // if there will be vertical scrollbar, let's enable it - scrollBodyPanel.getElement().getStyle().clearOverflowY(); - } else { - // if there is no need for vertical scrollbar, let's disable it - // this is necessary since sometimes the browsers insist showing - // the scrollbar even if the content would fit perfectly - scrollBodyPanel.getElement().getStyle() - .setOverflowY(Overflow.HIDDEN); - } - - availW -= totalExtraWidth; - int forceScrollBodyWidth = -1; - - int extraSpace = availW - usedMinimumWidth; - if (extraSpace < 0) { - if (getTotalRows() == 0) { - /* - * Too wide header combined with no rows in the table. - * - * No horizontal scrollbars would be displayed because - * there's no rows that grows too wide causing the - * scrollBody container div to overflow. Must explicitely - * force a width to a scrollbar. (see #9187) - */ - forceScrollBodyWidth = usedMinimumWidth + totalExtraWidth; - } - extraSpace = 0; - // if there will be horizontal scrollbar, let's enable it - scrollBodyPanel.getElement().getStyle().clearOverflowX(); - } else { - // if there is no need for horizontal scrollbar, let's disable - // it - // this is necessary since sometimes the browsers insist showing - // the scrollbar even if the content would fit perfectly - scrollBodyPanel.getElement().getStyle() - .setOverflowX(Overflow.HIDDEN); - } - - if (forceScrollBodyWidth > 0) { - scrollBody.container.getStyle().setWidth(forceScrollBodyWidth, - Unit.PX); - } else { - // Clear width that might have been set to force horizontal - // scrolling if there are no rows - scrollBody.container.getStyle().clearWidth(); - } - - int totalUndefinedNaturalWidths = usedMinimumWidth - - totalExplicitColumnsWidths; - - if (hierarchyHeaderInNeedOfFurtherHandling != null - && !hierarchyHeaderInNeedOfFurtherHandling.isDefinedWidth()) { - // ensure the cell gets enough space for the indent - int w = hierarchyHeaderInNeedOfFurtherHandling - .getNaturalColumnWidth(hierarchyColumnIndex); - int newSpace = Math.round(w + (float) extraSpace * (float) w - / totalUndefinedNaturalWidths); - if (newSpace >= hierarchyColumnIndent) { - // no special handling required - hierarchyHeaderInNeedOfFurtherHandling = null; - } else { - // treat as a defined width column of indent's width - totalExplicitColumnsWidths += hierarchyColumnIndent; - usedMinimumWidth -= w - hierarchyColumnIndent; - totalUndefinedNaturalWidths = usedMinimumWidth - - totalExplicitColumnsWidths; - expandRatioDivider += hierarchyHeaderInNeedOfFurtherHandling - .getExpandRatio(); - extraSpace = Math.max(availW - usedMinimumWidth, 0); - } - } - - // we have some space that can be divided optimally - HeaderCell hCell; - colIndex = 0; - headCells = tHead.iterator(); - int checksum = 0; - while (headCells.hasNext()) { - hCell = (HeaderCell) headCells.next(); - if (hCell.isResizing) { - continue; - } - if (!hCell.isDefinedWidth()) { - int w = hCell.getNaturalColumnWidth(colIndex); - int newSpace; - if (expandRatioDivider > 0) { - // divide excess space by expand ratios - if (hCell.getExpandRatio() > 0) { - w = 0; - } - newSpace = Math.round((w + extraSpace - * hCell.getExpandRatio() / expandRatioDivider)); - } else { - if (hierarchyHeaderInNeedOfFurtherHandling == hCell) { - // still exists, so needs exactly the indent's width - newSpace = hierarchyColumnIndent; - } else if (totalUndefinedNaturalWidths != 0) { - // divide relatively to natural column widths - newSpace = Math.round(w + (float) extraSpace - * (float) w / totalUndefinedNaturalWidths); - } else { - newSpace = w; - } - } - checksum += newSpace; - setColWidth(colIndex, newSpace, false); - - } else { - if (hierarchyHeaderInNeedOfFurtherHandling == hCell) { - // defined with enforced into indent width - checksum += hierarchyColumnIndent; - setColWidth(colIndex, hierarchyColumnIndent, false); - } else { - int cellWidth = hCell.getWidthWithIndent(); - checksum += cellWidth; - if (hCell.isHierarchyColumn()) { - // update in case the indent has changed - // (not detectable earlier) - setColWidth(colIndex, cellWidth, true); - } - } - } - colIndex++; - } - - if (extraSpace > 0 && checksum != availW) { - /* - * There might be in some cases a rounding error of 1px when - * extra space is divided so if there is one then we give the - * first undefined column 1 more pixel - */ - headCells = tHead.iterator(); - colIndex = 0; - while (headCells.hasNext()) { - HeaderCell hc = (HeaderCell) headCells.next(); - if (!hc.isResizing && !hc.isDefinedWidth()) { - setColWidth(colIndex, hc.getWidthWithIndent() + availW - - checksum, false); - break; - } - colIndex++; - } - } - - if (isDynamicHeight() && totalRows == pageLength) { - // fix body height (may vary if lazy loading is offhorizontal - // scrollbar appears/disappears) - int bodyHeight = scrollBody.getRequiredHeight(); - boolean needsSpaceForHorizontalScrollbar = (availW < usedMinimumWidth); - if (needsSpaceForHorizontalScrollbar) { - bodyHeight += WidgetUtil.getNativeScrollbarSize(); - } - int heightBefore = getOffsetHeight(); - scrollBodyPanel.setHeight(bodyHeight + "px"); - - if (heightBefore != getOffsetHeight()) { - Util.notifyParentOfSizeChange(VScrollTable.this, rendering); - } - } - - forceRealignColumnHeaders(); - } - - }; - - private void forceRealignColumnHeaders() { - if (BrowserInfo.get().isIE()) { - /* - * IE does not fire onscroll event if scroll position is reverted to - * 0 due to the content element size growth. Ensure headers are in - * sync with content manually. Safe to use null event as we don't - * actually use the event object in listener. - */ - onScroll(null); - } - } - - /** - * helper to set pixel size of head and body part - * - * @param pixels - */ - private void setContentWidth(int pixels) { - tHead.setWidth(pixels + "px"); - scrollBodyPanel.setWidth(pixels + "px"); - tFoot.setWidth(pixels + "px"); - } - - private int borderWidth = -1; - - /** - * @return border left + border right - */ - private int getBorderWidth() { - if (borderWidth < 0) { - borderWidth = WidgetUtil.measureHorizontalPaddingAndBorder( - scrollBodyPanel.getElement(), 2); - if (borderWidth < 0) { - borderWidth = 0; - } - } - return borderWidth; - } - - /** - * Ensures scrollable area is properly sized. This method is used when fixed - * size is used. - */ - private int containerHeight; - - private void setContainerHeight() { - if (!isDynamicHeight()) { - - /* - * Android 2.3 cannot measure the height of the inline-block - * properly, and will return the wrong offset height. So for android - * 2.3 we set the element to a block element while measuring and - * then restore it which yields the correct result. #11331 - */ - if (BrowserInfo.get().isAndroid23()) { - getElement().getStyle().setDisplay(Display.BLOCK); - } - - containerHeight = getOffsetHeight(); - containerHeight -= showColHeaders ? tHead.getOffsetHeight() : 0; - containerHeight -= tFoot.getOffsetHeight(); - containerHeight -= getContentAreaBorderHeight(); - if (containerHeight < 0) { - containerHeight = 0; - } - - scrollBodyPanel.setHeight(containerHeight + "px"); - - if (BrowserInfo.get().isAndroid23()) { - getElement().getStyle().clearDisplay(); - } - } - } - - private int contentAreaBorderHeight = -1; - private int scrollLeft; - private int scrollTop; - - /** For internal use only. May be removed or replaced in the future. */ - public VScrollTableDropHandler dropHandler; - - private boolean navKeyDown; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean multiselectPending; - - /** For internal use only. May be removed or replaced in the future. */ - public CollapseMenuContent collapsibleMenuContent; - - /** - * @return border top + border bottom of the scrollable area of table - */ - private int getContentAreaBorderHeight() { - if (contentAreaBorderHeight < 0) { - - scrollBodyPanel.getElement().getStyle() - .setOverflow(Overflow.HIDDEN); - int oh = scrollBodyPanel.getOffsetHeight(); - int ch = scrollBodyPanel.getElement() - .getPropertyInt("clientHeight"); - contentAreaBorderHeight = oh - ch; - scrollBodyPanel.getElement().getStyle().setOverflow(Overflow.AUTO); - } - return contentAreaBorderHeight; - } - - @Override - public void setHeight(String height) { - if (height.length() == 0 - && getElement().getStyle().getHeight().length() != 0) { - /* - * Changing from defined to undefined size -> should do a size init - * to take page length into account again - */ - sizeNeedsInit = true; - } - super.setHeight(height); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateHeight() { - setContainerHeight(); - - if (initializedAndAttached) { - updatePageLength(); - } - - triggerLazyColumnAdjustment(false); - - /* - * setting height may affect wheter the component has scrollbars -> - * needs scrolling or not - */ - setProperTabIndex(); - - } - - /* - * Overridden due Table might not survive of visibility change (scroll pos - * lost). Example ITabPanel just set contained components invisible and back - * when changing tabs. - */ - - @Override - public void setVisible(boolean visible) { - if (isVisible() != visible) { - super.setVisible(visible); - if (initializedAndAttached) { - if (visible) { - Scheduler.get().scheduleDeferred(new Command() { - - @Override - public void execute() { - scrollBodyPanel - .setScrollPosition(measureRowHeightOffset(firstRowInViewPort)); - } - }); - } - } - } - } - - /** - * Helper function to build html snippet for column or row headers - * - * @param uidl - * possibly with values caption and icon - * @return html snippet containing possibly an icon + caption text - */ - protected String buildCaptionHtmlSnippet(UIDL uidl) { - String s = uidl.hasAttribute("caption") ? uidl - .getStringAttribute("caption") : ""; - if (uidl.hasAttribute("icon")) { - Icon icon = client.getIcon(uidl.getStringAttribute("icon")); - icon.setAlternateText("icon"); - s = icon.getElement().getString() + s; - } - return s; - } - - // Updates first visible row for the case we cannot wait - // for onScroll - private void updateFirstVisibleRow() { - scrollTop = scrollBodyPanel.getScrollPosition(); - firstRowInViewPort = calcFirstRowInViewPort(); - int maxFirstRow = totalRows - pageLength; - if (firstRowInViewPort > maxFirstRow && maxFirstRow >= 0) { - firstRowInViewPort = maxFirstRow; - } - lastRequestedFirstvisible = firstRowInViewPort; - client.updateVariable(paintableId, "firstvisible", firstRowInViewPort, - false); - } - - /** - * This method has logic which rows needs to be requested from server when - * user scrolls - */ - - @Override - public void onScroll(ScrollEvent event) { - // Do not handle scroll events while there is scroll initiated from - // server side which is not yet executed (#11454) - if (isLazyScrollerActive()) { - return; - } - - scrollLeft = scrollBodyPanel.getElement().getScrollLeft(); - scrollTop = scrollBodyPanel.getScrollPosition(); - /* - * #6970 - IE sometimes fires scroll events for a detached table. - * - * FIXME initializedAndAttached should probably be renamed - its name - * doesn't seem to reflect its semantics. onDetach() doesn't set it to - * false, and changing that might break something else, so we need to - * check isAttached() separately. - */ - if (!initializedAndAttached || !isAttached()) { - return; - } - if (!enabled) { - scrollBodyPanel - .setScrollPosition(measureRowHeightOffset(firstRowInViewPort)); - return; - } - - rowRequestHandler.cancel(); - - if (BrowserInfo.get().isSafari() && event != null && scrollTop == 0) { - // due to the webkitoverflowworkaround, top may sometimes report 0 - // for webkit, although it really is not. Expecting to have the - // correct - // value available soon. - Scheduler.get().scheduleDeferred(new Command() { - - @Override - public void execute() { - onScroll(null); - } - }); - return; - } - - // fix headers horizontal scrolling - tHead.setHorizontalScrollPosition(scrollLeft); - - // fix footers horizontal scrolling - tFoot.setHorizontalScrollPosition(scrollLeft); - - if (totalRows == 0) { - // No rows, no need to fetch new rows - return; - } - - firstRowInViewPort = calcFirstRowInViewPort(); - int maxFirstRow = totalRows - pageLength; - if (firstRowInViewPort > maxFirstRow && maxFirstRow >= 0) { - firstRowInViewPort = maxFirstRow; - } - - int postLimit = (int) (firstRowInViewPort + (pageLength - 1) + pageLength - * cache_react_rate); - if (postLimit > totalRows - 1) { - postLimit = totalRows - 1; - } - int preLimit = (int) (firstRowInViewPort - pageLength - * cache_react_rate); - if (preLimit < 0) { - preLimit = 0; - } - final int lastRendered = scrollBody.getLastRendered(); - final int firstRendered = scrollBody.getFirstRendered(); - - if (postLimit <= lastRendered && preLimit >= firstRendered) { - // we're within no-react area, no need to request more rows - // remember which firstvisible we requested, in case the server has - // a differing opinion - lastRequestedFirstvisible = firstRowInViewPort; - client.updateVariable(paintableId, "firstvisible", - firstRowInViewPort, false); - return; - } - - if (allRenderedRowsAreNew()) { - // need a totally new set of rows - rowRequestHandler - .setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate))); - int last = firstRowInViewPort + (int) (cache_rate * pageLength) - + pageLength - 1; - if (last >= totalRows) { - last = totalRows - 1; - } - rowRequestHandler.setReqRows(last - - rowRequestHandler.getReqFirstRow() + 1); - updatedReqRows = false; - rowRequestHandler.deferRowFetch(); - return; - } - if (preLimit < firstRendered) { - // need some rows to the beginning of the rendered area - - rowRequestHandler - .setReqFirstRow((int) (firstRowInViewPort - pageLength - * cache_rate)); - rowRequestHandler.setReqRows(firstRendered - - rowRequestHandler.getReqFirstRow()); - rowRequestHandler.deferRowFetch(); - - return; - } - if (postLimit > lastRendered) { - // need some rows to the end of the rendered area - int reqRows = (int) ((firstRowInViewPort + pageLength + pageLength - * cache_rate) - lastRendered); - rowRequestHandler.triggerRowFetch(lastRendered + 1, reqRows); - } - } - - private boolean allRenderedRowsAreNew() { - int firstRowInViewPort = calcFirstRowInViewPort(); - int firstRendered = scrollBody.getFirstRendered(); - int lastRendered = scrollBody.getLastRendered(); - return (firstRowInViewPort - pageLength * cache_rate > lastRendered || firstRowInViewPort - + pageLength + pageLength * cache_rate < firstRendered); - } - - protected int calcFirstRowInViewPort() { - return (int) Math.ceil(scrollTop / scrollBody.getRowHeight()); - } - - @Override - public VScrollTableDropHandler getDropHandler() { - return dropHandler; - } - - private static class TableDDDetails { - int overkey = -1; - VerticalDropLocation dropLocation; - String colkey; - - @Override - public boolean equals(Object obj) { - if (obj instanceof TableDDDetails) { - TableDDDetails other = (TableDDDetails) obj; - return dropLocation == other.dropLocation - && overkey == other.overkey - && ((colkey != null && colkey.equals(other.colkey)) || (colkey == null && other.colkey == null)); - } - return false; - } - - // - // public int hashCode() { - // return overkey; - // } - } - - public class VScrollTableDropHandler extends VAbstractDropHandler { - - private static final String ROWSTYLEBASE = "v-table-row-drag-"; - private TableDDDetails dropDetails; - private TableDDDetails lastEmphasized; - - @Override - public void dragEnter(VDragEvent drag) { - updateDropDetails(drag); - super.dragEnter(drag); - } - - private void updateDropDetails(VDragEvent drag) { - dropDetails = new TableDDDetails(); - Element elementOver = drag.getElementOver(); - - Class clazz = getRowClass(); - VScrollTableRow row = null; - if (clazz != null) { - row = WidgetUtil.findWidget(elementOver, clazz); - } - if (row != null) { - dropDetails.overkey = row.rowKey; - Element tr = row.getElement(); - Element element = elementOver; - while (element != null && element.getParentElement() != tr) { - element = element.getParentElement(); - } - int childIndex = DOM.getChildIndex(tr, element); - dropDetails.colkey = tHead.getHeaderCell(childIndex) - .getColKey(); - dropDetails.dropLocation = DDUtil.getVerticalDropLocation( - row.getElement(), drag.getCurrentGwtEvent(), 0.2); - } - - drag.getDropDetails().put("itemIdOver", dropDetails.overkey + ""); - drag.getDropDetails().put( - "detail", - dropDetails.dropLocation != null ? dropDetails.dropLocation - .toString() : null); - - } - - private Class getRowClass() { - // get the row type this way to make dd work in derived - // implementations - Iterator iterator = scrollBody.iterator(); - if (iterator.hasNext()) { - return iterator.next().getClass(); - } else { - return null; - } - } - - @Override - public void dragOver(VDragEvent drag) { - TableDDDetails oldDetails = dropDetails; - updateDropDetails(drag); - if (!oldDetails.equals(dropDetails)) { - deEmphasis(); - final TableDDDetails newDetails = dropDetails; - VAcceptCallback cb = new VAcceptCallback() { - - @Override - public void accepted(VDragEvent event) { - if (newDetails.equals(dropDetails)) { - dragAccepted(event); - } - /* - * Else new target slot already defined, ignore - */ - } - }; - validate(cb, drag); - } - } - - @Override - public void dragLeave(VDragEvent drag) { - deEmphasis(); - super.dragLeave(drag); - } - - @Override - public boolean drop(VDragEvent drag) { - deEmphasis(); - return super.drop(drag); - } - - private void deEmphasis() { - UIObject.setStyleName(getElement(), - getStylePrimaryName() + "-drag", false); - if (lastEmphasized == null) { - return; - } - for (Widget w : scrollBody.renderedRows) { - VScrollTableRow row = (VScrollTableRow) w; - if (lastEmphasized != null - && row.rowKey == lastEmphasized.overkey) { - String stylename = ROWSTYLEBASE - + lastEmphasized.dropLocation.toString() - .toLowerCase(); - VScrollTableRow.setStyleName(row.getElement(), stylename, - false); - lastEmphasized = null; - return; - } - } - } - - /** - * TODO needs different drop modes ?? (on cells, on rows), now only - * supports rows - */ - private void emphasis(TableDDDetails details) { - deEmphasis(); - UIObject.setStyleName(getElement(), - getStylePrimaryName() + "-drag", true); - // iterate old and new emphasized row - for (Widget w : scrollBody.renderedRows) { - VScrollTableRow row = (VScrollTableRow) w; - if (details != null && details.overkey == row.rowKey) { - String stylename = ROWSTYLEBASE - + details.dropLocation.toString().toLowerCase(); - VScrollTableRow.setStyleName(row.getElement(), stylename, - true); - lastEmphasized = details; - return; - } - } - } - - @Override - protected void dragAccepted(VDragEvent drag) { - emphasis(dropDetails); - } - - @Override - public ComponentConnector getConnector() { - return ConnectorMap.get(client).getConnector(VScrollTable.this); - } - - @Override - public ApplicationConnection getApplicationConnection() { - return client; - } - - } - - protected VScrollTableRow getFocusedRow() { - return focusedRow; - } - - /** - * Moves the selection head to a specific row - * - * @param row - * The row to where the selection head should move - * @return Returns true if focus was moved successfully, else false - */ - public boolean setRowFocus(VScrollTableRow row) { - - if (!isSelectable()) { - return false; - } - - // Remove previous selection - if (focusedRow != null && focusedRow != row) { - focusedRow.removeStyleName(getStylePrimaryName() + "-focus"); - } - - if (row != null) { - // Apply focus style to new selection - row.addStyleName(getStylePrimaryName() + "-focus"); - - /* - * Trying to set focus on already focused row - */ - if (row == focusedRow) { - return false; - } - - // Set new focused row - focusedRow = row; - - if (hasFocus) { - ensureRowIsVisible(row); - } - - return true; - } - - return false; - } - - /** - * Ensures that the row is visible - * - * @param row - * The row to ensure is visible - */ - private void ensureRowIsVisible(VScrollTableRow row) { - if (BrowserInfo.get().isTouchDevice()) { - // Skip due to android devices that have broken scrolltop will may - // get odd scrolling here. - return; - } - /* - * FIXME The next line doesn't always do what expected, because if the - * row is not in the DOM it won't scroll to it. - */ - WidgetUtil.scrollIntoViewVertically(row.getElement()); - } - - /** - * Handles the keyboard events handled by the table - * - * @param event - * The keyboard event received - * @return true iff the navigation event was handled - */ - protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { - if (keycode == KeyCodes.KEY_TAB || keycode == KeyCodes.KEY_SHIFT) { - // Do not handle tab key - return false; - } - - // Down navigation - if (!isSelectable() && keycode == getNavigationDownKey()) { - scrollBodyPanel.setScrollPosition(scrollBodyPanel - .getScrollPosition() + scrollingVelocity); - return true; - } else if (keycode == getNavigationDownKey()) { - if (isMultiSelectModeAny() && moveFocusDown()) { - selectFocusedRow(ctrl, shift); - - } else if (isSingleSelectMode() && !shift && moveFocusDown()) { - selectFocusedRow(ctrl, shift); - } - return true; - } - - // Up navigation - if (!isSelectable() && keycode == getNavigationUpKey()) { - scrollBodyPanel.setScrollPosition(scrollBodyPanel - .getScrollPosition() - scrollingVelocity); - return true; - } else if (keycode == getNavigationUpKey()) { - if (isMultiSelectModeAny() && moveFocusUp()) { - selectFocusedRow(ctrl, shift); - } else if (isSingleSelectMode() && !shift && moveFocusUp()) { - selectFocusedRow(ctrl, shift); - } - return true; - } - - if (keycode == getNavigationLeftKey()) { - // Left navigation - scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel - .getHorizontalScrollPosition() - scrollingVelocity); - return true; - - } else if (keycode == getNavigationRightKey()) { - // Right navigation - scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel - .getHorizontalScrollPosition() + scrollingVelocity); - return true; - } - - // Select navigation - if (isSelectable() && keycode == getNavigationSelectKey()) { - if (isSingleSelectMode()) { - boolean wasSelected = focusedRow.isSelected(); - deselectAll(); - if (!wasSelected || !nullSelectionAllowed) { - focusedRow.toggleSelection(); - } - } else { - focusedRow.toggleSelection(); - removeRowFromUnsentSelectionRanges(focusedRow); - } - - sendSelectedRows(); - return true; - } - - // Page Down navigation - if (keycode == getNavigationPageDownKey()) { - if (isSelectable()) { - /* - * If selectable we plagiate MSW behaviour: first scroll to the - * end of current view. If at the end, scroll down one page - * length and keep the selected row in the bottom part of - * visible area. - */ - if (!isFocusAtTheEndOfTable()) { - VScrollTableRow lastVisibleRowInViewPort = scrollBody - .getRowByRowIndex(firstRowInViewPort - + getFullyVisibleRowCount() - 1); - if (lastVisibleRowInViewPort != null - && lastVisibleRowInViewPort != focusedRow) { - // focused row is not at the end of the table, move - // focus and select the last visible row - setRowFocus(lastVisibleRowInViewPort); - selectFocusedRow(ctrl, shift); - updateFirstVisibleAndSendSelectedRows(); - } else { - int indexOfToBeFocused = focusedRow.getIndex() - + getFullyVisibleRowCount(); - if (indexOfToBeFocused >= totalRows) { - indexOfToBeFocused = totalRows - 1; - } - VScrollTableRow toBeFocusedRow = scrollBody - .getRowByRowIndex(indexOfToBeFocused); - - if (toBeFocusedRow != null) { - /* - * if the next focused row is rendered - */ - setRowFocus(toBeFocusedRow); - selectFocusedRow(ctrl, shift); - // TODO needs scrollintoview ? - updateFirstVisibleAndSendSelectedRows(); - } else { - // scroll down by pixels and return, to wait for - // new rows, then select the last item in the - // viewport - selectLastItemInNextRender = true; - multiselectPending = shift; - scrollByPagelength(1); - } - } - } - } else { - /* No selections, go page down by scrolling */ - scrollByPagelength(1); - } - return true; - } - - // Page Up navigation - if (keycode == getNavigationPageUpKey()) { - if (isSelectable()) { - /* - * If selectable we plagiate MSW behaviour: first scroll to the - * end of current view. If at the end, scroll down one page - * length and keep the selected row in the bottom part of - * visible area. - */ - if (!isFocusAtTheBeginningOfTable()) { - VScrollTableRow firstVisibleRowInViewPort = scrollBody - .getRowByRowIndex(firstRowInViewPort); - if (firstVisibleRowInViewPort != null - && firstVisibleRowInViewPort != focusedRow) { - // focus is not at the beginning of the table, move - // focus and select the first visible row - setRowFocus(firstVisibleRowInViewPort); - selectFocusedRow(ctrl, shift); - updateFirstVisibleAndSendSelectedRows(); - } else { - int indexOfToBeFocused = focusedRow.getIndex() - - getFullyVisibleRowCount(); - if (indexOfToBeFocused < 0) { - indexOfToBeFocused = 0; - } - VScrollTableRow toBeFocusedRow = scrollBody - .getRowByRowIndex(indexOfToBeFocused); - - if (toBeFocusedRow != null) { // if the next focused row - // is rendered - setRowFocus(toBeFocusedRow); - selectFocusedRow(ctrl, shift); - // TODO needs scrollintoview ? - updateFirstVisibleAndSendSelectedRows(); - } else { - // unless waiting for the next rowset already - // scroll down by pixels and return, to wait for - // new rows, then select the last item in the - // viewport - selectFirstItemInNextRender = true; - multiselectPending = shift; - scrollByPagelength(-1); - } - } - } - } else { - /* No selections, go page up by scrolling */ - scrollByPagelength(-1); - } - - return true; - } - - // Goto start navigation - if (keycode == getNavigationStartKey()) { - scrollBodyPanel.setScrollPosition(0); - if (isSelectable()) { - if (focusedRow != null && focusedRow.getIndex() == 0) { - return false; - } else { - VScrollTableRow rowByRowIndex = (VScrollTableRow) scrollBody - .iterator().next(); - if (rowByRowIndex.getIndex() == 0) { - setRowFocus(rowByRowIndex); - selectFocusedRow(ctrl, shift); - updateFirstVisibleAndSendSelectedRows(); - } else { - // first row of table will come in next row fetch - if (ctrl) { - focusFirstItemInNextRender = true; - } else { - selectFirstItemInNextRender = true; - multiselectPending = shift; - } - } - } - } - return true; - } - - // Goto end navigation - if (keycode == getNavigationEndKey()) { - scrollBodyPanel.setScrollPosition(scrollBody.getOffsetHeight()); - if (isSelectable()) { - final int lastRendered = scrollBody.getLastRendered(); - if (lastRendered + 1 == totalRows) { - VScrollTableRow rowByRowIndex = scrollBody - .getRowByRowIndex(lastRendered); - if (focusedRow != rowByRowIndex) { - setRowFocus(rowByRowIndex); - selectFocusedRow(ctrl, shift); - updateFirstVisibleAndSendSelectedRows(); - } - } else { - if (ctrl) { - focusLastItemInNextRender = true; - } else { - selectLastItemInNextRender = true; - multiselectPending = shift; - } - } - } - return true; - } - - return false; - } - - private boolean isFocusAtTheBeginningOfTable() { - return focusedRow.getIndex() == 0; - } - - private boolean isFocusAtTheEndOfTable() { - return focusedRow.getIndex() + 1 >= totalRows; - } - - private int getFullyVisibleRowCount() { - return (int) (scrollBodyPanel.getOffsetHeight() / scrollBody - .getRowHeight()); - } - - private void scrollByPagelength(int i) { - int pixels = i * scrollBodyPanel.getOffsetHeight(); - int newPixels = scrollBodyPanel.getScrollPosition() + pixels; - if (newPixels < 0) { - newPixels = 0; - } // else if too high, NOP (all know browsers accept illegally big - // values here) - scrollBodyPanel.setScrollPosition(newPixels); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event - * .dom.client.FocusEvent) - */ - - @Override - public void onFocus(FocusEvent event) { - if (isFocusable()) { - hasFocus = true; - - // Focus a row if no row is in focus - if (focusedRow == null) { - focusRowFromBody(); - } else { - setRowFocus(focusedRow); - } - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event - * .dom.client.BlurEvent) - */ - - @Override - public void onBlur(BlurEvent event) { - hasFocus = false; - navKeyDown = false; - - if (BrowserInfo.get().isIE()) { - /* - * IE sometimes moves focus to a clicked table cell... (#7965) - * ...and sometimes it sends blur events even though the focus - * handler is still active. (#10464) - */ - Element focusedElement = WidgetUtil.getFocusedElement(); - if (Util.getConnectorForElement(client, getParent(), focusedElement) == this - && focusedElement != null - && focusedElement != scrollBodyPanel.getFocusElement()) { - /* - * Steal focus back to the focus handler if it was moved to some - * other part of the table. Avoid stealing focus in other cases. - */ - focus(); - return; - } - } - - if (isFocusable()) { - // Unfocus any row - setRowFocus(null); - } - } - - /** - * Removes a key from a range if the key is found in a selected range - * - * @param key - * The key to remove - */ - private void removeRowFromUnsentSelectionRanges(VScrollTableRow row) { - Collection newRanges = null; - for (Iterator iterator = selectedRowRanges.iterator(); iterator - .hasNext();) { - SelectionRange range = iterator.next(); - if (range.inRange(row)) { - // Split the range if given row is in range - Collection splitranges = range.split(row); - if (newRanges == null) { - newRanges = new ArrayList(); - } - newRanges.addAll(splitranges); - iterator.remove(); - } - } - if (newRanges != null) { - selectedRowRanges.addAll(newRanges); - } - } - - /** - * Can the Table be focused? - * - * @return True if the table can be focused, else false - */ - public boolean isFocusable() { - if (scrollBody != null && enabled) { - return !(!hasHorizontalScrollbar() && !hasVerticalScrollbar() && !isSelectable()); - } - return false; - } - - private boolean hasHorizontalScrollbar() { - return scrollBody.getOffsetWidth() > scrollBodyPanel.getOffsetWidth(); - } - - private boolean hasVerticalScrollbar() { - return scrollBody.getOffsetHeight() > scrollBodyPanel.getOffsetHeight(); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.Focusable#focus() - */ - - @Override - public void focus() { - if (isFocusable()) { - scrollBodyPanel.focus(); - } - } - - /** - * Sets the proper tabIndex for scrollBodyPanel (the focusable elemen in the - * component). - *

- * If the component has no explicit tabIndex a zero is given (default - * tabbing order based on dom hierarchy) or -1 if the component does not - * need to gain focus. The component needs no focus if it has no scrollabars - * (not scrollable) and not selectable. Note that in the future shortcut - * actions may need focus. - *

- * For internal use only. May be removed or replaced in the future. - */ - public void setProperTabIndex() { - int storedScrollTop = 0; - int storedScrollLeft = 0; - - if (BrowserInfo.get().getOperaVersion() >= 11) { - // Workaround for Opera scroll bug when changing tabIndex (#6222) - storedScrollTop = scrollBodyPanel.getScrollPosition(); - storedScrollLeft = scrollBodyPanel.getHorizontalScrollPosition(); - } - - if (tabIndex == 0 && !isFocusable()) { - scrollBodyPanel.setTabIndex(-1); - } else { - scrollBodyPanel.setTabIndex(tabIndex); - } - - if (BrowserInfo.get().getOperaVersion() >= 11) { - // Workaround for Opera scroll bug when changing tabIndex (#6222) - scrollBodyPanel.setScrollPosition(storedScrollTop); - scrollBodyPanel.setHorizontalScrollPosition(storedScrollLeft); - } - } - - public void startScrollingVelocityTimer() { - if (scrollingVelocityTimer == null) { - scrollingVelocityTimer = new Timer() { - - @Override - public void run() { - scrollingVelocity++; - } - }; - scrollingVelocityTimer.scheduleRepeating(100); - } - } - - public void cancelScrollingVelocityTimer() { - if (scrollingVelocityTimer != null) { - // Remove velocityTimer if it exists and the Table is disabled - scrollingVelocityTimer.cancel(); - scrollingVelocityTimer = null; - scrollingVelocity = 10; - } - } - - /** - * - * @param keyCode - * @return true if the given keyCode is used by the table for navigation - */ - private boolean isNavigationKey(int keyCode) { - return keyCode == getNavigationUpKey() - || keyCode == getNavigationLeftKey() - || keyCode == getNavigationRightKey() - || keyCode == getNavigationDownKey() - || keyCode == getNavigationPageUpKey() - || keyCode == getNavigationPageDownKey() - || keyCode == getNavigationEndKey() - || keyCode == getNavigationStartKey(); - } - - public void lazyRevertFocusToRow(final VScrollTableRow currentlyFocusedRow) { - Scheduler.get().scheduleFinally(new ScheduledCommand() { - @Override - public void execute() { - if (currentlyFocusedRow != null) { - setRowFocus(currentlyFocusedRow); - } else { - VConsole.log("no row?"); - focusRowFromBody(); - } - scrollBody.ensureFocus(); - } - }); - } - - @Override - public Action[] getActions() { - if (bodyActionKeys == null) { - return new Action[] {}; - } - final Action[] actions = new Action[bodyActionKeys.length]; - for (int i = 0; i < actions.length; i++) { - final String actionKey = bodyActionKeys[i]; - Action bodyAction = new TreeAction(this, null, actionKey); - bodyAction.setCaption(getActionCaption(actionKey)); - bodyAction.setIconUrl(getActionIcon(actionKey)); - actions[i] = bodyAction; - } - return actions; - } - - @Override - public ApplicationConnection getClient() { - return client; - } - - @Override - public String getPaintableId() { - return paintableId; - } - - /** - * Add this to the element mouse down event by using element.setPropertyJSO - * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again - * when the mouse is depressed in the mouse up event. - * - * @return Returns the JSO preventing text selection - */ - private static native JavaScriptObject getPreventTextSelectionIEHack() - /*-{ - return function(){ return false; }; - }-*/; - - public void triggerLazyColumnAdjustment(boolean now) { - lazyAdjustColumnWidths.cancel(); - if (now) { - lazyAdjustColumnWidths.run(); - } else { - lazyAdjustColumnWidths.schedule(LAZY_COLUMN_ADJUST_TIMEOUT); - } - } - - private boolean isDynamicWidth() { - ComponentConnector paintable = ConnectorMap.get(client).getConnector( - this); - return paintable.isUndefinedWidth(); - } - - private boolean isDynamicHeight() { - ComponentConnector paintable = ConnectorMap.get(client).getConnector( - this); - if (paintable == null) { - // This should be refactored. As isDynamicHeight can be called from - // a timer it is possible that the connector has been unregistered - // when this method is called, causing getConnector to return null. - return false; - } - return paintable.isUndefinedHeight(); - } - - private void debug(String msg) { - if (enableDebug) { - VConsole.error(msg); - } - } - - public Widget getWidgetForPaintable() { - return this; - } - - private static final String SUBPART_HEADER = "header"; - private static final String SUBPART_FOOTER = "footer"; - private static final String SUBPART_ROW = "row"; - private static final String SUBPART_COL = "col"; - /** - * Matches header[ix] - used for extracting the index of the targeted header - * cell - */ - private static final RegExp SUBPART_HEADER_REGEXP = RegExp - .compile(SUBPART_HEADER + "\\[(\\d+)\\]"); - /** - * Matches footer[ix] - used for extracting the index of the targeted footer - * cell - */ - private static final RegExp SUBPART_FOOTER_REGEXP = RegExp - .compile(SUBPART_FOOTER + "\\[(\\d+)\\]"); - /** Matches row[ix] - used for extracting the index of the targeted row */ - private static final RegExp SUBPART_ROW_REGEXP = RegExp.compile(SUBPART_ROW - + "\\[(\\d+)]"); - /** Matches col[ix] - used for extracting the index of the targeted column */ - private static final RegExp SUBPART_ROW_COL_REGEXP = RegExp - .compile(SUBPART_ROW + "\\[(\\d+)\\]/" + SUBPART_COL - + "\\[(\\d+)\\]"); - - @Override - public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - if (SUBPART_ROW_COL_REGEXP.test(subPart)) { - MatchResult result = SUBPART_ROW_COL_REGEXP.exec(subPart); - int rowIx = Integer.valueOf(result.getGroup(1)); - int colIx = Integer.valueOf(result.getGroup(2)); - VScrollTableRow row = scrollBody.getRowByRowIndex(rowIx); - if (row != null) { - Element rowElement = row.getElement(); - if (colIx < rowElement.getChildCount()) { - return rowElement.getChild(colIx).getFirstChild().cast(); - } - } - - } else if (SUBPART_ROW_REGEXP.test(subPart)) { - MatchResult result = SUBPART_ROW_REGEXP.exec(subPart); - int rowIx = Integer.valueOf(result.getGroup(1)); - VScrollTableRow row = scrollBody.getRowByRowIndex(rowIx); - if (row != null) { - return row.getElement(); - } - - } else if (SUBPART_HEADER_REGEXP.test(subPart)) { - MatchResult result = SUBPART_HEADER_REGEXP.exec(subPart); - int headerIx = Integer.valueOf(result.getGroup(1)); - HeaderCell headerCell = tHead.getHeaderCell(headerIx); - if (headerCell != null) { - return headerCell.getElement(); - } - - } else if (SUBPART_FOOTER_REGEXP.test(subPart)) { - MatchResult result = SUBPART_FOOTER_REGEXP.exec(subPart); - int footerIx = Integer.valueOf(result.getGroup(1)); - FooterCell footerCell = tFoot.getFooterCell(footerIx); - if (footerCell != null) { - return footerCell.getElement(); - } - } - // Nothing found. - return null; - } - - @Override - public String getSubPartName(com.google.gwt.user.client.Element subElement) { - Widget widget = WidgetUtil.findWidget(subElement, null); - if (widget instanceof HeaderCell) { - return SUBPART_HEADER + "[" + tHead.visibleCells.indexOf(widget) - + "]"; - } else if (widget instanceof FooterCell) { - return SUBPART_FOOTER + "[" + tFoot.visibleCells.indexOf(widget) - + "]"; - } else if (widget instanceof VScrollTableRow) { - // a cell in a row - VScrollTableRow row = (VScrollTableRow) widget; - int rowIx = scrollBody.indexOf(row); - if (rowIx >= 0) { - int colIx = -1; - for (int ix = 0; ix < row.getElement().getChildCount(); ix++) { - if (row.getElement().getChild(ix).isOrHasChild(subElement)) { - colIx = ix; - break; - } - } - if (colIx >= 0) { - return SUBPART_ROW + "[" + rowIx + "]/" + SUBPART_COL + "[" - + colIx + "]"; - } - return SUBPART_ROW + "[" + rowIx + "]"; - } - } - // Nothing found. - return null; - } - - /** - * @since 7.2.6 - */ - public void onUnregister() { - if (addCloseHandler != null) { - addCloseHandler.removeHandler(); - } - } - - /* - * Return true if component need to perform some work and false otherwise. - */ - @Override - public boolean isWorkPending() { - return lazyAdjustColumnWidths.isRunning(); - } - - private static Logger getLogger() { - return Logger.getLogger(VScrollTable.class.getName()); - } - - public ChildMeasurementHint getChildMeasurementHint() { - return childMeasurementHint; - } - - public void setChildMeasurementHint(ChildMeasurementHint hint) { - childMeasurementHint = hint; - } -} diff --git a/client/src/com/vaadin/client/ui/VSlider.java b/client/src/com/vaadin/client/ui/VSlider.java deleted file mode 100644 index 952c387539..0000000000 --- a/client/src/com/vaadin/client/ui/VSlider.java +++ /dev/null @@ -1,675 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -// -package com.vaadin.client.ui; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.HasValue; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.WidgetUtil; -import com.vaadin.shared.ui.slider.SliderOrientation; - -public class VSlider extends SimpleFocusablePanel implements Field, - HasValue, SubPartAware { - - public static final String CLASSNAME = "v-slider"; - - /** - * Minimum size (width or height, depending on orientation) of the slider - * base. - */ - private static final int MIN_SIZE = 50; - - protected ApplicationConnection client; - - protected String id; - - protected boolean immediate; - protected boolean disabled; - protected boolean readonly; - - private int acceleration = 1; - protected double min; - protected double max; - protected int resolution; - protected Double value; - protected SliderOrientation orientation = SliderOrientation.HORIZONTAL; - - private final HTML feedback = new HTML("", false); - private final VOverlay feedbackPopup = new VOverlay(true, false, true) { - { - setOwner(VSlider.this); - } - - @Override - public void show() { - super.show(); - updateFeedbackPosition(); - } - }; - - /* DOM element for slider's base */ - private final Element base; - private final int BASE_BORDER_WIDTH = 1; - - /* DOM element for slider's handle */ - private final Element handle; - - /* DOM element for decrement arrow */ - private final Element smaller; - - /* DOM element for increment arrow */ - private final Element bigger; - - /* Temporary dragging/animation variables */ - private boolean dragging = false; - - private VLazyExecutor delayedValueUpdater = new VLazyExecutor(100, - new ScheduledCommand() { - - @Override - public void execute() { - fireValueChanged(); - acceleration = 1; - } - }); - - public VSlider() { - super(); - - base = DOM.createDiv(); - handle = DOM.createDiv(); - smaller = DOM.createDiv(); - bigger = DOM.createDiv(); - - setStyleName(CLASSNAME); - - getElement().appendChild(bigger); - getElement().appendChild(smaller); - getElement().appendChild(base); - base.appendChild(handle); - - // Hide initially - smaller.getStyle().setDisplay(Display.NONE); - bigger.getStyle().setDisplay(Display.NONE); - - sinkEvents(Event.MOUSEEVENTS | Event.ONMOUSEWHEEL | Event.KEYEVENTS - | Event.FOCUSEVENTS | Event.TOUCHEVENTS); - - feedbackPopup.setWidget(feedback); - } - - @Override - public void setStyleName(String style) { - updateStyleNames(style, false); - } - - @Override - public void setStylePrimaryName(String style) { - updateStyleNames(style, true); - } - - protected void updateStyleNames(String styleName, boolean isPrimaryStyleName) { - - feedbackPopup.removeStyleName(getStylePrimaryName() + "-feedback"); - removeStyleName(getStylePrimaryName() + "-vertical"); - - if (isPrimaryStyleName) { - super.setStylePrimaryName(styleName); - } else { - super.setStyleName(styleName); - } - - feedbackPopup.addStyleName(getStylePrimaryName() + "-feedback"); - base.setClassName(getStylePrimaryName() + "-base"); - handle.setClassName(getStylePrimaryName() + "-handle"); - smaller.setClassName(getStylePrimaryName() + "-smaller"); - bigger.setClassName(getStylePrimaryName() + "-bigger"); - - if (isVertical()) { - addStyleName(getStylePrimaryName() + "-vertical"); - } - } - - public void setFeedbackValue(double value) { - feedback.setText(String.valueOf(value)); - } - - private void updateFeedbackPosition() { - if (isVertical()) { - feedbackPopup.setPopupPosition( - handle.getAbsoluteLeft() + handle.getOffsetWidth(), - handle.getAbsoluteTop() + handle.getOffsetHeight() / 2 - - feedbackPopup.getOffsetHeight() / 2); - } else { - feedbackPopup.setPopupPosition( - handle.getAbsoluteLeft() + handle.getOffsetWidth() / 2 - - feedbackPopup.getOffsetWidth() / 2, - handle.getAbsoluteTop() - feedbackPopup.getOffsetHeight()); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void buildBase() { - final String styleAttribute = isVertical() ? "height" : "width"; - final String oppositeStyleAttribute = isVertical() ? "width" : "height"; - final String domProperty = isVertical() ? "offsetHeight" - : "offsetWidth"; - - // clear unnecessary opposite style attribute - base.getStyle().clearProperty(oppositeStyleAttribute); - - /* - * To resolve defect #13681 we should not return from method buildBase() - * if slider has no parentElement, because such operations as - * buildHandle() and setValues(), which are needed for Slider, are - * called at the end of method buildBase(). And these methods will not - * be called if there is no parentElement. So, instead of returning from - * method buildBase() if there is no parentElement "if condition" is - * applied to call code for parentElement only in case it exists. - */ - if (getElement().hasParentElement()) { - final Element p = getElement(); - if (p.getPropertyInt(domProperty) > MIN_SIZE) { - if (isVertical()) { - setHeight(); - } else { - base.getStyle().clearProperty(styleAttribute); - } - } else { - // Set minimum size and adjust after all components have - // (supposedly) been drawn completely. - base.getStyle().setPropertyPx(styleAttribute, MIN_SIZE); - Scheduler.get().scheduleDeferred(new Command() { - - @Override - public void execute() { - final Element p = getElement(); - if (p.getPropertyInt(domProperty) > (MIN_SIZE + 5) - || propertyNotNullOrEmpty(styleAttribute, p)) { - if (isVertical()) { - setHeight(); - } else { - base.getStyle().clearProperty(styleAttribute); - } - // Ensure correct position - setValue(value, false); - } - } - - // Style has non empty property - private boolean propertyNotNullOrEmpty( - final String styleAttribute, final Element p) { - return p.getStyle().getProperty(styleAttribute) != null - && !p.getStyle().getProperty(styleAttribute) - .isEmpty(); - } - }); - } - } - - if (!isVertical()) { - // Draw handle with a delay to allow base to gain maximum width - Scheduler.get().scheduleDeferred(new Command() { - @Override - public void execute() { - buildHandle(); - setValue(value, false); - } - }); - } else { - buildHandle(); - setValue(value, false); - } - - // TODO attach listeners for focusing and arrow keys - } - - void buildHandle() { - final String handleAttribute = isVertical() ? "marginTop" - : "marginLeft"; - final String oppositeHandleAttribute = isVertical() ? "marginLeft" - : "marginTop"; - - handle.getStyle().setProperty(handleAttribute, "0"); - - // clear unnecessary opposite handle attribute - handle.getStyle().clearProperty(oppositeHandleAttribute); - } - - @Override - public void onBrowserEvent(Event event) { - if (disabled || readonly) { - return; - } - final Element targ = DOM.eventGetTarget(event); - - if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) { - processMouseWheelEvent(event); - } else if (dragging || targ == handle) { - processHandleEvent(event); - } else if (targ == smaller) { - decreaseValue(true); - } else if (targ == bigger) { - increaseValue(true); - } else if (DOM.eventGetType(event) == Event.MOUSEEVENTS) { - processBaseEvent(event); - } else if ((BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYPRESS) - || (!BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYDOWN)) { - - if (handleNavigation(event.getKeyCode(), event.getCtrlKey(), - event.getShiftKey())) { - - feedbackPopup.show(); - - delayedValueUpdater.trigger(); - - DOM.eventPreventDefault(event); - DOM.eventCancelBubble(event, true); - } - } else if (targ.equals(getElement()) - && DOM.eventGetType(event) == Event.ONFOCUS) { - feedbackPopup.show(); - } else if (targ.equals(getElement()) - && DOM.eventGetType(event) == Event.ONBLUR) { - feedbackPopup.hide(); - } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) { - feedbackPopup.show(); - } - if (WidgetUtil.isTouchEvent(event)) { - event.preventDefault(); // avoid simulated events - event.stopPropagation(); - } - } - - private void processMouseWheelEvent(final Event event) { - final int dir = DOM.eventGetMouseWheelVelocityY(event); - - if (dir < 0) { - increaseValue(false); - } else { - decreaseValue(false); - } - - delayedValueUpdater.trigger(); - - DOM.eventPreventDefault(event); - DOM.eventCancelBubble(event, true); - } - - private void processHandleEvent(Event event) { - switch (DOM.eventGetType(event)) { - case Event.ONMOUSEDOWN: - case Event.ONTOUCHSTART: - if (!disabled && !readonly) { - focus(); - feedbackPopup.show(); - dragging = true; - handle.setClassName(getStylePrimaryName() + "-handle"); - handle.addClassName(getStylePrimaryName() + "-handle-active"); - - DOM.setCapture(getElement()); - DOM.eventPreventDefault(event); // prevent selecting text - DOM.eventCancelBubble(event, true); - event.stopPropagation(); - } - break; - case Event.ONMOUSEMOVE: - case Event.ONTOUCHMOVE: - if (dragging) { - setValueByEvent(event, false); - updateFeedbackPosition(); - event.stopPropagation(); - } - break; - case Event.ONTOUCHEND: - feedbackPopup.hide(); - case Event.ONMOUSEUP: - // feedbackPopup.hide(); - dragging = false; - handle.setClassName(getStylePrimaryName() + "-handle"); - DOM.releaseCapture(getElement()); - setValueByEvent(event, true); - event.stopPropagation(); - break; - default: - break; - } - } - - private void processBaseEvent(Event event) { - if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) { - if (!disabled && !readonly && !dragging) { - setValueByEvent(event, true); - DOM.eventCancelBubble(event, true); - } - } - } - - private void decreaseValue(boolean updateToServer) { - setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)), - updateToServer); - } - - private void increaseValue(boolean updateToServer) { - setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)), - updateToServer); - } - - private void setValueByEvent(Event event, boolean updateToServer) { - double v = min; // Fallback to min - - final int coord = getEventPosition(event); - - final int handleSize, baseSize, baseOffset; - if (isVertical()) { - handleSize = handle.getOffsetHeight(); - baseSize = base.getOffsetHeight(); - baseOffset = base.getAbsoluteTop() - Window.getScrollTop() - - handleSize / 2; - } else { - handleSize = handle.getOffsetWidth(); - baseSize = base.getOffsetWidth(); - baseOffset = base.getAbsoluteLeft() - Window.getScrollLeft() - + handleSize / 2; - } - - if (isVertical()) { - v = ((baseSize - (coord - baseOffset)) / (double) (baseSize - handleSize)) - * (max - min) + min; - } else { - v = ((coord - baseOffset) / (double) (baseSize - handleSize)) - * (max - min) + min; - } - - if (v < min) { - v = min; - } else if (v > max) { - v = max; - } - - setValue(v, updateToServer); - } - - /** - * TODO consider extracting touches support to an impl class specific for - * webkit (only browser that really supports touches). - * - * @param event - * @return - */ - protected int getEventPosition(Event event) { - if (isVertical()) { - return WidgetUtil.getTouchOrMouseClientY(event); - } else { - return WidgetUtil.getTouchOrMouseClientX(event); - } - } - - public void iLayout() { - if (isVertical()) { - setHeight(); - } - // Update handle position - setValue(value, false); - } - - private void setHeight() { - // Calculate decoration size - base.getStyle().setHeight(0, Unit.PX); - base.getStyle().setOverflow(Overflow.HIDDEN); - int h = getElement().getOffsetHeight(); - if (h < MIN_SIZE) { - h = MIN_SIZE; - } - base.getStyle().setHeight(h, Unit.PX); - base.getStyle().clearOverflow(); - } - - private void fireValueChanged() { - ValueChangeEvent.fire(VSlider.this, value); - } - - /** - * Handles the keyboard events handled by the Slider - * - * @param event - * The keyboard event received - * @return true iff the navigation event was handled - */ - public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { - - // No support for ctrl moving - if (ctrl) { - return false; - } - - if ((keycode == getNavigationUpKey() && isVertical()) - || (keycode == getNavigationRightKey() && !isVertical())) { - if (shift) { - for (int a = 0; a < acceleration; a++) { - increaseValue(false); - } - acceleration++; - } else { - increaseValue(false); - } - return true; - } else if (keycode == getNavigationDownKey() && isVertical() - || (keycode == getNavigationLeftKey() && !isVertical())) { - if (shift) { - for (int a = 0; a < acceleration; a++) { - decreaseValue(false); - } - acceleration++; - } else { - decreaseValue(false); - } - return true; - } - - return false; - } - - /** - * Get the key that increases the vertical slider. By default it is the up - * arrow key but by overriding this you can change the key to whatever you - * want. - * - * @return The keycode of the key - */ - protected int getNavigationUpKey() { - return KeyCodes.KEY_UP; - } - - /** - * Get the key that decreases the vertical slider. By default it is the down - * arrow key but by overriding this you can change the key to whatever you - * want. - * - * @return The keycode of the key - */ - protected int getNavigationDownKey() { - return KeyCodes.KEY_DOWN; - } - - /** - * Get the key that decreases the horizontal slider. By default it is the - * left arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationLeftKey() { - return KeyCodes.KEY_LEFT; - } - - /** - * Get the key that increases the horizontal slider. By default it is the - * right arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationRightKey() { - return KeyCodes.KEY_RIGHT; - } - - public void setConnection(ApplicationConnection client) { - this.client = client; - } - - public void setId(String id) { - this.id = id; - } - - public void setImmediate(boolean immediate) { - this.immediate = immediate; - } - - public void setDisabled(boolean disabled) { - this.disabled = disabled; - } - - public void setReadOnly(boolean readonly) { - this.readonly = readonly; - } - - private boolean isVertical() { - return orientation == SliderOrientation.VERTICAL; - } - - public void setOrientation(SliderOrientation orientation) { - if (this.orientation != orientation) { - this.orientation = orientation; - updateStyleNames(getStylePrimaryName(), true); - } - } - - public void setMinValue(double value) { - min = value; - } - - public void setMaxValue(double value) { - max = value; - } - - public void setResolution(int resolution) { - this.resolution = resolution; - } - - @Override - public HandlerRegistration addValueChangeHandler( - ValueChangeHandler handler) { - return addHandler(handler, ValueChangeEvent.getType()); - } - - @Override - public Double getValue() { - return value; - } - - @Override - public void setValue(Double value) { - if (value < min) { - value = min; - } else if (value > max) { - value = max; - } - - // Update handle position - final String styleAttribute = isVertical() ? "marginTop" : "marginLeft"; - final String domProperty = isVertical() ? "offsetHeight" - : "offsetWidth"; - final int handleSize = handle.getPropertyInt(domProperty); - final int baseSize = base.getPropertyInt(domProperty) - - (2 * BASE_BORDER_WIDTH); - - final int range = baseSize - handleSize; - double v = value.doubleValue(); - - // Round value to resolution - if (resolution > 0) { - v = Math.round(v * Math.pow(10, resolution)); - v = v / Math.pow(10, resolution); - } else { - v = Math.round(v); - } - final double valueRange = max - min; - double p = 0; - if (valueRange > 0) { - p = range * ((v - min) / valueRange); - } - if (p < 0) { - p = 0; - } - if (isVertical()) { - p = range - p; - } - final double pos = p; - - handle.getStyle().setPropertyPx(styleAttribute, (int) Math.round(pos)); - - // Update value - this.value = new Double(v); - setFeedbackValue(v); - } - - @Override - public void setValue(Double value, boolean fireEvents) { - if (value == null) { - return; - } - - setValue(value); - - if (fireEvents) { - fireValueChanged(); - } - } - - @Override - public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - if (subPart.equals("popup")) { - feedbackPopup.show(); - return feedbackPopup.getElement(); - } - return null; - } - - @Override - public String getSubPartName(com.google.gwt.user.client.Element subElement) { - if (feedbackPopup.getElement().isOrHasChild(subElement)) { - return "popup"; - } - return null; - } -} diff --git a/client/src/com/vaadin/client/ui/VSplitPanelHorizontal.java b/client/src/com/vaadin/client/ui/VSplitPanelHorizontal.java deleted file mode 100644 index 1a3e699b20..0000000000 --- a/client/src/com/vaadin/client/ui/VSplitPanelHorizontal.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.shared.ui.Orientation; - -public class VSplitPanelHorizontal extends VAbstractSplitPanel { - - public VSplitPanelHorizontal() { - super(Orientation.HORIZONTAL); - } - - @Override - protected void startResize() { - if (getFirstWidget() != null && isWidgetFullWidth(getFirstWidget())) { - getFirstContainer().getStyle().setOverflow(Overflow.HIDDEN); - } - - if (getSecondWidget() != null && isWidgetFullWidth(getSecondWidget())) { - getSecondContainer().getStyle().setOverflow(Overflow.HIDDEN); - } - } - - @Override - protected void stopResize() { - getFirstContainer().getStyle().clearOverflow(); - getSecondContainer().getStyle().clearOverflow(); - } - - private boolean isWidgetFullWidth(Widget w) { - return w.getElement().getStyle().getWidth().equals("100%"); - } -} diff --git a/client/src/com/vaadin/client/ui/VSplitPanelVertical.java b/client/src/com/vaadin/client/ui/VSplitPanelVertical.java deleted file mode 100644 index 7baed03ca3..0000000000 --- a/client/src/com/vaadin/client/ui/VSplitPanelVertical.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.shared.ui.Orientation; - -public class VSplitPanelVertical extends VAbstractSplitPanel { - - public VSplitPanelVertical() { - super(Orientation.VERTICAL); - } - - @Override - protected void startResize() { - if (getFirstWidget() != null && isWidgetFullHeight(getFirstWidget())) { - getFirstContainer().getStyle().setOverflow(Overflow.HIDDEN); - } - - if (getSecondWidget() != null && isWidgetFullHeight(getSecondWidget())) { - getSecondContainer().getStyle().setOverflow(Overflow.HIDDEN); - } - } - - @Override - protected void stopResize() { - getFirstContainer().getStyle().clearOverflow(); - getSecondContainer().getStyle().clearOverflow(); - } - - private boolean isWidgetFullHeight(Widget w) { - return w.getElement().getStyle().getHeight().equals("100%"); - } -} diff --git a/client/src/com/vaadin/client/ui/VTabsheet.java b/client/src/com/vaadin/client/ui/VTabsheet.java deleted file mode 100644 index e196870348..0000000000 --- a/client/src/com/vaadin/client/ui/VTabsheet.java +++ /dev/null @@ -1,1998 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.Iterator; -import java.util.List; - -import com.google.gwt.aria.client.Id; -import com.google.gwt.aria.client.LiveValue; -import com.google.gwt.aria.client.Roles; -import com.google.gwt.aria.client.SelectedValue; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.Style.Visibility; -import com.google.gwt.dom.client.TableCellElement; -import com.google.gwt.dom.client.TableElement; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.HasBlurHandlers; -import com.google.gwt.event.dom.client.HasFocusHandlers; -import com.google.gwt.event.dom.client.HasKeyDownHandlers; -import com.google.gwt.event.dom.client.HasMouseDownHandlers; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.MouseDownEvent; -import com.google.gwt.event.dom.client.MouseDownHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.regexp.shared.MatchResult; -import com.google.gwt.regexp.shared.RegExp; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.ComplexPanel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.user.client.ui.impl.FocusImpl; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ComputedStyle; -import com.vaadin.client.Focusable; -import com.vaadin.client.TooltipInfo; -import com.vaadin.client.VCaption; -import com.vaadin.client.VTooltip; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.ui.aria.AriaHelper; -import com.vaadin.shared.AbstractComponentState; -import com.vaadin.shared.ComponentConstants; -import com.vaadin.shared.EventId; -import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc; -import com.vaadin.shared.ui.ComponentStateUtil; -import com.vaadin.shared.ui.tabsheet.TabState; -import com.vaadin.shared.ui.tabsheet.TabsheetServerRpc; -import com.vaadin.shared.ui.tabsheet.TabsheetState; - -public class VTabsheet extends VTabsheetBase implements Focusable, SubPartAware { - - private static final String PREV_SCROLLER_DISABLED_CLASSNAME = "Prev-disabled"; - - private static class VCloseEvent { - private Tab tab; - - VCloseEvent(Tab tab) { - this.tab = tab; - } - - public Tab getTab() { - return tab; - } - - } - - private interface VCloseHandler { - public void onClose(VCloseEvent event); - } - - /** - * Representation of a single "tab" shown in the TabBar - * - */ - public static class Tab extends SimplePanel implements HasFocusHandlers, - HasBlurHandlers, HasMouseDownHandlers, HasKeyDownHandlers { - private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell"; - private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME - + "-first"; - private static final String TD_SELECTED_CLASSNAME = TD_CLASSNAME - + "-selected"; - private static final String TD_SELECTED_FIRST_CLASSNAME = TD_SELECTED_CLASSNAME - + "-first"; - private static final String TD_FOCUS_CLASSNAME = TD_CLASSNAME - + "-focus"; - private static final String TD_FOCUS_FIRST_CLASSNAME = TD_FOCUS_CLASSNAME - + "-first"; - private static final String TD_DISABLED_CLASSNAME = TD_CLASSNAME - + "-disabled"; - - private static final String DIV_CLASSNAME = CLASSNAME + "-tabitem"; - private static final String DIV_SELECTED_CLASSNAME = DIV_CLASSNAME - + "-selected"; - private static final String DIV_FOCUS_CLASSNAME = DIV_CLASSNAME - + "-focus"; - - private TabCaption tabCaption; - Element td = getElement(); - private VCloseHandler closeHandler; - - private boolean enabledOnServer = true; - private Element div; - private TabBar tabBar; - private boolean hiddenOnServer = false; - - private String styleName; - - private String id; - - private Tab(TabBar tabBar) { - super(DOM.createTD()); - this.tabBar = tabBar; - setStyleName(td, TD_CLASSNAME); - - Roles.getTabRole().set(getElement()); - Roles.getTabRole().setAriaSelectedState(getElement(), - SelectedValue.FALSE); - - div = DOM.createDiv(); - setTabulatorIndex(-1); - setStyleName(div, DIV_CLASSNAME); - - DOM.appendChild(td, div); - - tabCaption = new TabCaption(this); - add(tabCaption); - - Roles.getTabRole().setAriaLabelledbyProperty(getElement(), - Id.of(tabCaption.getElement())); - } - - public boolean isHiddenOnServer() { - return hiddenOnServer; - } - - public void setHiddenOnServer(boolean hiddenOnServer) { - this.hiddenOnServer = hiddenOnServer; - Roles.getTabRole().setAriaHiddenState(getElement(), hiddenOnServer); - } - - @Override - protected com.google.gwt.user.client.Element getContainerElement() { - // Attach caption element to div, not td - return DOM.asOld(div); - } - - public boolean isEnabledOnServer() { - return enabledOnServer; - } - - public void setEnabledOnServer(boolean enabled) { - enabledOnServer = enabled; - Roles.getTabRole().setAriaDisabledState(getElement(), !enabled); - - setStyleName(td, TD_DISABLED_CLASSNAME, !enabled); - if (!enabled) { - focusImpl.setTabIndex(td, -1); - } - } - - public void addClickHandler(ClickHandler handler) { - tabCaption.addClickHandler(handler); - } - - public void setCloseHandler(VCloseHandler closeHandler) { - this.closeHandler = closeHandler; - } - - /** - * Toggles the style names for the Tab - * - * @param selected - * true if the Tab is selected - * @param first - * true if the Tab is the first visible Tab - */ - public void setStyleNames(boolean selected, boolean first) { - setStyleNames(selected, first, false); - } - - public void setStyleNames(boolean selected, boolean first, - boolean keyboardFocus) { - setStyleName(td, TD_FIRST_CLASSNAME, first); - setStyleName(td, TD_SELECTED_CLASSNAME, selected); - setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first); - setStyleName(div, DIV_SELECTED_CLASSNAME, selected); - setStyleName(td, TD_FOCUS_CLASSNAME, keyboardFocus); - setStyleName(td, TD_FOCUS_FIRST_CLASSNAME, keyboardFocus && first); - setStyleName(div, DIV_FOCUS_CLASSNAME, keyboardFocus); - } - - public void setTabulatorIndex(int tabIndex) { - getElement().setTabIndex(tabIndex); - } - - public boolean isClosable() { - return tabCaption.isClosable(); - } - - public void onClose() { - closeHandler.onClose(new VCloseEvent(this)); - } - - public VTabsheet getTabsheet() { - return tabBar.getTabsheet(); - } - - private void updateFromState(TabState tabState) { - tabCaption.setCaptionAsHtml(getTabsheet().isTabCaptionsAsHtml()); - tabCaption.update(tabState); - // Apply the styleName set for the tab - String newStyleName = tabState.styleName; - // Find the nth td element - if (newStyleName != null && !newStyleName.isEmpty()) { - if (!newStyleName.equals(styleName)) { - // If we have a new style name - if (styleName != null && !styleName.isEmpty()) { - // Remove old style name if present - td.removeClassName(TD_CLASSNAME + "-" + styleName); - } - // Set new style name - td.addClassName(TD_CLASSNAME + "-" + newStyleName); - styleName = newStyleName; - } - } else if (styleName != null) { - // Remove the set stylename if no stylename is present in the - // uidl - td.removeClassName(TD_CLASSNAME + "-" + styleName); - styleName = null; - } - - String newId = tabState.id; - if (newId != null && !newId.isEmpty()) { - td.setId(newId); - id = newId; - } else if (id != null) { - td.removeAttribute("id"); - id = null; - } - } - - public void recalculateCaptionWidth() { - tabCaption.setWidth(tabCaption.getRequiredWidth() + "px"); - } - - @Override - public HandlerRegistration addFocusHandler(FocusHandler handler) { - return addDomHandler(handler, FocusEvent.getType()); - } - - @Override - public HandlerRegistration addBlurHandler(BlurHandler handler) { - return addDomHandler(handler, BlurEvent.getType()); - } - - @Override - public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) { - return addDomHandler(handler, MouseDownEvent.getType()); - } - - @Override - public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) { - return addDomHandler(handler, KeyDownEvent.getType()); - } - - public void focus() { - getTabsheet().scrollIntoView(this); - focusImpl.focus(td); - } - - public void blur() { - focusImpl.blur(td); - } - - public boolean hasTooltip() { - return tabCaption.getTooltipInfo() != null; - } - - public TooltipInfo getTooltipInfo() { - return tabCaption.getTooltipInfo(); - } - - public void setAssistiveDescription(String descriptionId) { - Roles.getTablistRole().setAriaDescribedbyProperty(getElement(), - Id.of(descriptionId)); - } - - public void removeAssistiveDescription() { - Roles.getTablistRole().removeAriaDescribedbyProperty(getElement()); - } - } - - public static class TabCaption extends VCaption { - - private boolean closable = false; - private Element closeButton; - private Tab tab; - - TabCaption(Tab tab) { - super(tab.getTabsheet().connector.getConnection()); - this.tab = tab; - - AriaHelper.ensureHasId(getElement()); - } - - private boolean update(TabState tabState) { - if (tabState.description != null || tabState.componentError != null) { - setTooltipInfo(new TooltipInfo(tabState.description, - tabState.componentError, this)); - } else { - setTooltipInfo(null); - } - - // TODO need to call this instead of super because the caption does - // not have an owner - String captionString = tabState.caption.isEmpty() ? null - : tabState.caption; - boolean ret = updateCaptionWithoutOwner(captionString, - !tabState.enabled, hasAttribute(tabState.description), - hasAttribute(tabState.componentError), - tab.getTabsheet().connector - .getResourceUrl(ComponentConstants.ICON_RESOURCE - + tabState.key), tabState.iconAltText); - - setClosable(tabState.closable); - - return ret; - } - - private boolean hasAttribute(String string) { - return string != null && !string.trim().isEmpty(); - } - - private VTabsheet getTabsheet() { - return tab.getTabsheet(); - } - - @Override - public void onBrowserEvent(Event event) { - if (closable && event.getTypeInt() == Event.ONCLICK - && event.getEventTarget().cast() == closeButton) { - tab.onClose(); - event.stopPropagation(); - event.preventDefault(); - } - - super.onBrowserEvent(event); - - if (event.getTypeInt() == Event.ONLOAD) { - getTabsheet().tabSizeMightHaveChanged(getTab()); - } - } - - public Tab getTab() { - return tab; - } - - public void setClosable(boolean closable) { - this.closable = closable; - if (closable && closeButton == null) { - closeButton = DOM.createSpan(); - closeButton.setInnerHTML("×"); - closeButton - .setClassName(VTabsheet.CLASSNAME + "-caption-close"); - - Roles.getTabRole().setAriaHiddenState(closeButton, true); - Roles.getTabRole().setAriaDisabledState(closeButton, true); - - getElement().appendChild(closeButton); - } else if (!closable && closeButton != null) { - getElement().removeChild(closeButton); - closeButton = null; - } - if (closable) { - addStyleDependentName("closable"); - } else { - removeStyleDependentName("closable"); - } - } - - public boolean isClosable() { - return closable; - } - - @Override - public int getRequiredWidth() { - int width = super.getRequiredWidth(); - if (closeButton != null) { - width += WidgetUtil.getRequiredWidth(closeButton); - } - return width; - } - - public com.google.gwt.user.client.Element getCloseButton() { - return DOM.asOld(closeButton); - } - - } - - static class TabBar extends ComplexPanel implements VCloseHandler { - - private final Element tr = DOM.createTR(); - - private final Element spacerTd = DOM.createTD(); - - private Tab selected; - - private VTabsheet tabsheet; - - TabBar(VTabsheet tabsheet) { - this.tabsheet = tabsheet; - - Element el = DOM.createTable(); - Roles.getPresentationRole().set(el); - - Element tbody = DOM.createTBody(); - DOM.appendChild(el, tbody); - DOM.appendChild(tbody, tr); - setStyleName(spacerTd, CLASSNAME + "-spacertd"); - DOM.appendChild(tr, spacerTd); - DOM.appendChild(spacerTd, DOM.createDiv()); - - setElement(el); - } - - @Override - public void onClose(VCloseEvent event) { - Tab tab = event.getTab(); - if (!tab.isEnabledOnServer()) { - return; - } - int tabIndex = getWidgetIndex(tab); - getTabsheet().sendTabClosedEvent(tabIndex); - } - - protected com.google.gwt.user.client.Element getContainerElement() { - return DOM.asOld(tr); - } - - /** - * Gets the number of tabs from the tab bar. - * - * @return the number of tabs from the tab bar. - */ - public int getTabCount() { - return getWidgetCount(); - } - - /** - * Adds a tab to the tab bar. - * - * @return the added tab. - */ - public Tab addTab() { - Tab t = new Tab(this); - int tabIndex = getTabCount(); - - // Logical attach - insert(t, tr, tabIndex, true); - - if (tabIndex == 0) { - // Set the "first" style - t.setStyleNames(false, true); - } - - getTabsheet().selectionHandler.registerTab(t); - - t.setCloseHandler(this); - - return t; - } - - /** - * Gets the tab sheet instance where the tab bar is attached to. - * - * @return the tab sheet instance where the tab bar is attached to. - */ - public VTabsheet getTabsheet() { - return tabsheet; - } - - public Tab getTab(int index) { - if (index < 0 || index >= getTabCount()) { - return null; - } - return (Tab) super.getWidget(index); - } - - private int getTabIndex(String tabId) { - if (tabId == null) { - return -1; - } - for (int i = 0; i < getTabCount(); i++) { - if (tabId.equals(getTab(i).id)) { - return i; - } - } - return -1; - } - - public void selectTab(int index) { - final Tab newSelected = getTab(index); - final Tab oldSelected = selected; - - newSelected.setStyleNames(true, isFirstVisibleTab(index), true); - newSelected.setTabulatorIndex(getTabsheet().tabulatorIndex); - Roles.getTabRole().setAriaSelectedState(newSelected.getElement(), - SelectedValue.TRUE); - - if (oldSelected != null && oldSelected != newSelected) { - oldSelected.setStyleNames(false, - isFirstVisibleTab(getWidgetIndex(oldSelected))); - oldSelected.setTabulatorIndex(-1); - - Roles.getTabRole().setAriaSelectedState( - oldSelected.getElement(), SelectedValue.FALSE); - } - - // Update the field holding the currently selected tab - selected = newSelected; - - // The selected tab might need more (or less) space - newSelected.recalculateCaptionWidth(); - getTab(tabsheet.activeTabIndex).recalculateCaptionWidth(); - } - - public Tab navigateTab(int fromIndex, int toIndex) { - Tab newNavigated = getTab(toIndex); - if (newNavigated == null) { - throw new IllegalArgumentException( - "Tab at provided index toIndex was not found"); - } - - Tab oldNavigated = getTab(fromIndex); - newNavigated.setStyleNames(newNavigated.equals(selected), - isFirstVisibleTab(toIndex), true); - - if (oldNavigated != null && fromIndex != toIndex) { - oldNavigated.setStyleNames(oldNavigated.equals(selected), - isFirstVisibleTab(fromIndex), false); - } - - return newNavigated; - } - - public void removeTab(int i) { - Tab tab = getTab(i); - if (tab == null) { - return; - } - - remove(tab); - - /* - * If this widget was selected we need to unmark it as the last - * selected - */ - if (tab == selected) { - selected = null; - } - // FIXME: Shouldn't something be selected instead? - - int scrollerIndexCandidate = getTabIndex(getTabsheet().scrollerPositionTabId); - if (scrollerIndexCandidate < 0) { - // The tab with id scrollerPositionTabId has been removed - scrollerIndexCandidate = getTabsheet().scrollerIndex; - } - scrollerIndexCandidate = selectNewShownTab(scrollerIndexCandidate); - if (scrollerIndexCandidate >= 0 - && scrollerIndexCandidate < getTabCount()) { - getTabsheet().scrollIntoView(getTab(scrollerIndexCandidate)); - } - } - - private int selectNewShownTab(int oldPosition) { - // After removing a tab, find a new scroll position. In most - // cases the scroll position does not change, but if the tab - // at the scroll position was removed, need to find a nearby - // tab that is visible. - for (int i = oldPosition; i < getTabCount(); i++) { - Tab tab = getTab(i); - if (!tab.isHiddenOnServer()) { - return i; - } - } - - for (int i = oldPosition - 1; i >= 0; i--) { - Tab tab = getTab(i); - if (!tab.isHiddenOnServer()) { - return i; - } - } - - return -1; - } - - private boolean isFirstVisibleTab(int index) { - return getFirstVisibleTab() == index; - } - - /** - * Returns the index of the first visible tab on the server - */ - private int getFirstVisibleTab() { - return getNextVisibleTab(-1); - } - - /** - * Find the next visible tab. Returns -1 if none is found. - * - * @param i - * @return - */ - private int getNextVisibleTab(int i) { - int tabs = getTabCount(); - do { - i++; - } while (i < tabs && getTab(i).isHiddenOnServer()); - - if (i == tabs) { - return -1; - } else { - return i; - } - } - - /** - * Returns the index of the first visible tab in browser. - */ - private int getFirstVisibleTabClient() { - int tabs = getTabCount(); - int i = 0; - while (i < tabs && !getTab(i).isVisible()) { - i++; - } - - if (i == tabs) { - return -1; - } else { - return i; - } - } - - /** - * Find the previous visible tab. Returns -1 if none is found. - * - * @param i - * @return - */ - private int getPreviousVisibleTab(int i) { - do { - i--; - } while (i >= 0 && getTab(i).isHiddenOnServer()); - - return i; - - } - - public int scrollLeft(int currentFirstVisible) { - int prevVisible = getPreviousVisibleTab(currentFirstVisible); - if (prevVisible == -1) { - return -1; - } - - Tab newFirst = getTab(prevVisible); - newFirst.setVisible(true); - newFirst.recalculateCaptionWidth(); - - return prevVisible; - } - - public int scrollRight(int currentFirstVisible) { - int nextVisible = getNextVisibleTab(currentFirstVisible); - if (nextVisible == -1) { - return -1; - } - Tab currentFirst = getTab(currentFirstVisible); - currentFirst.setVisible(false); - currentFirst.recalculateCaptionWidth(); - return nextVisible; - } - - private void recalculateCaptionWidths() { - for (int i = 0; i < getTabCount(); ++i) { - getTab(i).recalculateCaptionWidth(); - } - } - } - - // TODO using the CLASSNAME directly makes primaryStyleName for TabSheet of - // very limited use - all use of style names should be refactored in the - // future - public static final String CLASSNAME = TabsheetState.PRIMARY_STYLE_NAME; - - public static final String TABS_CLASSNAME = CLASSNAME + "-tabcontainer"; - public static final String SCROLLER_CLASSNAME = CLASSNAME + "-scroller"; - - /** For internal use only. May be removed or replaced in the future. */ - // tabbar and 'scroller' container - public final Element tabs; - - /** - * The tabindex property (position in the browser's focus cycle.) Named like - * this to avoid confusion with activeTabIndex. - */ - int tabulatorIndex = 0; - - private static final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel(); - - // tab-scroller element - private final Element scroller; - // tab-scroller next button element - private final Element scrollerNext; - // tab-scroller prev button element - private final Element scrollerPrev; - - /** - * The index of the first visible tab (when scrolled) - */ - private int scrollerIndex = 0; - /** - * The id of the tab at position scrollerIndex. This is used for keeping the - * scroll position unchanged when a tab is removed from the server side and - * the removed tab lies to the left of the current scroll position. For - * other cases scrollerIndex alone would be sufficient. Since the tab at the - * current scroll position can be removed, scrollerIndex is required in - * addition to this variable. - */ - private String scrollerPositionTabId; - - final TabBar tb = new TabBar(this); - /** For internal use only. May be removed or replaced in the future. */ - protected final VTabsheetPanel tabPanel = new VTabsheetPanel(); - /** For internal use only. May be removed or replaced in the future. */ - public final Element contentNode; - - private final Element deco; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean waitingForResponse; - - private String currentStyle; - - /** - * @return Whether the tab could be selected or not. - */ - private boolean canSelectTab(final int tabIndex) { - Tab tab = tb.getTab(tabIndex); - if (getApplicationConnection() == null || disabled - || waitingForResponse) { - return false; - } - if (!tab.isEnabledOnServer() || tab.isHiddenOnServer()) { - return false; - } - - // Note that we return true when tabIndex == activeTabIndex; the active - // tab could be selected, it's just a no-op. - return true; - } - - /** - * Load the content of a tab of the provided index. - * - * @param index - * of the tab to load - * - * @return true if the specified sheet gets loaded, otherwise false. - */ - public boolean loadTabSheet(int tabIndex) { - if (activeTabIndex != tabIndex && canSelectTab(tabIndex)) { - tb.selectTab(tabIndex); - - activeTabIndex = tabIndex; - - addStyleDependentName("loading"); - // Hide the current contents so a loading indicator can be shown - // instead - getCurrentlyDisplayedWidget().getElement().getParentElement() - .getStyle().setVisibility(Visibility.HIDDEN); - - getRpcProxy().setSelected(tabKeys.get(tabIndex).toString()); - - waitingForResponse = true; - - tb.getTab(tabIndex).focus(); // move keyboard focus to active tab - - return true; - } - - return false; - } - - /** - * Returns the currently displayed widget in the tab panel. - * - * @since 7.2 - * @return currently displayed content widget - */ - public Widget getCurrentlyDisplayedWidget() { - return tabPanel.getWidget(tabPanel.getVisibleWidget()); - } - - /** - * Returns the client to server RPC proxy for the tabsheet. - * - * @since 7.2 - * @return RPC proxy - */ - protected TabsheetServerRpc getRpcProxy() { - return connector.getRpcProxy(TabsheetServerRpc.class); - } - - /** - * For internal use only. - * - * Avoid using this method directly and use appropriate superclass methods - * where applicable. - * - * @deprecated since 7.2 - use more specific methods instead (getRpcProxy(), - * getConnectorForWidget(Widget) etc.) - * @return ApplicationConnection - */ - @Deprecated - public ApplicationConnection getApplicationConnection() { - return client; - } - - private VTooltip getVTooltip() { - return getApplicationConnection().getVTooltip(); - } - - public void tabSizeMightHaveChanged(Tab tab) { - // icon onloads may change total width of tabsheet - if (isDynamicWidth()) { - updateDynamicWidth(); - } - updateTabScroller(); - - } - - void sendTabClosedEvent(int tabIndex) { - getRpcProxy().closeTab(tabKeys.get(tabIndex)); - } - - public VTabsheet() { - super(CLASSNAME); - - // Tab scrolling - getElement().getStyle().setOverflow(Overflow.HIDDEN); - tabs = DOM.createDiv(); - DOM.setElementProperty(tabs, "className", TABS_CLASSNAME); - Roles.getTablistRole().set(tabs); - Roles.getTablistRole().setAriaLiveProperty(tabs, LiveValue.OFF); - scroller = DOM.createDiv(); - Roles.getTablistRole().setAriaHiddenState(scroller, true); - - DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME); - - scrollerPrev = DOM.createButton(); - scrollerPrev.setTabIndex(-1); - DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME - + "Prev"); - Roles.getTablistRole().setAriaHiddenState(scrollerPrev, true); - DOM.sinkEvents(scrollerPrev, Event.ONCLICK | Event.ONMOUSEDOWN); - - scrollerNext = DOM.createButton(); - scrollerNext.setTabIndex(-1); - DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME - + "Next"); - Roles.getTablistRole().setAriaHiddenState(scrollerNext, true); - DOM.sinkEvents(scrollerNext, Event.ONCLICK | Event.ONMOUSEDOWN); - - DOM.appendChild(getElement(), tabs); - - // Tabs - tabPanel.setStyleName(CLASSNAME + "-tabsheetpanel"); - contentNode = DOM.createDiv(); - Roles.getTabpanelRole().set(contentNode); - - deco = DOM.createDiv(); - - tb.setStyleName(CLASSNAME + "-tabs"); - DOM.setElementProperty(contentNode, "className", CLASSNAME + "-content"); - DOM.setElementProperty(deco, "className", CLASSNAME + "-deco"); - - add(tb, tabs); - DOM.appendChild(scroller, scrollerPrev); - DOM.appendChild(scroller, scrollerNext); - - DOM.appendChild(getElement(), contentNode); - add(tabPanel, contentNode); - DOM.appendChild(getElement(), deco); - - DOM.appendChild(tabs, scroller); - - // TODO Use for Safari only. Fix annoying 1px first cell in TabBar. - // DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM - // .getFirstChild(tb.getElement()))), "display", "none"); - - } - - @Override - public void onBrowserEvent(Event event) { - com.google.gwt.dom.client.Element eventTarget = DOM - .eventGetTarget(event); - - if (event.getTypeInt() == Event.ONCLICK) { - - // Tab scrolling - if (eventTarget == scrollerPrev || eventTarget == scrollerNext) { - scrollAccordingToScrollTarget(eventTarget); - - event.stopPropagation(); - } - - } else if (event.getTypeInt() == Event.ONMOUSEDOWN) { - - if (eventTarget == scrollerPrev || eventTarget == scrollerNext) { - // In case the focus was previously on a Tab, we need to cancel - // the upcoming blur on the Tab which will follow this mouse - // down event. - focusBlurManager.cancelNextBlurSchedule(); - - return; - } - } - - super.onBrowserEvent(event); - } - - /* - * Scroll the tab bar according to the last scrollTarget (the scroll button - * pressed). - */ - private void scrollAccordingToScrollTarget( - com.google.gwt.dom.client.Element scrollTarget) { - if (scrollTarget == null) { - return; - } - - int newFirstIndex = -1; - - // Scroll left. - if (isScrolledTabs() && scrollTarget == scrollerPrev) { - newFirstIndex = tb.scrollLeft(scrollerIndex); - - // Scroll right. - } else if (isClippedTabs() && scrollTarget == scrollerNext) { - newFirstIndex = tb.scrollRight(scrollerIndex); - } - - if (newFirstIndex != -1) { - scrollerIndex = newFirstIndex; - scrollerPositionTabId = tb.getTab(scrollerIndex).id; - updateTabScroller(); - } - - // For this to work well, make sure the method gets called only from - // user events. - selectionHandler.focusTabAtIndex(scrollerIndex); - } - - /** - * Checks if the tab with the selected index has been scrolled out of the - * view (on the left side). - * - * @param index - * @return - */ - private boolean scrolledOutOfView(int index) { - return scrollerIndex > index; - } - - /** For internal use only. May be removed or replaced in the future. */ - public void handleStyleNames(AbstractComponentState state) { - // Add proper stylenames for all elements (easier to prevent unwanted - // style inheritance) - if (ComponentStateUtil.hasStyles(state)) { - final List styles = state.styles; - if (currentStyle == null || !currentStyle.equals(styles.toString())) { - currentStyle = styles.toString(); - final String tabsBaseClass = TABS_CLASSNAME; - String tabsClass = tabsBaseClass; - final String contentBaseClass = CLASSNAME + "-content"; - String contentClass = contentBaseClass; - final String decoBaseClass = CLASSNAME + "-deco"; - String decoClass = decoBaseClass; - for (String style : styles) { - tb.addStyleDependentName(style); - tabsClass += " " + tabsBaseClass + "-" + style; - contentClass += " " + contentBaseClass + "-" + style; - decoClass += " " + decoBaseClass + "-" + style; - } - DOM.setElementProperty(tabs, "className", tabsClass); - DOM.setElementProperty(contentNode, "className", contentClass); - DOM.setElementProperty(deco, "className", decoClass); - } - } else { - tb.setStyleName(CLASSNAME + "-tabs"); - DOM.setElementProperty(tabs, "className", TABS_CLASSNAME); - DOM.setElementProperty(contentNode, "className", CLASSNAME - + "-content"); - DOM.setElementProperty(deco, "className", CLASSNAME + "-deco"); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateDynamicWidth() { - // Find width consumed by tabs - TableCellElement spacerCell = ((TableElement) tb.getElement().cast()) - .getRows().getItem(0).getCells().getItem(tb.getTabCount()); - - int spacerWidth = spacerCell.getOffsetWidth(); - DivElement div = (DivElement) spacerCell.getFirstChildElement(); - - int spacerMinWidth = spacerCell.getOffsetWidth() - div.getOffsetWidth(); - - int tabsWidth = tb.getOffsetWidth() - spacerWidth + spacerMinWidth; - - // Find content width - Style style = tabPanel.getElement().getStyle(); - String overflow = style.getProperty("overflow"); - style.setProperty("overflow", "hidden"); - style.setPropertyPx("width", tabsWidth); - - boolean hasTabs = tabPanel.getWidgetCount() > 0; - - Style wrapperstyle = null; - if (hasTabs) { - wrapperstyle = getCurrentlyDisplayedWidget().getElement() - .getParentElement().getStyle(); - wrapperstyle.setPropertyPx("width", tabsWidth); - } - // Get content width from actual widget - - int contentWidth = 0; - if (hasTabs) { - contentWidth = getCurrentlyDisplayedWidget().getOffsetWidth(); - } - style.setProperty("overflow", overflow); - - // Set widths to max(tabs,content) - if (tabsWidth < contentWidth) { - tabsWidth = contentWidth; - } - - int outerWidth = tabsWidth + getContentAreaBorderWidth(); - - tabs.getStyle().setPropertyPx("width", outerWidth); - style.setPropertyPx("width", tabsWidth); - if (hasTabs) { - wrapperstyle.setPropertyPx("width", tabsWidth); - } - - contentNode.getStyle().setPropertyPx("width", tabsWidth); - super.setWidth(outerWidth + "px"); - updateOpenTabSize(); - } - - private boolean isAllTabsBeforeIndexInvisible() { - boolean invisible = true; - for (int i = 0; i < scrollerIndex; i++) { - invisible = invisible & !tb.getTab(i).isVisible(); - } - return invisible; - } - - private boolean isScrollerPrevDisabled() { - return scrollerPrev.getClassName().contains( - PREV_SCROLLER_DISABLED_CLASSNAME); - } - - private boolean isScrollerHidden() { - return scroller.getStyle().getDisplay() - .equals(Display.NONE.getCssName()); - } - - private boolean isIndexSkippingHiddenTabs() { - return isAllTabsBeforeIndexInvisible() - && (isScrollerPrevDisabled() || isScrollerHidden()); - } - - @Override - public void renderTab(final TabState tabState, int index) { - Tab tab = tb.getTab(index); - if (tab == null) { - tab = tb.addTab(); - } - - tab.updateFromState(tabState); - tab.setEnabledOnServer((!disabledTabKeys.contains(tabKeys.get(index)))); - tab.setHiddenOnServer(!tabState.visible); - - if (scrolledOutOfView(index) && !isIndexSkippingHiddenTabs()) { - // Should not set tabs visible if they are scrolled out of view - tab.setVisible(false); - } else { - // When the tab was hidden and then turned visible again - // and there is space for it, it should be in view (#17096) (#17333) - if (isTabSetVisibleBeforeScroller(tabState, index, tab)) { - scrollerIndex = index; - tab.setVisible(true); - tab.setStyleNames(false, true); - - // scroll to the currently selected tab if it got clipped - // after making another tab visible - if (isClippedTabs()) { - scrollIntoView(getActiveTab()); - } - } else { - tab.setVisible(tabState.visible); - } - } - - /* - * Force the width of the caption container so the content will not wrap - * and tabs won't be too narrow in certain browsers - */ - tab.recalculateCaptionWidth(); - } - - /** - * Checks whether the tab has been set to visible and the scroller is at the - * first visible tab. That means that the scroller has to be adjusted so - * that the tab is visible again. - */ - private boolean isTabSetVisibleBeforeScroller(TabState tabState, int index, - Tab tab) { - return isIndexSkippingHiddenTabs() && isScrollerAtFirstVisibleTab() - && hasTabChangedVisibility(tabState, tab) - && scrolledOutOfView(index); - } - - /** - * Checks whether the tab is visible on server but is not visible on client - * yet. - */ - private boolean hasTabChangedVisibility(TabState tabState, Tab tab) { - return !tab.isVisible() && tabState.visible; - } - - private boolean isScrollerAtFirstVisibleTab() { - return tb.getFirstVisibleTabClient() == scrollerIndex; - } - - /** - * @deprecated as of 7.1, VTabsheet only keeps the active tab in the DOM - * without any place holders. - */ - @Deprecated - public class PlaceHolder extends VLabel { - public PlaceHolder() { - super(""); - } - } - - /** - * Renders the widget content for a tab sheet. - * - * @param newWidget - */ - public void renderContent(Widget newWidget) { - assert tabPanel.getWidgetCount() <= 1; - - if (null == newWidget) { - newWidget = new SimplePanel(); - } - - if (tabPanel.getWidgetCount() == 0) { - tabPanel.add(newWidget); - } else if (tabPanel.getWidget(0) != newWidget) { - tabPanel.remove(0); - tabPanel.add(newWidget); - } - - assert tabPanel.getWidgetCount() <= 1; - - // There's never any other index than 0, but maintaining API for now - tabPanel.showWidget(0); - - VTabsheet.this.iLayout(); - updateOpenTabSize(); - VTabsheet.this.removeStyleDependentName("loading"); - } - - /** - * Recalculates the sizes of tab captions, causing the tabs to be rendered - * the correct size. - */ - private void updateTabCaptionSizes() { - for (int tabIx = 0; tabIx < tb.getTabCount(); tabIx++) { - tb.getTab(tabIx).recalculateCaptionWidth(); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateContentNodeHeight() { - if (!isDynamicHeight()) { - int contentHeight = getOffsetHeight(); - contentHeight -= deco.getOffsetHeight(); - contentHeight -= tb.getOffsetHeight(); - - ComputedStyle cs = new ComputedStyle(contentNode); - contentHeight -= Math.ceil(cs.getPaddingHeight()); - contentHeight -= Math.ceil(cs.getBorderHeight()); - - if (contentHeight < 0) { - contentHeight = 0; - } - - // Set proper values for content element - contentNode.getStyle().setHeight(contentHeight, Unit.PX); - } else { - contentNode.getStyle().clearHeight(); - } - } - - /** - * Run internal layouting. - */ - public void iLayout() { - updateTabScroller(); - updateTabCaptionSizes(); - } - - /** - * Sets the size of the visible tab (component). As the tab is set to - * position: absolute (to work around a firefox flickering bug) we must keep - * this up-to-date by hand. - *

- * For internal use only. May be removed or replaced in the future. - */ - public void updateOpenTabSize() { - /* - * The overflow=auto element must have a height specified, otherwise it - * will be just as high as the contents and no scrollbars will appear - */ - int height = -1; - int width = -1; - int minWidth = 0; - - if (!isDynamicHeight()) { - height = contentNode.getOffsetHeight(); - } - if (!isDynamicWidth()) { - width = contentNode.getOffsetWidth() - getContentAreaBorderWidth(); - } else { - /* - * If the tabbar is wider than the content we need to use the tabbar - * width as minimum width so scrollbars get placed correctly (at the - * right edge). - */ - minWidth = tb.getOffsetWidth() - getContentAreaBorderWidth(); - } - tabPanel.fixVisibleTabSize(width, height, minWidth); - - } - - /** - * Layouts the tab-scroller elements, and applies styles. - */ - private void updateTabScroller() { - if (!isDynamicWidth()) { - tabs.getStyle().setWidth(100, Unit.PCT); - } - - // Make sure scrollerIndex is valid - if (scrollerIndex < 0 || scrollerIndex > tb.getTabCount()) { - scrollerIndex = tb.getFirstVisibleTab(); - } else if (tb.getTabCount() > 0 - && tb.getTab(scrollerIndex).isHiddenOnServer()) { - scrollerIndex = tb.getNextVisibleTab(scrollerIndex); - } - - boolean scrolled = isScrolledTabs(); - boolean clipped = isClippedTabs(); - if (tb.getTabCount() > 0 && tb.isVisible() && (scrolled || clipped)) { - scroller.getStyle().clearDisplay(); - DOM.setElementProperty(scrollerPrev, "className", - SCROLLER_CLASSNAME - + (scrolled ? "Prev" - : PREV_SCROLLER_DISABLED_CLASSNAME)); - DOM.setElementProperty(scrollerNext, "className", - SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled")); - - // the active tab should be focusable if and only if it is visible - boolean isActiveTabVisible = scrollerIndex <= activeTabIndex - && !isClipped(tb.selected); - tb.selected.setTabulatorIndex(isActiveTabVisible ? tabulatorIndex - : -1); - - } else { - scroller.getStyle().setDisplay(Display.NONE); - } - - if (BrowserInfo.get().isSafari()) { - /* - * another hack for webkits. tabscroller sometimes drops without - * "shaking it" reproducable in - * com.vaadin.tests.components.tabsheet.TabSheetIcons - */ - final Style style = scroller.getStyle(); - style.setProperty("whiteSpace", "normal"); - Scheduler.get().scheduleDeferred(new Command() { - - @Override - public void execute() { - style.setProperty("whiteSpace", ""); - } - }); - } - - } - - /** For internal use only. May be removed or replaced in the future. */ - public void showAllTabs() { - scrollerIndex = tb.getFirstVisibleTab(); - scrollerPositionTabId = scrollerIndex < 0 ? null : tb - .getTab(scrollerIndex).id; - for (int i = 0; i < tb.getTabCount(); i++) { - Tab t = tb.getTab(i); - if (!t.isHiddenOnServer()) { - t.setVisible(true); - } - } - } - - private boolean isScrolledTabs() { - return scrollerIndex > tb.getFirstVisibleTab(); - } - - private boolean isClippedTabs() { - return (tb.getOffsetWidth() - DOM.getElementPropertyInt((Element) tb - .getContainerElement().getLastChild().cast(), "offsetWidth")) > getOffsetWidth() - - (isScrolledTabs() ? scroller.getOffsetWidth() : 0); - } - - private boolean isClipped(Tab tab) { - return tab.getAbsoluteLeft() + tab.getOffsetWidth() > getAbsoluteLeft() - + getOffsetWidth() - scroller.getOffsetWidth(); - } - - @Override - protected void clearPaintables() { - - int i = tb.getTabCount(); - while (i > 0) { - tb.removeTab(--i); - } - tabPanel.clear(); - - } - - @Override - public Iterator getWidgetIterator() { - return tabPanel.iterator(); - } - - /** For internal use only. May be removed or replaced in the future. */ - public int getContentAreaBorderWidth() { - return WidgetUtil.measureHorizontalBorder(contentNode); - } - - @Override - public int getTabCount() { - return tb.getTabCount(); - } - - @Override - public ComponentConnector getTab(int index) { - if (tabPanel.getWidgetCount() > index) { - Widget widget = tabPanel.getWidget(index); - return getConnectorForWidget(widget); - } - return null; - } - - @Override - public void removeTab(int index) { - tb.removeTab(index); - - // Removing content from tp is handled by the connector - } - - @Override - public void selectTab(int index) { - tb.selectTab(index); - } - - @Override - public void focus() { - getActiveTab().focus(); - } - - public void blur() { - getActiveTab().blur(); - } - - /* - * Gets the active tab. - */ - private Tab getActiveTab() { - return tb.getTab(activeTabIndex); - } - - @Override - public void setConnector(AbstractComponentConnector connector) { - super.setConnector(connector); - - focusBlurManager.connector = connector; - } - - /* - * The focus and blur manager instance. - */ - private FocusBlurManager focusBlurManager = new FocusBlurManager(); - - /* - * Generate the correct focus/blur events for the main TabSheet component - * (#14304). - * - * The TabSheet must fire one focus event when the user clicks on the tab - * bar (i.e. inner TabBar class) containing the Tabs or when the focus is - * provided to the TabSheet by any means. Also one blur event should be - * fired only when the user leaves the tab bar. After the user focus on the - * tab bar and before leaving it, no matter how many times he's pressing the - * Tabs or the scroll buttons, the TabSheet component should not fire any of - * those blur/focus events. - * - * The only focusable elements contained in the tab bar are the Tabs (see - * inner class Tab). The reason is the accessibility support. - * - * Having this in mind, the chosen solution path for our problem is to match - * a sequence of focus/blur events on the tabs, choose only the first focus - * and last blur events and pass only those further to the main component. - * Any consecutive blur/focus events on 2 Tabs must be ignored. - * - * Because in a blur event we don't know whether or not a focus will follow, - * we just defer a command initiated on the blur event to wait and see if - * any focus will appear. The command will be executed after the next focus, - * so if no focus was triggered in the mean while it'll submit the blur - * event to the main component, otherwise it'll do nothing, so the main - * component will not generate the blur.. - */ - private static class FocusBlurManager { - - // The real tab with focus on it. If the focus goes to another element - // in the page this will be null. - private Tab focusedTab; - - /* - * Gets the focused tab. - */ - private Tab getFocusedTab() { - return focusedTab; - } - - /* - * Sets the local field tracking the focused tab. - */ - private void setFocusedTab(Tab focusedTab) { - this.focusedTab = focusedTab; - } - - /* - * The ultimate focus/blur event dispatcher. - */ - private AbstractComponentConnector connector; - - /** - * Delegate method for the onFocus event occurring on Tab. - * - * @since 7.2.6 - * @param newFocusTab - * the new focused tab. - * @see #onBlur(Tab) - */ - public void onFocus(Tab newFocusTab) { - - if (connector.hasEventListener(EventId.FOCUS)) { - - // Send the focus event only first time when we focus on any - // tab. The focused tab will be reseted on the last blur. - if (focusedTab == null) { - connector.getRpcProxy(FocusAndBlurServerRpc.class).focus(); - } - } - - cancelLastBlurSchedule(); - - setFocusedTab(newFocusTab); - } - - /** - * Delegate method for the onBlur event occurring on Tab. - * - * @param blurSource - * the source of the blur. - * - * @see #onFocus(Tab) - */ - public void onBlur(Tab blurSource) { - if (focusedTab != null && focusedTab == blurSource) { - - if (connector.hasEventListener(EventId.BLUR)) { - scheduleBlur(focusedTab); - } - } - } - - /* - * The last blur command to be executed. - */ - private BlurCommand blurCommand; - - /* - * Execute the final blur command. - */ - private class BlurCommand implements Command { - - /* - * The blur source. - */ - private Tab blurSource; - - /** - * Create the blur command using the blur source. - * - * @param blurSource - * the source. - * @param focusedTabProvider - * provides the current focused tab. - */ - public BlurCommand(Tab blurSource) { - this.blurSource = blurSource; - } - - /** - * Stop the command from being executed. - * - * @since 7.4 - */ - public void stopSchedule() { - blurSource = null; - } - - /** - * Schedule the command for a deferred execution. - * - * @since 7.4 - */ - public void scheduleDeferred() { - Scheduler.get().scheduleDeferred(this); - } - - @Override - public void execute() { - - Tab focusedTab = getFocusedTab(); - - if (blurSource == null) { - return; - } - - // The focus didn't change since this blur triggered, so - // the new focused element is not a tab. - if (focusedTab == blurSource) { - - // We're certain there's no focus anymore. - focusedTab.removeAssistiveDescription(); - setFocusedTab(null); - - connector.getRpcProxy(FocusAndBlurServerRpc.class).blur(); - } - - // Call this to set it to null and be consistent. - cancelLastBlurSchedule(); - } - } - - /* - * Schedule a new blur event for a deferred execution. - */ - private void scheduleBlur(Tab blurSource) { - - if (nextBlurScheduleCancelled) { - - // This will set the stopNextBlurCommand back to false as well. - cancelLastBlurSchedule(); - - // Reset the status. - nextBlurScheduleCancelled = false; - return; - } - - cancelLastBlurSchedule(); - - blurCommand = new BlurCommand(blurSource); - blurCommand.scheduleDeferred(); - } - - /** - * Remove the last blur deferred command from execution. - */ - public void cancelLastBlurSchedule() { - if (blurCommand != null) { - blurCommand.stopSchedule(); - blurCommand = null; - } - - // We really want to make sure this flag gets reseted at any time - // when something interact with the blur manager and ther's no blur - // command scheduled (as we just canceled it). - nextBlurScheduleCancelled = false; - } - - /** - * Cancel the next scheduled execution. This method must be called only - * from an event occurring before the onBlur event. It's the case of IE - * which doesn't trigger the focus event, so we're using this approach - * to cancel the next blur event prior it's execution, calling the - * method from mouse down event. - */ - public void cancelNextBlurSchedule() { - - // Make sure there's still no other command to be executed. - cancelLastBlurSchedule(); - - nextBlurScheduleCancelled = true; - } - - /* - * Flag that the next deferred command won't get executed. This is - * useful in case of IE where the user focus event don't fire and we're - * using the mouse down event to track the focus. But the mouse down - * event triggers before the blur, so we need to cancel the deferred - * execution in advance. - */ - private boolean nextBlurScheduleCancelled = false; - - } - - /* - * The tabs selection handler instance. - */ - private final TabSelectionHandler selectionHandler = new TabSelectionHandler(); - - /* - * Handle the events for selecting the tabs. - */ - private class TabSelectionHandler implements FocusHandler, BlurHandler, - KeyDownHandler, ClickHandler, MouseDownHandler { - - /** For internal use only. May be removed or replaced in the future. */ - // The current visible focused index. - private int focusedTabIndex = 0; - - /** - * Register the tab to the selection handler. - * - * @param tab - * the tab to register. - */ - public void registerTab(Tab tab) { - - tab.addBlurHandler(this); - tab.addFocusHandler(this); - tab.addKeyDownHandler(this); - tab.addClickHandler(this); - tab.addMouseDownHandler(this); - } - - @Override - public void onBlur(final BlurEvent event) { - - getVTooltip().hideTooltip(); - - Object blurSource = event.getSource(); - - if (blurSource instanceof Tab) { - focusBlurManager.onBlur((Tab) blurSource); - } - } - - @Override - public void onFocus(FocusEvent event) { - - if (event.getSource() instanceof Tab) { - Tab focusSource = (Tab) event.getSource(); - focusBlurManager.onFocus(focusSource); - - if (focusSource.hasTooltip()) { - focusSource.setAssistiveDescription(getVTooltip() - .getUniqueId()); - getVTooltip().showAssistive(focusSource.getTooltipInfo()); - } - - } - } - - @Override - public void onClick(ClickEvent event) { - - // IE doesn't trigger focus when click, so we need to make sure - // the previous blur deferred command will get killed. - focusBlurManager.cancelLastBlurSchedule(); - - TabCaption caption = (TabCaption) event.getSource(); - Element targetElement = event.getNativeEvent().getEventTarget() - .cast(); - // the tab should not be focused if the close button was clicked - if (targetElement == caption.getCloseButton()) { - return; - } - - int index = tb.getWidgetIndex(caption.getParent()); - - tb.navigateTab(focusedTabIndex, index); - - focusedTabIndex = index; - - if (!loadTabSheet(index)) { - - // This needs to be called at the end, as the activeTabIndex - // is set in the loadTabSheet. - focus(); - } - } - - @Override - public void onMouseDown(MouseDownEvent event) { - - if (event.getSource() instanceof Tab) { - - // IE doesn't trigger focus when click, so we need to make sure - // the - // next blur deferred command will get killed. - focusBlurManager.cancelNextBlurSchedule(); - } - } - - @Override - public void onKeyDown(KeyDownEvent event) { - if (event.getSource() instanceof Tab) { - int keycode = event.getNativeEvent().getKeyCode(); - - if (!event.isAnyModifierKeyDown()) { - if (keycode == getPreviousTabKey()) { - selectPreviousTab(); - event.stopPropagation(); - - } else if (keycode == getNextTabKey()) { - selectNextTab(); - event.stopPropagation(); - - } else if (keycode == getCloseTabKey()) { - Tab tab = tb.getTab(activeTabIndex); - if (tab.isClosable()) { - tab.onClose(); - } - - } else if (keycode == getSelectTabKey()) { - loadTabSheet(focusedTabIndex); - - // Prevent the page from scrolling when hitting space - // (select key) to select the current tab. - event.preventDefault(); - } - } - } - } - - /* - * Left arrow key selection. - */ - private void selectPreviousTab() { - int newTabIndex = focusedTabIndex; - // Find the previous visible and enabled tab if any. - do { - newTabIndex--; - } while (newTabIndex >= 0 && !canSelectTab(newTabIndex)); - - if (newTabIndex >= 0) { - keySelectTab(newTabIndex); - } - } - - /* - * Right arrow key selection. - */ - private void selectNextTab() { - int newTabIndex = focusedTabIndex; - // Find the next visible and enabled tab if any. - do { - newTabIndex++; - } while (newTabIndex < getTabCount() && !canSelectTab(newTabIndex)); - - if (newTabIndex < getTabCount()) { - keySelectTab(newTabIndex); - } - } - - /* - * Select the specified tab using left/right key. - */ - private void keySelectTab(int newTabIndex) { - Tab tab = tb.getTab(newTabIndex); - if (tab == null) { - return; - } - - // Focus the tab, otherwise the selected one will loose focus and - // TabSheet will get blurred. - focusTabAtIndex(newTabIndex); - - tb.navigateTab(focusedTabIndex, newTabIndex); - - focusedTabIndex = newTabIndex; - } - - /** - * Focus the specified tab. Make sure to call this only from user - * events, otherwise will break things. - * - * @param tabIndex - * the index of the tab to set. - */ - void focusTabAtIndex(int tabIndex) { - Tab tabToFocus = tb.getTab(tabIndex); - if (tabToFocus != null) { - tabToFocus.focus(); - } - } - - } - - /** - * @return The key code of the keyboard shortcut that selects the previous - * tab in a focused tabsheet. - */ - protected int getPreviousTabKey() { - return KeyCodes.KEY_LEFT; - } - - /** - * Gets the key to activate the selected tab when navigating using - * previous/next (left/right) keys. - * - * @return the key to activate the selected tab. - * - * @see #getNextTabKey() - * @see #getPreviousTabKey() - */ - protected int getSelectTabKey() { - return KeyCodes.KEY_SPACE; - } - - /** - * @return The key code of the keyboard shortcut that selects the next tab - * in a focused tabsheet. - */ - protected int getNextTabKey() { - return KeyCodes.KEY_RIGHT; - } - - /** - * @return The key code of the keyboard shortcut that closes the currently - * selected tab in a focused tabsheet. - */ - protected int getCloseTabKey() { - return KeyCodes.KEY_DELETE; - } - - private void scrollIntoView(Tab tab) { - - if (!tab.isHiddenOnServer()) { - - // Check for visibility first as clipped tabs to the right are - // always visible. - // On IE8 a tab with false visibility would have the bounds of the - // full TabBar. - if (!tab.isVisible()) { - while (!tab.isVisible()) { - scrollerIndex = tb.scrollLeft(scrollerIndex); - } - updateTabScroller(); - - } else if (isClipped(tab)) { - while (isClipped(tab) && scrollerIndex != -1) { - scrollerIndex = tb.scrollRight(scrollerIndex); - } - updateTabScroller(); - } - if (scrollerIndex >= 0 && scrollerIndex < tb.getTabCount()) { - scrollerPositionTabId = tb.getTab(scrollerIndex).id; - } else { - scrollerPositionTabId = null; - } - } - } - - /** - * Makes tab bar visible. - * - * @since 7.2 - */ - public void showTabs() { - tb.setVisible(true); - removeStyleName(CLASSNAME + "-hidetabs"); - tb.recalculateCaptionWidths(); - } - - /** - * Makes tab bar invisible. - * - * @since 7.2 - */ - public void hideTabs() { - tb.setVisible(false); - addStyleName(CLASSNAME + "-hidetabs"); - } - - /** Matches tab[ix] - used for extracting the index of the targeted tab */ - private static final RegExp SUBPART_TAB_REGEXP = RegExp - .compile("tab\\[(\\d+)](.*)"); - - @Override - public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - if ("tabpanel".equals(subPart)) { - return DOM.asOld(tabPanel.getElement().getFirstChildElement()); - } else if (SUBPART_TAB_REGEXP.test(subPart)) { - MatchResult result = SUBPART_TAB_REGEXP.exec(subPart); - int tabIx = Integer.valueOf(result.getGroup(1)); - Tab tab = tb.getTab(tabIx); - if (tab != null) { - if ("/close".equals(result.getGroup(2))) { - if (tab.isClosable()) { - return tab.tabCaption.getCloseButton(); - } - } else { - return tab.tabCaption.getElement(); - } - } - } - return null; - } - - @Override - public String getSubPartName(com.google.gwt.user.client.Element subElement) { - if (tabPanel.getElement().equals(subElement.getParentElement()) - || tabPanel.getElement().equals(subElement)) { - return "tabpanel"; - } else { - for (int i = 0; i < tb.getTabCount(); ++i) { - Tab tab = tb.getTab(i); - if (tab.isClosable() - && tab.tabCaption.getCloseButton().isOrHasChild( - subElement)) { - return "tab[" + i + "]/close"; - } else if (tab.getElement().isOrHasChild(subElement)) { - return "tab[" + i + "]"; - } - } - } - return null; - } -} diff --git a/client/src/com/vaadin/client/ui/VTabsheetBase.java b/client/src/com/vaadin/client/ui/VTabsheetBase.java deleted file mode 100644 index e96aa035ed..0000000000 --- a/client/src/com/vaadin/client/ui/VTabsheetBase.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.ComplexPanel; -import com.google.gwt.user.client.ui.HasEnabled; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorMap; -import com.vaadin.shared.ui.tabsheet.TabState; - -public abstract class VTabsheetBase extends ComplexPanel implements HasEnabled { - - /** For internal use only. May be removed or replaced in the future. */ - protected ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - protected final ArrayList tabKeys = new ArrayList(); - /** For internal use only. May be removed or replaced in the future. */ - protected Set disabledTabKeys = new HashSet(); - - /** For internal use only. May be removed or replaced in the future. */ - protected int activeTabIndex = 0; - /** For internal use only. May be removed or replaced in the future. */ - protected boolean disabled; - /** For internal use only. May be removed or replaced in the future. */ - protected boolean readonly; - - /** For internal use only. May be removed or replaced in the future. */ - protected AbstractComponentConnector connector; - - private boolean tabCaptionsAsHtml = false; - - public VTabsheetBase(String classname) { - setElement(DOM.createDiv()); - setStyleName(classname); - } - - /** - * @return a list of currently shown Widgets - */ - public abstract Iterator getWidgetIterator(); - - /** - * Clears current tabs and contents - */ - protected abstract void clearPaintables(); - - /** - * Implement in extending classes. This method should render needed elements - * and set the visibility of the tab according to the 'selected' parameter. - */ - public abstract void renderTab(TabState tabState, int index); - - /** - * Implement in extending classes. This method should return the number of - * tabs currently rendered. - */ - public abstract int getTabCount(); - - /** - * Implement in extending classes. This method should return the Paintable - * corresponding to the given index. - */ - public abstract ComponentConnector getTab(int index); - - /** - * Implement in extending classes. This method should remove the rendered - * tab with the specified index. - */ - public abstract void removeTab(int index); - - /** - * Returns true if the width of the widget is undefined, false otherwise. - * - * @since 7.2 - * @return true if width of the widget is determined by its content - */ - protected boolean isDynamicWidth() { - return getConnectorForWidget(this).isUndefinedWidth(); - } - - /** - * Returns true if the height of the widget is undefined, false otherwise. - * - * @since 7.2 - * @return true if width of the height is determined by its content - */ - protected boolean isDynamicHeight() { - return getConnectorForWidget(this).isUndefinedHeight(); - } - - /** - * Sets the connector that should be notified of events etc. - * - * For internal use only. This method may be removed or replaced in the - * future. - * - * @since 7.2 - * @param connector - */ - public void setConnector(AbstractComponentConnector connector) { - this.connector = connector; - } - - /** For internal use only. May be removed or replaced in the future. */ - public void clearTabKeys() { - tabKeys.clear(); - disabledTabKeys.clear(); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void addTabKey(String key, boolean disabled) { - tabKeys.add(key); - if (disabled) { - disabledTabKeys.add(key); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void setClient(ApplicationConnection client) { - this.client = client; - } - - /** For internal use only. May be removed or replaced in the future. */ - public void setActiveTabIndex(int activeTabIndex) { - this.activeTabIndex = activeTabIndex; - } - - /** For internal use only. May be removed or replaced in the future. */ - @Override - public void setEnabled(boolean enabled) { - disabled = !enabled; - } - - /** For internal use only. May be removed or replaced in the future. */ - public void setReadonly(boolean readonly) { - this.readonly = readonly; - } - - /** For internal use only. May be removed or replaced in the future. */ - protected ComponentConnector getConnectorForWidget(Widget widget) { - return ConnectorMap.get(client).getConnector(widget); - } - - /** For internal use only. May be removed or replaced in the future. */ - public abstract void selectTab(int index); - - @Override - public boolean isEnabled() { - return !disabled; - } - - /** - * Sets whether the caption is rendered as HTML. - *

- * The default is false, i.e. render tab captions as plain text - * - * @since 7.4 - * @param captionAsHtml - * true if the captions are rendered as HTML, false if rendered - * as plain text - */ - public void setTabCaptionsAsHtml(boolean tabCaptionsAsHtml) { - this.tabCaptionsAsHtml = tabCaptionsAsHtml; - } - - /** - * Checks whether captions are rendered as HTML - * - * The default is false, i.e. render tab captions as plain text - * - * @since 7.4 - * @return true if the captions are rendered as HTML, false if rendered as - * plain text - */ - public boolean isTabCaptionsAsHtml() { - return tabCaptionsAsHtml; - } - -} diff --git a/client/src/com/vaadin/client/ui/VTabsheetPanel.java b/client/src/com/vaadin/client/ui/VTabsheetPanel.java deleted file mode 100644 index 240f493907..0000000000 --- a/client/src/com/vaadin/client/ui/VTabsheetPanel.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.Style.Visibility; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.ComplexPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler; - -/** - * A panel that displays all of its child widgets in a 'deck', where only one - * can be visible at a time. It is used by - * {@link com.vaadin.client.ui.VTabsheet}. - * - * This class has the same basic functionality as the GWT DeckPanel - * {@link com.google.gwt.user.client.ui.DeckPanel}, with the exception that it - * doesn't manipulate the child widgets' width and height attributes. - */ -public class VTabsheetPanel extends ComplexPanel { - - private Widget visibleWidget; - - private final TouchScrollHandler touchScrollHandler; - - /** - * Creates an empty tabsheet panel. - */ - public VTabsheetPanel() { - setElement(DOM.createDiv()); - touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); - } - - /** - * Adds the specified widget to the deck. - * - * @param w - * the widget to be added - */ - @Override - public void add(Widget w) { - Element el = createContainerElement(); - DOM.appendChild(getElement(), el); - super.add(w, el); - } - - private Element createContainerElement() { - Element el = DOM.createDiv(); - el.getStyle().setPosition(Position.ABSOLUTE); - hide(el); - touchScrollHandler.addElement(el); - return el; - } - - /** - * Gets the index of the currently-visible widget. - * - * @return the visible widget's index - */ - public int getVisibleWidget() { - return getWidgetIndex(visibleWidget); - } - - /** - * Inserts a widget before the specified index. - * - * @param w - * the widget to be inserted - * @param beforeIndex - * the index before which it will be inserted - * @throws IndexOutOfBoundsException - * if beforeIndex is out of range - */ - public void insert(Widget w, int beforeIndex) { - Element el = createContainerElement(); - DOM.insertChild(getElement(), el, beforeIndex); - super.insert(w, el, beforeIndex, false); - } - - @Override - public boolean remove(Widget w) { - Element child = w.getElement(); - Element parent = null; - if (child != null) { - parent = DOM.getParent(child); - } - final boolean removed = super.remove(w); - if (removed) { - if (visibleWidget == w) { - visibleWidget = null; - } - if (parent != null) { - DOM.removeChild(getElement(), parent); - } - touchScrollHandler.removeElement(parent); - } - return removed; - } - - /** - * Shows the widget at the specified index. This causes the currently- - * visible widget to be hidden. - * - * @param index - * the index of the widget to be shown - */ - public void showWidget(int index) { - checkIndexBoundsForAccess(index); - Widget newVisible = getWidget(index); - if (visibleWidget != newVisible) { - if (visibleWidget != null) { - hide(DOM.getParent(visibleWidget.getElement())); - } - visibleWidget = newVisible; - touchScrollHandler.setElements(visibleWidget.getElement() - .getParentElement()); - } - // Always ensure the selected tab is visible. If server prevents a tab - // change we might end up here with visibleWidget == newVisible but its - // parent is still hidden. - unHide(DOM.getParent(visibleWidget.getElement())); - } - - private void hide(Element e) { - e.getStyle().setVisibility(Visibility.HIDDEN); - e.getStyle().setTop(-100000, Unit.PX); - e.getStyle().setLeft(-100000, Unit.PX); - } - - private void unHide(Element e) { - e.getStyle().setTop(0, Unit.PX); - e.getStyle().setLeft(0, Unit.PX); - e.getStyle().clearVisibility(); - } - - public void fixVisibleTabSize(int width, int height, int minWidth) { - if (visibleWidget == null) { - return; - } - - boolean dynamicHeight = false; - - if (height < 0) { - height = visibleWidget.getOffsetHeight(); - dynamicHeight = true; - } - if (width < 0) { - width = visibleWidget.getOffsetWidth(); - } - if (width < minWidth) { - width = minWidth; - } - - Element wrapperDiv = visibleWidget.getElement().getParentElement(); - - // width first - getElement().getStyle().setPropertyPx("width", width); - wrapperDiv.getStyle().setPropertyPx("width", width); - - if (dynamicHeight) { - // height of widget might have changed due wrapping - height = visibleWidget.getOffsetHeight(); - } - // v-tabsheet-tabsheetpanel height - getElement().getStyle().setPropertyPx("height", height); - - // widget wrapper height - if (dynamicHeight) { - wrapperDiv.getStyle().clearHeight(); - } else { - // widget wrapper height - wrapperDiv.getStyle().setPropertyPx("height", height); - } - } - - public void replaceComponent(Widget oldComponent, Widget newComponent) { - boolean isVisible = (visibleWidget == oldComponent); - int widgetIndex = getWidgetIndex(oldComponent); - remove(oldComponent); - insert(newComponent, widgetIndex); - if (isVisible) { - showWidget(widgetIndex); - } - } -} diff --git a/client/src/com/vaadin/client/ui/VTextArea.java b/client/src/com/vaadin/client/ui/VTextArea.java deleted file mode 100644 index bb3d3a476b..0000000000 --- a/client/src/com/vaadin/client/ui/VTextArea.java +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.dom.client.Style.WhiteSpace; -import com.google.gwt.dom.client.TextAreaElement; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.ui.dd.DragImageModifier; - -/** - * This class represents a multiline textfield (textarea). - * - * TODO consider replacing this with a RichTextArea based implementation. IE - * does not support CSS height for textareas in Strict mode :-( - * - * @author Vaadin Ltd. - * - */ -public class VTextArea extends VTextField implements DragImageModifier { - - public static final String CLASSNAME = "v-textarea"; - private boolean wordwrap = true; - private MaxLengthHandler maxLengthHandler = new MaxLengthHandler(); - private boolean browserSupportsMaxLengthAttribute = browserSupportsMaxLengthAttribute(); - private EnterDownHandler enterDownHandler = new EnterDownHandler(); - - public VTextArea() { - super(DOM.createTextArea()); - setStyleName(CLASSNAME); - - // KeyDownHandler is needed for correct text input on all - // browsers, not just those that don't support a max length attribute - addKeyDownHandler(enterDownHandler); - - if (!browserSupportsMaxLengthAttribute) { - addKeyUpHandler(maxLengthHandler); - addChangeHandler(maxLengthHandler); - sinkEvents(Event.ONPASTE); - } - } - - public TextAreaElement getTextAreaElement() { - return super.getElement().cast(); - } - - public void setRows(int rows) { - getTextAreaElement().setRows(rows); - } - - @Override - public void setSelectionRange(int pos, int length) { - super.setSelectionRange(pos, length); - final String value = getValue(); - /* - * Align position to index inside string value - */ - int index = pos; - if (index < 0) { - index = 0; - } - if (index > value.length()) { - index = value.length(); - } - // Get pixels count required to scroll textarea vertically - int scrollTop = getScrollTop(value, index); - int scrollLeft = -1; - /* - * Check if textarea has wrap attribute set to "off". In the latter case - * horizontal scroll is also required. - */ - if (!isWordwrap()) { - // Get pixels count required to scroll textarea horizontally - scrollLeft = getScrollLeft(value, index); - } - // Set back original text if previous methods calls changed it - if (!isWordwrap() || index < value.length()) { - setValue(value, false); - } - /* - * Call original method to set cursor position. In most browsers it - * doesn't lead to scrolling. - */ - super.setSelectionRange(pos, length); - /* - * Align vertical scroll to middle of textarea view (height) if - * scrolling is reqiured at all. - */ - if (scrollTop > 0) { - scrollTop += getElement().getClientHeight() / 2; - } - /* - * Align horizontal scroll to middle of textarea view (widht) if - * scrolling is reqiured at all. - */ - if (scrollLeft > 0) { - scrollLeft += getElement().getClientWidth() / 2; - } - /* - * Scroll if computed scrollTop is greater than scroll after cursor - * setting - */ - if (getElement().getScrollTop() < scrollTop) { - getElement().setScrollTop(scrollTop); - } - /* - * Scroll if computed scrollLeft is greater than scroll after cursor - * setting - */ - if (getElement().getScrollLeft() < scrollLeft) { - getElement().setScrollLeft(scrollLeft); - } - } - - /* - * Get horizontal scroll value required to get position visible. Method is - * called only when text wrapping is off. There is need to scroll - * horizontally in case words are wrapped. - */ - private int getScrollLeft(String value, int index) { - String beginning = value.substring(0, index); - // Compute beginning of the current line - int begin = beginning.lastIndexOf('\n'); - String line = value.substring(begin + 1); - index = index - begin - 1; - if (index < line.length()) { - index++; - } - line = line.substring(0, index); - /* - * Now line contains current line up to index position - */ - setValue(line.trim(), false); // Set this line to the textarea. - /* - * Scroll textarea up to the end of the line (maximum possible - * horizontal scrolling value). Now the end line becomes visible. - */ - getElement().setScrollLeft(getElement().getScrollWidth()); - // Return resulting horizontal scrolling value. - return getElement().getScrollLeft(); - } - - /* - * Get vertical scroll value required to get position visible - */ - private int getScrollTop(String value, int index) { - /* - * Trim text after position and set this trimmed text if index is not - * very end. - */ - if (index < value.length()) { - String beginning = value.substring(0, index); - setValue(beginning, false); - } - /* - * Now textarea contains trimmed text and could be scrolled up to the - * top. Scroll it to maximum possible value to get end of the text - * visible. - */ - getElement().setScrollTop(getElement().getScrollHeight()); - // Return resulting vertical scrolling value. - return getElement().getScrollTop(); - } - - private class MaxLengthHandler implements KeyUpHandler, ChangeHandler { - - @Override - public void onKeyUp(KeyUpEvent event) { - enforceMaxLength(); - } - - public void onPaste(Event event) { - enforceMaxLength(); - } - - @Override - public void onChange(ChangeEvent event) { - // Opera does not support paste events so this enforces max length - // for Opera. - enforceMaxLength(); - } - - } - - protected void enforceMaxLength() { - if (getMaxLength() >= 0) { - Scheduler.get().scheduleDeferred(new Command() { - @Override - public void execute() { - if (getText().length() > getMaxLength()) { - setText(getText().substring(0, getMaxLength())); - } - } - }); - } - } - - protected boolean browserSupportsMaxLengthAttribute() { - BrowserInfo info = BrowserInfo.get(); - if (info.isFirefox()) { - return true; - } - if (info.isSafari()) { - return true; - } - if (info.isIE10() || info.isIE11() || info.isEdge()) { - return true; - } - if (info.isAndroid()) { - return true; - } - return false; - } - - @Override - protected void updateMaxLength(int maxLength) { - if (browserSupportsMaxLengthAttribute) { - super.updateMaxLength(maxLength); - } else { - // Events handled by MaxLengthHandler. This call enforces max length - // when the max length value has changed - enforceMaxLength(); - } - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (event.getTypeInt() == Event.ONPASTE) { - maxLengthHandler.onPaste(event); - } - } - - private class EnterDownHandler implements KeyDownHandler { - - @Override - public void onKeyDown(KeyDownEvent event) { - // Fix for #12424/13811 - if the key being pressed is enter, we stop - // propagation of the KeyDownEvents if there were no modifier keys - // also pressed. This prevents shortcuts that are bound to only the - // enter key from being processed but allows usage of e.g. - // shift-enter or ctrl-enter. - if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER - && !event.isAnyModifierKeyDown()) { - event.stopPropagation(); - } - } - - } - - @Override - public int getCursorPos() { - // This is needed so that TextBoxImplIE6 is used to return the correct - // position for old Internet Explorer versions where it has to be - // detected in a different way. - return getImpl().getTextAreaCursorPos(getElement()); - } - - @Override - protected void setMaxLengthToElement(int newMaxLength) { - // There is no maxlength property for textarea. The maximum length is - // enforced by the KEYUP handler - - } - - public void setWordwrap(boolean wordwrap) { - if (wordwrap == this.wordwrap) { - return; // No change - } - - if (wordwrap) { - getElement().removeAttribute("wrap"); - getElement().getStyle().clearOverflow(); - getElement().getStyle().clearWhiteSpace(); - } else { - getElement().setAttribute("wrap", "off"); - getElement().getStyle().setOverflow(Overflow.AUTO); - getElement().getStyle().setWhiteSpace(WhiteSpace.PRE); - } - if (BrowserInfo.get().isOpera() - || (BrowserInfo.get().isWebkit() && wordwrap)) { - // Opera fails to dynamically update the wrap attribute so we detach - // and reattach the whole TextArea. - // Webkit fails to properly reflow the text when enabling wrapping, - // same workaround - WidgetUtil.detachAttach(getElement()); - } - this.wordwrap = wordwrap; - } - - @Override - public void onKeyDown(KeyDownEvent event) { - // Overridden to avoid submitting TextArea value on enter in IE. This is - // another reason why widgets should inherit a common abstract - // class instead of directly each other. - // This method is overridden only for IE and Firefox. - } - - @Override - public void modifyDragImage(Element element) { - // Fix for #13557 - drag image doesn't show original text area text. - // It happens because "value" property is not copied into the cloned - // element - String value = getElement().getPropertyString("value"); - if (value != null) { - element.setPropertyString("value", value); - } - } -} diff --git a/client/src/com/vaadin/client/ui/VTextField.java b/client/src/com/vaadin/client/ui/VTextField.java deleted file mode 100644 index 1554bd1a22..0000000000 --- a/client/src/com/vaadin/client/ui/VTextField.java +++ /dev/null @@ -1,531 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.ui.TextBoxBase; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.WidgetUtil; -import com.vaadin.shared.EventId; -import com.vaadin.shared.ui.textfield.TextFieldConstants; - -/** - * This class represents a basic text input field with one row. - * - * @author Vaadin Ltd. - * - */ -public class VTextField extends TextBoxBase implements Field, ChangeHandler, - FocusHandler, BlurHandler, KeyDownHandler { - - /** - * The input node CSS classname. - */ - public static final String CLASSNAME = "v-textfield"; - /** - * This CSS classname is added to the input node on hover. - */ - public static final String CLASSNAME_FOCUS = "focus"; - - /** For internal use only. May be removed or replaced in the future. */ - public String paintableId; - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - public String valueBeforeEdit = null; - - /** - * Set to false if a text change event has been sent since the last value - * change event. This means that {@link #valueBeforeEdit} should not be - * trusted when determining whether a text change even should be sent. - */ - private boolean valueBeforeEditIsSynced = true; - - private boolean immediate = false; - private int maxLength = -1; - - private static final String CLASSNAME_PROMPT = "prompt"; - private static final String TEXTCHANGE_MODE_TIMEOUT = "TIMEOUT"; - - private String inputPrompt = null; - private boolean prompting = false; - private int lastCursorPos = -1; - - // used while checking if FF has set input prompt as value - private boolean possibleInputError = false; - - public VTextField() { - this(DOM.createInputText()); - } - - protected VTextField(Element node) { - super(node); - setStyleName(CLASSNAME); - addChangeHandler(this); - if (BrowserInfo.get().isIE() || BrowserInfo.get().isFirefox()) { - addKeyDownHandler(this); - } - addFocusHandler(this); - addBlurHandler(this); - } - - /** - * For internal use only. May be removed or replaced in the future. - *

- * TODO When GWT adds ONCUT, add it there and remove workaround. See - * http://code.google.com/p/google-web-toolkit/issues/detail?id=4030 - *

- * Also note that the cut/paste are not totally crossbrowsers compatible. - * E.g. in Opera mac works via context menu, but on via File->Paste/Cut. - * Opera might need the polling method for 100% working textchanceevents. - * Eager polling for a change is bit dum and heavy operation, so I guess we - * should first try to survive without. - */ - public static final int TEXTCHANGE_EVENTS = Event.ONPASTE | Event.KEYEVENTS - | Event.ONMOUSEUP; - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - - if (listenTextChangeEvents - && (event.getTypeInt() & TEXTCHANGE_EVENTS) == event - .getTypeInt()) { - deferTextChangeEvent(); - } - - } - - /* - * TODO optimize this so that only changes are sent + make the value change - * event just a flag that moves the current text to value - */ - private String lastTextChangeString = null; - - private String getLastCommunicatedString() { - return lastTextChangeString; - } - - private void communicateTextValueToServer() { - String text = getText(); - if (prompting) { - // Input prompt visible, text is actually "" - text = ""; - } - if (!text.equals(getLastCommunicatedString())) { - if (valueBeforeEditIsSynced && text.equals(valueBeforeEdit)) { - /* - * Value change for the current text has been enqueued since the - * last text change event was sent, but we can't know that it - * has been sent to the server. Ensure that all pending changes - * are sent now. Sending a value change without a text change - * will simulate a TextChangeEvent on the server. - */ - client.sendPendingVariableChanges(); - } else { - // Default case - just send an immediate text change message - client.updateVariable(paintableId, - TextFieldConstants.VAR_CUR_TEXT, text, true); - - // Shouldn't investigate valueBeforeEdit to avoid duplicate text - // change events as the states are not in sync any more - valueBeforeEditIsSynced = false; - } - lastTextChangeString = text; - } - } - - private Timer textChangeEventTrigger = new Timer() { - - @Override - public void run() { - if (isAttached()) { - updateCursorPosition(); - communicateTextValueToServer(); - scheduled = false; - } - } - }; - - private boolean scheduled = false; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean listenTextChangeEvents; - /** For internal use only. May be removed or replaced in the future. */ - public String textChangeEventMode; - public int textChangeEventTimeout; - - private void deferTextChangeEvent() { - if (textChangeEventMode.equals(TEXTCHANGE_MODE_TIMEOUT) && scheduled) { - return; - } else { - textChangeEventTrigger.cancel(); - } - textChangeEventTrigger.schedule(getTextChangeEventTimeout()); - scheduled = true; - } - - private int getTextChangeEventTimeout() { - return textChangeEventTimeout; - } - - @Override - public void setReadOnly(boolean readOnly) { - boolean wasReadOnly = isReadOnly(); - - if (readOnly) { - setTabIndex(-1); - } else if (wasReadOnly && !readOnly && getTabIndex() == -1) { - /* - * Need to manually set tab index to 0 since server will not send - * the tab index if it is 0. - */ - setTabIndex(0); - } - - super.setReadOnly(readOnly); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateFieldContent(final String text) { - setPrompting(inputPrompt != null && focusedTextField != this - && (text.equals(""))); - - String fieldValue; - if (prompting) { - fieldValue = isReadOnly() ? "" : inputPrompt; - addStyleDependentName(CLASSNAME_PROMPT); - } else { - fieldValue = text; - removeStyleDependentName(CLASSNAME_PROMPT); - } - setText(fieldValue); - - lastTextChangeString = valueBeforeEdit = text; - valueBeforeEditIsSynced = true; - } - - protected void onCut() { - if (listenTextChangeEvents) { - deferTextChangeEvent(); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public native void attachCutEventListener(Element el) - /*-{ - var me = this; - el.oncut = $entry(function() { - me.@com.vaadin.client.ui.VTextField::onCut()(); - }); - }-*/; - - protected native void detachCutEventListener(Element el) - /*-{ - el.oncut = null; - }-*/; - - private void onDrop() { - if (focusedTextField == this) { - return; - } - updateText(false); - } - - private void updateText(boolean blurred) { - String text = getText(); - setPrompting(inputPrompt != null && (text == null || text.isEmpty())); - if (prompting) { - setText(isReadOnly() ? "" : inputPrompt); - if (blurred) { - addStyleDependentName(CLASSNAME_PROMPT); - } - } - - valueChange(blurred); - } - - private void scheduleOnDropEvent() { - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - @Override - public void execute() { - onDrop(); - } - }); - } - - private native void attachDropEventListener(Element el) - /*-{ - var me = this; - el.ondrop = $entry(function() { - me.@com.vaadin.client.ui.VTextField::scheduleOnDropEvent()(); - }); - }-*/; - - private native void detachDropEventListener(Element el) - /*-{ - el.ondrop = null; - }-*/; - - @Override - protected void onDetach() { - super.onDetach(); - detachCutEventListener(getElement()); - if (focusedTextField == this) { - focusedTextField = null; - } - if (BrowserInfo.get().isFirefox()) { - removeOnInputListener(getElement()); - detachDropEventListener(getElement()); - } - } - - @Override - protected void onAttach() { - super.onAttach(); - if (listenTextChangeEvents) { - detachCutEventListener(getElement()); - } - if (BrowserInfo.get().isFirefox()) { - // Workaround for FF setting input prompt as the value if esc is - // pressed while the field is focused and empty (#8051). - addOnInputListener(getElement()); - // Workaround for FF updating component's internal value after - // having drag-and-dropped text from another element (#14056) - attachDropEventListener(getElement()); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void setMaxLength(int newMaxLength) { - if (newMaxLength == maxLength) { - return; - } - maxLength = newMaxLength; - updateMaxLength(maxLength); - } - - /** - * This method is responsible for updating the DOM or otherwise ensuring - * that the given max length is enforced. Called when the max length for the - * field has changed. - * - * @param maxLength - * The new max length - */ - protected void updateMaxLength(int maxLength) { - if (maxLength >= 0) { - getElement().setPropertyInt("maxLength", maxLength); - } else { - getElement().removeAttribute("maxLength"); - - } - setMaxLengthToElement(maxLength); - } - - protected void setMaxLengthToElement(int newMaxLength) { - if (newMaxLength >= 0) { - getElement().setPropertyInt("maxLength", newMaxLength); - } else { - getElement().removeAttribute("maxLength"); - } - } - - public int getMaxLength() { - return maxLength; - } - - @Override - public void onChange(ChangeEvent event) { - valueChange(false); - } - - /** - * Called when the field value might have changed and/or the field was - * blurred. These are combined so the blur event is sent in the same batch - * as a possible value change event (these are often connected). - * - * @param blurred - * true if the field was blurred - */ - public void valueChange(boolean blurred) { - if (client != null && paintableId != null) { - boolean sendBlurEvent = false; - boolean sendValueChange = false; - - if (blurred && client.hasEventListeners(this, EventId.BLUR)) { - sendBlurEvent = true; - client.updateVariable(paintableId, EventId.BLUR, "", false); - } - - String newText = prompting ? "" : getText(); - if (newText != null && !newText.equals(valueBeforeEdit)) { - sendValueChange = immediate; - client.updateVariable(paintableId, "text", newText, false); - valueBeforeEdit = newText; - valueBeforeEditIsSynced = true; - } - - /* - * also send cursor position, no public api yet but for easier - * extension - */ - updateCursorPosition(); - - if (sendBlurEvent || sendValueChange) { - /* - * Avoid sending text change event as we will simulate it on the - * server side before value change events. - */ - textChangeEventTrigger.cancel(); - scheduled = false; - client.sendPendingVariableChanges(); - } - } - } - - /** - * Updates the cursor position variable if it has changed since the last - * update. - * - * @return true iff the value was updated - */ - protected boolean updateCursorPosition() { - if (WidgetUtil.isAttachedAndDisplayed(this)) { - int cursorPos = getCursorPos(); - if (lastCursorPos != cursorPos) { - client.updateVariable(paintableId, - TextFieldConstants.VAR_CURSOR, cursorPos, false); - lastCursorPos = cursorPos; - return true; - } - } - return false; - } - - private static VTextField focusedTextField; - - public static void flushChangesFromFocusedTextField() { - if (focusedTextField != null) { - focusedTextField.onChange(null); - } - } - - @Override - public void onFocus(FocusEvent event) { - addStyleDependentName(CLASSNAME_FOCUS); - if (prompting) { - setText(""); - removeStyleDependentName(CLASSNAME_PROMPT); - setPrompting(false); - } - focusedTextField = this; - if (client != null && client.hasEventListeners(this, EventId.FOCUS)) { - client.updateVariable(paintableId, EventId.FOCUS, "", true); - } - } - - @Override - public void onBlur(BlurEvent event) { - // this is called twice on Chrome when e.g. changing tab while prompting - // field focused - do not change settings on the second time - if (focusedTextField != this) { - return; - } - removeStyleDependentName(CLASSNAME_FOCUS); - focusedTextField = null; - updateText(true); - } - - private void setPrompting(boolean prompting) { - this.prompting = prompting; - } - - public void setColumns(int columns) { - if (columns <= 0) { - return; - } - - setWidth(columns + "em"); - } - - @Override - public void onKeyDown(KeyDownEvent event) { - if (BrowserInfo.get().isIE() - && event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { - // IE does not send change events when pressing enter in a text - // input so we handle it using a key listener instead - valueChange(false); - } else if (BrowserInfo.get().isFirefox() - && event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE - && getText().equals("")) { - // check after onInput event if inputPrompt has appeared as the - // value of the field - possibleInputError = true; - } - } - - public void setImmediate(boolean immediate) { - this.immediate = immediate; - } - - public void setInputPrompt(String inputPrompt) { - this.inputPrompt = inputPrompt; - } - - protected boolean isWordwrap() { - String wrap = getElement().getAttribute("wrap"); - return !"off".equals(wrap); - } - - private native void addOnInputListener(Element el) - /*-{ - var self = this; - el.oninput = $entry(function() { - self.@com.vaadin.client.ui.VTextField::checkForInputError()(); - }); - }-*/; - - private native void removeOnInputListener(Element el) - /*-{ - el.oninput = null; - }-*/; - - private void checkForInputError() { - if (possibleInputError && getText().equals(inputPrompt)) { - setText(""); - } - possibleInputError = false; - } -} diff --git a/client/src/com/vaadin/client/ui/VTextualDate.java b/client/src/com/vaadin/client/ui/VTextualDate.java deleted file mode 100644 index 4d80b30f4f..0000000000 --- a/client/src/com/vaadin/client/ui/VTextualDate.java +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.Date; - -import com.google.gwt.aria.client.Roles; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.user.client.ui.TextBox; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.Focusable; -import com.vaadin.client.LocaleNotLoadedException; -import com.vaadin.client.LocaleService; -import com.vaadin.client.VConsole; -import com.vaadin.client.ui.aria.AriaHelper; -import com.vaadin.client.ui.aria.HandlesAriaCaption; -import com.vaadin.client.ui.aria.HandlesAriaInvalid; -import com.vaadin.client.ui.aria.HandlesAriaRequired; -import com.vaadin.shared.EventId; -import com.vaadin.shared.ui.datefield.Resolution; - -public class VTextualDate extends VDateField implements Field, ChangeHandler, - Focusable, SubPartAware, HandlesAriaCaption, HandlesAriaInvalid, - HandlesAriaRequired, KeyDownHandler { - - private static final String PARSE_ERROR_CLASSNAME = "-parseerror"; - - /** For internal use only. May be removed or replaced in the future. */ - public final TextBox text; - - /** For internal use only. May be removed or replaced in the future. */ - public String formatStr; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean lenient; - - private static final String CLASSNAME_PROMPT = "prompt"; - - /** For internal use only. May be removed or replaced in the future. */ - public static final String ATTR_INPUTPROMPT = "prompt"; - - /** For internal use only. May be removed or replaced in the future. */ - public String inputPrompt = ""; - - private boolean prompting = false; - - public VTextualDate() { - super(); - text = new TextBox(); - text.addChangeHandler(this); - text.addFocusHandler(new FocusHandler() { - @Override - public void onFocus(FocusEvent event) { - text.addStyleName(VTextField.CLASSNAME + "-" - + VTextField.CLASSNAME_FOCUS); - if (prompting) { - text.setText(""); - setPrompting(false); - } - if (getClient() != null - && getClient().hasEventListeners(VTextualDate.this, - EventId.FOCUS)) { - getClient() - .updateVariable(getId(), EventId.FOCUS, "", true); - } - - // Needed for tooltip event handling - VTextualDate.this.fireEvent(event); - } - }); - text.addBlurHandler(new BlurHandler() { - @Override - public void onBlur(BlurEvent event) { - text.removeStyleName(VTextField.CLASSNAME + "-" - + VTextField.CLASSNAME_FOCUS); - String value = getText(); - setPrompting(inputPrompt != null - && (value == null || "".equals(value))); - if (prompting) { - text.setText(readonly ? "" : inputPrompt); - } - if (getClient() != null - && getClient().hasEventListeners(VTextualDate.this, - EventId.BLUR)) { - getClient().updateVariable(getId(), EventId.BLUR, "", true); - } - - // Needed for tooltip event handling - VTextualDate.this.fireEvent(event); - } - }); - if (BrowserInfo.get().isIE()) { - addDomHandler(this, KeyDownEvent.getType()); - } - add(text); - } - - protected void updateStyleNames() { - if (text != null) { - text.setStyleName(VTextField.CLASSNAME); - text.addStyleName(getStylePrimaryName() + "-textfield"); - } - } - - protected String getFormatString() { - if (formatStr == null) { - if (currentResolution == Resolution.YEAR) { - formatStr = "yyyy"; // force full year - } else { - - try { - String frmString = LocaleService - .getDateFormat(currentLocale); - frmString = cleanFormat(frmString); - // String delim = LocaleService - // .getClockDelimiter(currentLocale); - if (currentResolution.getCalendarField() >= Resolution.HOUR - .getCalendarField()) { - if (dts.isTwelveHourClock()) { - frmString += " hh"; - } else { - frmString += " HH"; - } - if (currentResolution.getCalendarField() >= Resolution.MINUTE - .getCalendarField()) { - frmString += ":mm"; - if (currentResolution.getCalendarField() >= Resolution.SECOND - .getCalendarField()) { - frmString += ":ss"; - } - } - if (dts.isTwelveHourClock()) { - frmString += " aaa"; - } - - } - - formatStr = frmString; - } catch (LocaleNotLoadedException e) { - // TODO should die instead? Can the component survive - // without format string? - VConsole.error(e); - } - } - } - return formatStr; - } - - @Override - public void bindAriaCaption( - com.google.gwt.user.client.Element captionElement) { - AriaHelper.bindCaption(text, captionElement); - } - - @Override - public void setAriaRequired(boolean required) { - AriaHelper.handleInputRequired(text, required); - } - - @Override - public void setAriaInvalid(boolean invalid) { - AriaHelper.handleInputInvalid(text, invalid); - } - - /** - * Updates the text field according to the current date (provided by - * {@link #getDate()}). Takes care of updating text, enabling and disabling - * the field, setting/removing readonly status and updating readonly styles. - *

- * For internal use only. May be removed or replaced in the future. - *

- * TODO: Split part of this into a method that only updates the text as this - * is what usually is needed except for updateFromUIDL. - */ - public void buildDate() { - removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME); - // Create the initial text for the textfield - String dateText; - Date currentDate = getDate(); - if (currentDate != null) { - dateText = getDateTimeService().formatDate(currentDate, - getFormatString()); - } else { - dateText = ""; - } - - setText(dateText); - text.setEnabled(enabled); - text.setReadOnly(readonly); - - if (readonly) { - text.addStyleName("v-readonly"); - Roles.getTextboxRole().setAriaReadonlyProperty(text.getElement(), - true); - } else { - text.removeStyleName("v-readonly"); - Roles.getTextboxRole() - .removeAriaReadonlyProperty(text.getElement()); - } - - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - text.setEnabled(enabled); - } - - protected void setPrompting(boolean prompting) { - this.prompting = prompting; - if (prompting) { - addStyleDependentName(CLASSNAME_PROMPT); - } else { - removeStyleDependentName(CLASSNAME_PROMPT); - } - } - - @Override - @SuppressWarnings("deprecation") - public void onChange(ChangeEvent event) { - if (!text.getText().equals("")) { - try { - String enteredDate = text.getText(); - - setDate(getDateTimeService().parseDate(enteredDate, - getFormatString(), lenient)); - - if (lenient) { - // If date value was leniently parsed, normalize text - // presentation. - // FIXME: Add a description/example here of when this is - // needed - text.setValue( - getDateTimeService().formatDate(getDate(), - getFormatString()), false); - } - - // remove possibly added invalid value indication - removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME); - } catch (final Exception e) { - VConsole.log(e); - - addStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME); - // this is a hack that may eventually be removed - getClient().updateVariable(getId(), "lastInvalidDateString", - text.getText(), false); - setDate(null); - } - } else { - setDate(null); - // remove possibly added invalid value indication - removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME); - } - // always send the date string - getClient() - .updateVariable(getId(), "dateString", text.getText(), false); - - // Update variables - // (only the smallest defining resolution needs to be - // immediate) - Date currentDate = getDate(); - getClient().updateVariable(getId(), "year", - currentDate != null ? currentDate.getYear() + 1900 : -1, - currentResolution == Resolution.YEAR && immediate); - if (currentResolution.getCalendarField() >= Resolution.MONTH - .getCalendarField()) { - getClient().updateVariable(getId(), "month", - currentDate != null ? currentDate.getMonth() + 1 : -1, - currentResolution == Resolution.MONTH && immediate); - } - if (currentResolution.getCalendarField() >= Resolution.DAY - .getCalendarField()) { - getClient().updateVariable(getId(), "day", - currentDate != null ? currentDate.getDate() : -1, - currentResolution == Resolution.DAY && immediate); - } - if (currentResolution.getCalendarField() >= Resolution.HOUR - .getCalendarField()) { - getClient().updateVariable(getId(), "hour", - currentDate != null ? currentDate.getHours() : -1, - currentResolution == Resolution.HOUR && immediate); - } - if (currentResolution.getCalendarField() >= Resolution.MINUTE - .getCalendarField()) { - getClient().updateVariable(getId(), "min", - currentDate != null ? currentDate.getMinutes() : -1, - currentResolution == Resolution.MINUTE && immediate); - } - if (currentResolution.getCalendarField() >= Resolution.SECOND - .getCalendarField()) { - getClient().updateVariable(getId(), "sec", - currentDate != null ? currentDate.getSeconds() : -1, - currentResolution == Resolution.SECOND && immediate); - } - - } - - private String cleanFormat(String format) { - // Remove unnecessary d & M if resolution is too low - if (currentResolution.getCalendarField() < Resolution.DAY - .getCalendarField()) { - format = format.replaceAll("d", ""); - } - if (currentResolution.getCalendarField() < Resolution.MONTH - .getCalendarField()) { - format = format.replaceAll("M", ""); - } - - // Remove unsupported patterns - // TODO support for 'G', era designator (used at least in Japan) - format = format.replaceAll("[GzZwWkK]", ""); - - // Remove extra delimiters ('/' and '.') - while (format.startsWith("/") || format.startsWith(".") - || format.startsWith("-")) { - format = format.substring(1); - } - while (format.endsWith("/") || format.endsWith(".") - || format.endsWith("-")) { - format = format.substring(0, format.length() - 1); - } - - // Remove duplicate delimiters - format = format.replaceAll("//", "/"); - format = format.replaceAll("\\.\\.", "."); - format = format.replaceAll("--", "-"); - - return format.trim(); - } - - @Override - public void focus() { - text.setFocus(true); - } - - protected String getText() { - if (prompting) { - return ""; - } - return text.getText(); - } - - protected void setText(String text) { - if (inputPrompt != null - && (text == null || "".equals(text)) - && !this.text.getStyleName() - .contains( - VTextField.CLASSNAME + "-" - + VTextField.CLASSNAME_FOCUS)) { - text = readonly ? "" : inputPrompt; - setPrompting(true); - } else { - setPrompting(false); - } - - this.text.setText(text); - } - - private final String TEXTFIELD_ID = "field"; - - @Override - public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - if (subPart.equals(TEXTFIELD_ID)) { - return text.getElement(); - } - - return null; - } - - @Override - public String getSubPartName(com.google.gwt.user.client.Element subElement) { - if (text.getElement().isOrHasChild(subElement)) { - return TEXTFIELD_ID; - } - - return null; - } - - @Override - public void onKeyDown(KeyDownEvent event) { - if (BrowserInfo.get().isIE() - && event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { - // IE does not send change events when pressing enter in a text - // input so we handle it using a key listener instead - onChange(null); - } - } -} diff --git a/client/src/com/vaadin/client/ui/VTree.java b/client/src/com/vaadin/client/ui/VTree.java deleted file mode 100644 index efbafd0cb7..0000000000 --- a/client/src/com/vaadin/client/ui/VTree.java +++ /dev/null @@ -1,2262 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import com.google.gwt.aria.client.ExpandedValue; -import com.google.gwt.aria.client.Id; -import com.google.gwt.aria.client.Roles; -import com.google.gwt.aria.client.SelectedValue; -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Node; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ContextMenuEvent; -import com.google.gwt.event.dom.client.ContextMenuHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.KeyPressEvent; -import com.google.gwt.event.dom.client.KeyPressHandler; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.UIObject; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorMap; -import com.vaadin.client.MouseEventDetailsBuilder; -import com.vaadin.client.UIDL; -import com.vaadin.client.Util; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.ui.aria.AriaHelper; -import com.vaadin.client.ui.aria.HandlesAriaCaption; -import com.vaadin.client.ui.dd.DDUtil; -import com.vaadin.client.ui.dd.VAbstractDropHandler; -import com.vaadin.client.ui.dd.VAcceptCallback; -import com.vaadin.client.ui.dd.VDragAndDropManager; -import com.vaadin.client.ui.dd.VDragEvent; -import com.vaadin.client.ui.dd.VDropHandler; -import com.vaadin.client.ui.dd.VHasDropHandler; -import com.vaadin.client.ui.dd.VTransferable; -import com.vaadin.client.ui.tree.TreeConnector; -import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.MouseEventDetails.MouseButton; -import com.vaadin.shared.ui.MultiSelectMode; -import com.vaadin.shared.ui.dd.VerticalDropLocation; -import com.vaadin.shared.ui.tree.TreeConstants; - -/** - * - */ -public class VTree extends FocusElementPanel implements VHasDropHandler, - FocusHandler, BlurHandler, KeyPressHandler, KeyDownHandler, - SubPartAware, ActionOwner, HandlesAriaCaption { - private String lastNodeKey = ""; - - public static final String CLASSNAME = "v-tree"; - - /** - * @deprecated As of 7.0, use {@link MultiSelectMode#DEFAULT} instead. - */ - @Deprecated - public static final MultiSelectMode MULTISELECT_MODE_DEFAULT = MultiSelectMode.DEFAULT; - - /** - * @deprecated As of 7.0, use {@link MultiSelectMode#SIMPLE} instead. - */ - @Deprecated - public static final MultiSelectMode MULTISELECT_MODE_SIMPLE = MultiSelectMode.SIMPLE; - - private static final int CHARCODE_SPACE = 32; - - /** For internal use only. May be removed or replaced in the future. */ - public final FlowPanel body = new FlowPanel(); - - /** For internal use only. May be removed or replaced in the future. */ - public Set selectedIds = new HashSet(); - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - public String paintableId; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean selectable; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean isMultiselect; - - private String currentMouseOverKey; - - /** For internal use only. May be removed or replaced in the future. */ - public TreeNode lastSelection; - - /** For internal use only. May be removed or replaced in the future. */ - public TreeNode focusedNode; - - /** For internal use only. May be removed or replaced in the future. */ - public MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT; - - private final HashMap keyToNode = new HashMap(); - - /** - * This map contains captions and icon urls for actions like: * "33_c" -> - * "Edit" * "33_i" -> "http://dom.com/edit.png" - */ - private final HashMap actionMap = new HashMap(); - - /** For internal use only. May be removed or replaced in the future. */ - public boolean immediate; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean isNullSelectionAllowed = true; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean isHtmlContentAllowed = false; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean disabled = false; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean readonly; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean rendering; - - private VAbstractDropHandler dropHandler; - - /** For internal use only. May be removed or replaced in the future. */ - public int dragMode; - - private boolean selectionHasChanged = false; - - /* - * to fix #14388. The cause of defect #14388: event 'clickEvent' is sent to - * server before updating of "selected" variable, but should be sent after - * it - */ - private boolean clickEventPending = false; - - /** For internal use only. May be removed or replaced in the future. */ - public String[] bodyActionKeys; - - /** For internal use only. May be removed or replaced in the future. */ - public TreeConnector connector; - - public VLazyExecutor iconLoaded = new VLazyExecutor(50, - new ScheduledCommand() { - - @Override - public void execute() { - doLayout(); - } - - }); - - public VTree() { - super(); - setStyleName(CLASSNAME); - - Roles.getTreeRole().set(body.getElement()); - add(body); - - addFocusHandler(this); - addBlurHandler(this); - - /* - * Listen to context menu events on the empty space in the tree - */ - sinkEvents(Event.ONCONTEXTMENU); - addDomHandler(new ContextMenuHandler() { - @Override - public void onContextMenu(ContextMenuEvent event) { - handleBodyContextMenu(event); - } - }, ContextMenuEvent.getType()); - - /* - * Firefox auto-repeat works correctly only if we use a key press - * handler, other browsers handle it correctly when using a key down - * handler - */ - if (BrowserInfo.get().isGecko()) { - addKeyPressHandler(this); - } else { - addKeyDownHandler(this); - } - - /* - * We need to use the sinkEvents method to catch the keyUp events so we - * can cache a single shift. KeyUpHandler cannot do this. At the same - * time we catch the mouse down and up events so we can apply the text - * selection patch in IE - */ - sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONKEYUP); - - /* - * Re-set the tab index to make sure that the FocusElementPanel's - * (super) focus element gets the tab index and not the element - * containing the tree. - */ - setTabIndex(0); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user - * .client.Event) - */ - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (event.getTypeInt() == Event.ONMOUSEDOWN) { - // Prevent default text selection in IE - if (BrowserInfo.get().isIE()) { - ((Element) event.getEventTarget().cast()).setPropertyJSO( - "onselectstart", applyDisableTextSelectionIEHack()); - } - } else if (event.getTypeInt() == Event.ONMOUSEUP) { - // Remove IE text selection hack - if (BrowserInfo.get().isIE()) { - ((Element) event.getEventTarget().cast()).setPropertyJSO( - "onselectstart", null); - } - } else if (event.getTypeInt() == Event.ONKEYUP) { - if (selectionHasChanged) { - if (event.getKeyCode() == getNavigationDownKey() - && !event.getShiftKey()) { - sendSelectionToServer(); - event.preventDefault(); - } else if (event.getKeyCode() == getNavigationUpKey() - && !event.getShiftKey()) { - sendSelectionToServer(); - event.preventDefault(); - } else if (event.getKeyCode() == KeyCodes.KEY_SHIFT) { - sendSelectionToServer(); - event.preventDefault(); - } else if (event.getKeyCode() == getNavigationSelectKey()) { - sendSelectionToServer(); - event.preventDefault(); - } - } - } - } - - public String getActionCaption(String actionKey) { - return actionMap.get(actionKey + "_c"); - } - - public String getActionIcon(String actionKey) { - return actionMap.get(actionKey + "_i"); - } - - /** - * Returns the first root node of the tree or null if there are no root - * nodes. - * - * @return The first root {@link TreeNode} - */ - protected TreeNode getFirstRootNode() { - if (body.getWidgetCount() == 0) { - return null; - } - return (TreeNode) body.getWidget(0); - } - - /** - * Returns the last root node of the tree or null if there are no root - * nodes. - * - * @return The last root {@link TreeNode} - */ - protected TreeNode getLastRootNode() { - if (body.getWidgetCount() == 0) { - return null; - } - return (TreeNode) body.getWidget(body.getWidgetCount() - 1); - } - - /** - * Returns a list of all root nodes in the Tree in the order they appear in - * the tree. - * - * @return A list of all root {@link TreeNode}s. - */ - protected List getRootNodes() { - ArrayList rootNodes = new ArrayList(); - for (int i = 0; i < body.getWidgetCount(); i++) { - rootNodes.add((TreeNode) body.getWidget(i)); - } - return rootNodes; - } - - private void updateTreeRelatedDragData(VDragEvent drag) { - - currentMouseOverKey = findCurrentMouseOverKey(drag.getElementOver()); - - drag.getDropDetails().put("itemIdOver", currentMouseOverKey); - if (currentMouseOverKey != null) { - TreeNode treeNode = getNodeByKey(currentMouseOverKey); - VerticalDropLocation detail = treeNode.getDropDetail(drag - .getCurrentGwtEvent()); - Boolean overTreeNode = null; - if (treeNode != null && !treeNode.isLeaf() - && detail == VerticalDropLocation.MIDDLE) { - overTreeNode = true; - } - drag.getDropDetails().put("itemIdOverIsNode", overTreeNode); - drag.getDropDetails().put("detail", detail); - } else { - drag.getDropDetails().put("itemIdOverIsNode", null); - drag.getDropDetails().put("detail", null); - } - - } - - private String findCurrentMouseOverKey(Element elementOver) { - TreeNode treeNode = WidgetUtil.findWidget(elementOver, TreeNode.class); - return treeNode == null ? null : treeNode.key; - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateDropHandler(UIDL childUidl) { - if (dropHandler == null) { - dropHandler = new VAbstractDropHandler() { - - @Override - public void dragEnter(VDragEvent drag) { - } - - @Override - protected void dragAccepted(final VDragEvent drag) { - - } - - @Override - public void dragOver(final VDragEvent currentDrag) { - final Object oldIdOver = currentDrag.getDropDetails().get( - "itemIdOver"); - final VerticalDropLocation oldDetail = (VerticalDropLocation) currentDrag - .getDropDetails().get("detail"); - - updateTreeRelatedDragData(currentDrag); - final VerticalDropLocation detail = (VerticalDropLocation) currentDrag - .getDropDetails().get("detail"); - boolean nodeHasChanged = (currentMouseOverKey != null && currentMouseOverKey != oldIdOver) - || (currentMouseOverKey == null && oldIdOver != null); - boolean detailHasChanded = (detail != null && detail != oldDetail) - || (detail == null && oldDetail != null); - - if (nodeHasChanged || detailHasChanded) { - final String newKey = currentMouseOverKey; - TreeNode treeNode = keyToNode.get(oldIdOver); - if (treeNode != null) { - // clear old styles - treeNode.emphasis(null); - } - if (newKey != null) { - validate(new VAcceptCallback() { - @Override - public void accepted(VDragEvent event) { - VerticalDropLocation curDetail = (VerticalDropLocation) event - .getDropDetails().get("detail"); - if (curDetail == detail - && newKey.equals(currentMouseOverKey)) { - getNodeByKey(newKey).emphasis(detail); - } - /* - * Else drag is already on a different - * node-detail pair, new criteria check is - * going on - */ - } - }, currentDrag); - - } - } - - } - - @Override - public void dragLeave(VDragEvent drag) { - cleanUp(); - } - - private void cleanUp() { - if (currentMouseOverKey != null) { - getNodeByKey(currentMouseOverKey).emphasis(null); - currentMouseOverKey = null; - } - } - - @Override - public boolean drop(VDragEvent drag) { - cleanUp(); - return super.drop(drag); - } - - @Override - public ComponentConnector getConnector() { - return ConnectorMap.get(client).getConnector(VTree.this); - } - - @Override - public ApplicationConnection getApplicationConnection() { - return client; - } - - }; - } - dropHandler.updateAcceptRules(childUidl); - } - - public void setSelected(TreeNode treeNode, boolean selected) { - if (selected) { - if (!isMultiselect) { - while (selectedIds.size() > 0) { - final String id = selectedIds.iterator().next(); - final TreeNode oldSelection = getNodeByKey(id); - if (oldSelection != null) { - // can be null if the node is not visible (parent - // collapsed) - oldSelection.setSelected(false); - } - selectedIds.remove(id); - } - } - treeNode.setSelected(true); - selectedIds.add(treeNode.key); - } else { - if (!isNullSelectionAllowed) { - if (!isMultiselect || selectedIds.size() == 1) { - return; - } - } - selectedIds.remove(treeNode.key); - treeNode.setSelected(false); - } - - sendSelectionToServer(); - } - - /** - * Sends the selection to the server - */ - private void sendSelectionToServer() { - Command command = new Command() { - @Override - public void execute() { - /* - * we should send selection to server immediately in 2 cases: 1) - * 'immediate' property of Tree is true 2) clickEventPending is - * true - */ - client.updateVariable(paintableId, "selected", - selectedIds.toArray(new String[selectedIds.size()]), - clickEventPending || immediate); - clickEventPending = false; - selectionHasChanged = false; - } - }; - - /* - * Delaying the sending of the selection in webkit to ensure the - * selection is always sent when the tree has focus and after click - * events have been processed. This is due to the focusing - * implementation in FocusImplSafari which uses timeouts when focusing - * and blurring. - */ - if (BrowserInfo.get().isWebkit()) { - Scheduler.get().scheduleDeferred(command); - } else { - command.execute(); - } - } - - /** - * Is a node selected in the tree - * - * @param treeNode - * The node to check - * @return - */ - public boolean isSelected(TreeNode treeNode) { - return selectedIds.contains(treeNode.key); - } - - public class TreeNode extends SimplePanel implements ActionOwner { - - public static final String CLASSNAME = "v-tree-node"; - public static final String CLASSNAME_FOCUSED = CLASSNAME + "-focused"; - - public String key; - - /** For internal use only. May be removed or replaced in the future. */ - public String[] actionKeys = null; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean childrenLoaded; - - Element nodeCaptionDiv; - - protected Element nodeCaptionSpan; - - /** For internal use only. May be removed or replaced in the future. */ - public FlowPanel childNodeContainer; - - private boolean open; - - private Icon icon; - - private Event mouseDownEvent; - - private int cachedHeight = -1; - - private boolean focused = false; - - public TreeNode() { - constructDom(); - sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS - | Event.TOUCHEVENTS | Event.ONCONTEXTMENU); - } - - public VerticalDropLocation getDropDetail(NativeEvent currentGwtEvent) { - if (cachedHeight < 0) { - /* - * Height is cached to avoid flickering (drop hints may change - * the reported offsetheight -> would change the drop detail) - */ - cachedHeight = nodeCaptionDiv.getOffsetHeight(); - } - VerticalDropLocation verticalDropLocation = DDUtil - .getVerticalDropLocation(nodeCaptionDiv, cachedHeight, - currentGwtEvent, 0.15); - return verticalDropLocation; - } - - protected void emphasis(VerticalDropLocation detail) { - String base = "v-tree-node-drag-"; - UIObject.setStyleName(getElement(), base + "top", - VerticalDropLocation.TOP == detail); - UIObject.setStyleName(getElement(), base + "bottom", - VerticalDropLocation.BOTTOM == detail); - UIObject.setStyleName(getElement(), base + "center", - VerticalDropLocation.MIDDLE == detail); - base = "v-tree-node-caption-drag-"; - UIObject.setStyleName(nodeCaptionDiv, base + "top", - VerticalDropLocation.TOP == detail); - UIObject.setStyleName(nodeCaptionDiv, base + "bottom", - VerticalDropLocation.BOTTOM == detail); - UIObject.setStyleName(nodeCaptionDiv, base + "center", - VerticalDropLocation.MIDDLE == detail); - - // also add classname to "folder node" into which the drag is - // targeted - - TreeNode folder = null; - /* Possible parent of this TreeNode will be stored here */ - TreeNode parentFolder = getParentNode(); - - // TODO fix my bugs - if (isLeaf()) { - folder = parentFolder; - // note, parent folder may be null if this is root node => no - // folder target exists - } else { - if (detail == VerticalDropLocation.TOP) { - folder = parentFolder; - } else { - folder = this; - } - // ensure we remove the dragfolder classname from the previous - // folder node - setDragFolderStyleName(this, false); - setDragFolderStyleName(parentFolder, false); - } - if (folder != null) { - setDragFolderStyleName(folder, detail != null); - } - - } - - private TreeNode getParentNode() { - Widget parent2 = getParent().getParent(); - if (parent2 instanceof TreeNode) { - return (TreeNode) parent2; - } - return null; - } - - private void setDragFolderStyleName(TreeNode folder, boolean add) { - if (folder != null) { - UIObject.setStyleName(folder.getElement(), - "v-tree-node-dragfolder", add); - UIObject.setStyleName(folder.nodeCaptionDiv, - "v-tree-node-caption-dragfolder", add); - } - } - - /** - * Handles mouse selection - * - * @param ctrl - * Was the ctrl-key pressed - * @param shift - * Was the shift-key pressed - * @return Returns true if event was handled, else false - */ - private boolean handleClickSelection(final boolean ctrl, - final boolean shift) { - - // always when clicking an item, focus it - setFocusedNode(this, false); - - if (!BrowserInfo.get().isOpera()) { - /* - * Ensure that the tree's focus element also gains focus - * (TreeNodes focus is faked using FocusElementPanel in browsers - * other than Opera). - */ - focus(); - } - - executeEventCommand(new ScheduledCommand() { - - @Override - public void execute() { - - if (multiSelectMode == MultiSelectMode.SIMPLE - || !isMultiselect) { - toggleSelection(); - lastSelection = TreeNode.this; - } else if (multiSelectMode == MultiSelectMode.DEFAULT) { - // Handle ctrl+click - if (isMultiselect && ctrl && !shift) { - toggleSelection(); - lastSelection = TreeNode.this; - - // Handle shift+click - } else if (isMultiselect && !ctrl && shift) { - deselectAll(); - selectNodeRange(lastSelection.key, key); - sendSelectionToServer(); - - // Handle ctrl+shift click - } else if (isMultiselect && ctrl && shift) { - selectNodeRange(lastSelection.key, key); - - // Handle click - } else { - // TODO should happen only if this alone not yet - // selected, - // now sending excess server calls - deselectAll(); - toggleSelection(); - lastSelection = TreeNode.this; - } - } - } - }); - - return true; - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt - * .user.client.Event) - */ - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - final int type = DOM.eventGetType(event); - final Element target = DOM.eventGetTarget(event); - - if (type == Event.ONLOAD && icon != null && target == icon.getElement()) { - iconLoaded.trigger(); - } - - if (disabled) { - return; - } - - final boolean inCaption = isCaptionElement(target); - if (inCaption - && client.hasEventListeners(VTree.this, - TreeConstants.ITEM_CLICK_EVENT_ID) - - && (type == Event.ONDBLCLICK || type == Event.ONMOUSEUP)) { - fireClick(event); - } - if (type == Event.ONCLICK) { - if (getElement() == target) { - // state change - toggleState(); - } else if (!readonly && inCaption) { - if (selectable) { - // caption click = selection change && possible click - // event - if (handleClickSelection( - event.getCtrlKey() || event.getMetaKey(), - event.getShiftKey())) { - event.preventDefault(); - } - } else { - // Not selectable, only focus the node. - setFocusedNode(this); - } - } - event.stopPropagation(); - } else if (type == Event.ONCONTEXTMENU) { - showContextMenu(event); - } - - if (dragMode != 0 || dropHandler != null) { - if (type == Event.ONMOUSEDOWN || type == Event.ONTOUCHSTART) { - if (nodeCaptionDiv.isOrHasChild((Node) event - .getEventTarget().cast())) { - if (dragMode > 0 - && (type == Event.ONTOUCHSTART || event - .getButton() == NativeEvent.BUTTON_LEFT)) { - mouseDownEvent = event; // save event for possible - // dd operation - if (type == Event.ONMOUSEDOWN) { - event.preventDefault(); // prevent text - // selection - } else { - /* - * FIXME We prevent touch start event to be used - * as a scroll start event. Note that we cannot - * easily distinguish whether the user wants to - * drag or scroll. The same issue is in table - * that has scrollable area and has drag and - * drop enable. Some kind of timer might be used - * to resolve the issue. - */ - event.stopPropagation(); - } - } - } - } else if (type == Event.ONMOUSEMOVE - || type == Event.ONMOUSEOUT - || type == Event.ONTOUCHMOVE) { - - if (mouseDownEvent != null) { - // start actual drag on slight move when mouse is down - VTransferable t = new VTransferable(); - t.setDragSource(ConnectorMap.get(client).getConnector( - VTree.this)); - t.setData("itemId", key); - VDragEvent drag = VDragAndDropManager.get().startDrag( - t, mouseDownEvent, true); - - drag.createDragImage(nodeCaptionDiv, true); - event.stopPropagation(); - - mouseDownEvent = null; - } - } else if (type == Event.ONMOUSEUP) { - mouseDownEvent = null; - } - if (type == Event.ONMOUSEOVER) { - mouseDownEvent = null; - currentMouseOverKey = key; - event.stopPropagation(); - } - - } else if (type == Event.ONMOUSEDOWN - && event.getButton() == NativeEvent.BUTTON_LEFT) { - event.preventDefault(); // text selection - } - } - - /** - * Checks if the given element is the caption or the icon. - * - * @param target - * The element to check - * @return true if the element is the caption or the icon - */ - public boolean isCaptionElement(com.google.gwt.dom.client.Element target) { - return (target == nodeCaptionSpan || (icon != null && target == icon - .getElement())); - } - - private void fireClick(final Event evt) { - /* - * Ensure we have focus in tree before sending variables. Otherwise - * previously modified field may contain dirty variables. - */ - if (!treeHasFocus) { - if (BrowserInfo.get().isOpera()) { - if (focusedNode == null) { - getNodeByKey(key).setFocused(true); - } else { - focusedNode.setFocused(true); - } - } else { - focus(); - } - } - - final MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(evt); - - executeEventCommand(new ScheduledCommand() { - - @Override - public void execute() { - // Determine if we should send the event immediately to the - // server. We do not want to send the event if there is a - // selection event happening after this. In all other cases - // we want to send it immediately. - clickEventPending = false; - if ((details.getButton() == MouseButton.LEFT || details - .getButton() == MouseButton.MIDDLE) - && !details.isDoubleClick() && selectable) { - // Probably a selection that will cause a value change - // event to be sent - clickEventPending = true; - - // The exception is that user clicked on the - // currently selected row and null selection is not - // allowed == no selection event - if (isSelected() && selectedIds.size() == 1 - && !isNullSelectionAllowed) { - clickEventPending = false; - } - } - client.updateVariable(paintableId, "clickedKey", key, false); - client.updateVariable(paintableId, "clickEvent", - details.toString(), !clickEventPending); - } - }); - } - - /* - * Must wait for Safari to focus before sending click and value change - * events (see #6373, #6374) - */ - private void executeEventCommand(ScheduledCommand command) { - if (BrowserInfo.get().isWebkit() && !treeHasFocus) { - Scheduler.get().scheduleDeferred(command); - } else { - command.execute(); - } - } - - private void toggleSelection() { - if (selectable) { - VTree.this.setSelected(this, !isSelected()); - } - } - - private void toggleState() { - setState(!getState(), true); - } - - protected void constructDom() { - String labelId = DOM.createUniqueId(); - - addStyleName(CLASSNAME); - String treeItemId = DOM.createUniqueId(); - getElement().setId(treeItemId); - Roles.getTreeitemRole().set(getElement()); - Roles.getTreeitemRole().setAriaSelectedState(getElement(), - SelectedValue.FALSE); - Roles.getTreeitemRole().setAriaLabelledbyProperty(getElement(), - Id.of(labelId)); - - nodeCaptionDiv = DOM.createDiv(); - DOM.setElementProperty(nodeCaptionDiv, "className", CLASSNAME - + "-caption"); - Element wrapper = DOM.createDiv(); - wrapper.setId(labelId); - wrapper.setAttribute("for", treeItemId); - - nodeCaptionSpan = DOM.createSpan(); - DOM.appendChild(getElement(), nodeCaptionDiv); - DOM.appendChild(nodeCaptionDiv, wrapper); - DOM.appendChild(wrapper, nodeCaptionSpan); - - if (BrowserInfo.get().isOpera()) { - /* - * Focus the caption div of the node to get keyboard navigation - * to work without scrolling up or down when focusing a node. - */ - nodeCaptionDiv.setTabIndex(-1); - } - - childNodeContainer = new FlowPanel(); - childNodeContainer.setStyleName(CLASSNAME + "-children"); - Roles.getGroupRole().set(childNodeContainer.getElement()); - setWidget(childNodeContainer); - } - - public boolean isLeaf() { - String[] styleNames = getStyleName().split(" "); - for (String styleName : styleNames) { - if (styleName.equals(CLASSNAME + "-leaf")) { - return true; - } - } - return false; - } - - /** For internal use only. May be removed or replaced in the future. */ - public void setState(boolean state, boolean notifyServer) { - if (open == state) { - return; - } - if (state) { - if (!childrenLoaded && notifyServer) { - client.updateVariable(paintableId, "requestChildTree", - true, false); - } - if (notifyServer) { - client.updateVariable(paintableId, "expand", - new String[] { key }, true); - } - addStyleName(CLASSNAME + "-expanded"); - Roles.getTreeitemRole().setAriaExpandedState(getElement(), - ExpandedValue.TRUE); - childNodeContainer.setVisible(true); - } else { - removeStyleName(CLASSNAME + "-expanded"); - Roles.getTreeitemRole().setAriaExpandedState(getElement(), - ExpandedValue.FALSE); - childNodeContainer.setVisible(false); - if (notifyServer) { - client.updateVariable(paintableId, "collapse", - new String[] { key }, true); - } - } - open = state; - - if (!rendering) { - doLayout(); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public boolean getState() { - return open; - } - - /** For internal use only. May be removed or replaced in the future. */ - public void setText(String text) { - DOM.setInnerText(nodeCaptionSpan, text); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void setHtml(String html) { - nodeCaptionSpan.setInnerHTML(html); - } - - public boolean isChildrenLoaded() { - return childrenLoaded; - } - - /** - * Returns the children of the node - * - * @return A set of tree nodes - */ - public List getChildren() { - List nodes = new LinkedList(); - - if (!isLeaf() && isChildrenLoaded()) { - Iterator iter = childNodeContainer.iterator(); - while (iter.hasNext()) { - TreeNode node = (TreeNode) iter.next(); - nodes.add(node); - } - } - return nodes; - } - - @Override - public Action[] getActions() { - if (actionKeys == null) { - return new Action[] {}; - } - final Action[] actions = new Action[actionKeys.length]; - for (int i = 0; i < actions.length; i++) { - final String actionKey = actionKeys[i]; - final TreeAction a = new TreeAction(this, String.valueOf(key), - actionKey); - a.setCaption(getActionCaption(actionKey)); - a.setIconUrl(getActionIcon(actionKey)); - actions[i] = a; - } - return actions; - } - - @Override - public ApplicationConnection getClient() { - return client; - } - - @Override - public String getPaintableId() { - return paintableId; - } - - /** - * Adds/removes Vaadin specific style name. - *

- * For internal use only. May be removed or replaced in the future. - * - * @param selected - */ - public void setSelected(boolean selected) { - // add style name to caption dom structure only, not to subtree - setStyleName(nodeCaptionDiv, "v-tree-node-selected", selected); - } - - protected boolean isSelected() { - return VTree.this.isSelected(this); - } - - /** - * Travels up the hierarchy looking for this node - * - * @param child - * The child which grandparent this is or is not - * @return True if this is a grandparent of the child node - */ - public boolean isGrandParentOf(TreeNode child) { - TreeNode currentNode = child; - boolean isGrandParent = false; - while (currentNode != null) { - currentNode = currentNode.getParentNode(); - if (currentNode == this) { - isGrandParent = true; - break; - } - } - return isGrandParent; - } - - public boolean isSibling(TreeNode node) { - return node.getParentNode() == getParentNode(); - } - - public void showContextMenu(Event event) { - if (!readonly && !disabled) { - if (actionKeys != null) { - int left = event.getClientX(); - int top = event.getClientY(); - top += Window.getScrollTop(); - left += Window.getScrollLeft(); - client.getContextMenu().showAt(this, left, top); - - event.stopPropagation(); - event.preventDefault(); - } - } - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.Widget#onDetach() - */ - @Override - protected void onDetach() { - super.onDetach(); - client.getContextMenu().ensureHidden(this); - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.UIObject#toString() - */ - @Override - public String toString() { - return nodeCaptionSpan.getInnerText(); - } - - /** - * Is the node focused? - * - * @param focused - * True if focused, false if not - */ - public void setFocused(boolean focused) { - if (!this.focused && focused) { - nodeCaptionDiv.addClassName(CLASSNAME_FOCUSED); - - this.focused = focused; - if (BrowserInfo.get().isOpera()) { - nodeCaptionDiv.focus(); - } - treeHasFocus = true; - } else if (this.focused && !focused) { - nodeCaptionDiv.removeClassName(CLASSNAME_FOCUSED); - this.focused = focused; - treeHasFocus = false; - } - } - - /** - * Scrolls the caption into view - */ - public void scrollIntoView() { - WidgetUtil.scrollIntoViewVertically(nodeCaptionDiv); - } - - public void setIcon(String iconUrl, String altText) { - if (icon != null) { - DOM.getFirstChild(nodeCaptionDiv) - .removeChild(icon.getElement()); - } - icon = client.getIcon(iconUrl); - if (icon != null) { - DOM.insertBefore(DOM.getFirstChild(nodeCaptionDiv), - icon.getElement(), nodeCaptionSpan); - icon.setAlternateText(altText); - } - } - - public void setNodeStyleName(String styleName) { - addStyleName(TreeNode.CLASSNAME + "-" + styleName); - setStyleName(nodeCaptionDiv, TreeNode.CLASSNAME + "-caption-" - + styleName, true); - childNodeContainer.addStyleName(TreeNode.CLASSNAME + "-children-" - + styleName); - - } - - } - - @Override - public VDropHandler getDropHandler() { - return dropHandler; - } - - public TreeNode getNodeByKey(String key) { - return keyToNode.get(key); - } - - /** - * Deselects all items in the tree - */ - public void deselectAll() { - for (String key : selectedIds) { - TreeNode node = keyToNode.get(key); - if (node != null) { - node.setSelected(false); - } - } - selectedIds.clear(); - selectionHasChanged = true; - } - - /** - * Selects a range of nodes - * - * @param startNodeKey - * The start node key - * @param endNodeKey - * The end node key - */ - private void selectNodeRange(String startNodeKey, String endNodeKey) { - - TreeNode startNode = keyToNode.get(startNodeKey); - TreeNode endNode = keyToNode.get(endNodeKey); - - // The nodes have the same parent - if (startNode.getParent() == endNode.getParent()) { - doSiblingSelection(startNode, endNode); - - // The start node is a grandparent of the end node - } else if (startNode.isGrandParentOf(endNode)) { - doRelationSelection(startNode, endNode); - - // The end node is a grandparent of the start node - } else if (endNode.isGrandParentOf(startNode)) { - doRelationSelection(endNode, startNode); - - } else { - doNoRelationSelection(startNode, endNode); - } - } - - /** - * Selects a node and deselect all other nodes - * - * @param node - * The node to select - */ - private void selectNode(TreeNode node, boolean deselectPrevious) { - if (deselectPrevious) { - deselectAll(); - } - - if (node != null) { - node.setSelected(true); - selectedIds.add(node.key); - lastSelection = node; - } - selectionHasChanged = true; - } - - /** - * Deselects a node - * - * @param node - * The node to deselect - */ - private void deselectNode(TreeNode node) { - node.setSelected(false); - selectedIds.remove(node.key); - selectionHasChanged = true; - } - - /** - * Selects all the open children to a node - * - * @param node - * The parent node - */ - private void selectAllChildren(TreeNode node, boolean includeRootNode) { - if (includeRootNode) { - node.setSelected(true); - selectedIds.add(node.key); - } - - for (TreeNode child : node.getChildren()) { - if (!child.isLeaf() && child.getState()) { - selectAllChildren(child, true); - } else { - child.setSelected(true); - selectedIds.add(child.key); - } - } - selectionHasChanged = true; - } - - /** - * Selects all children until a stop child is reached - * - * @param root - * The root not to start from - * @param stopNode - * The node to finish with - * @param includeRootNode - * Should the root node be selected - * @param includeStopNode - * Should the stop node be selected - * - * @return Returns false if the stop child was found, else true if all - * children was selected - */ - private boolean selectAllChildrenUntil(TreeNode root, TreeNode stopNode, - boolean includeRootNode, boolean includeStopNode) { - if (includeRootNode) { - root.setSelected(true); - selectedIds.add(root.key); - } - if (root.getState() && root != stopNode) { - for (TreeNode child : root.getChildren()) { - if (!child.isLeaf() && child.getState() && child != stopNode) { - if (!selectAllChildrenUntil(child, stopNode, true, - includeStopNode)) { - return false; - } - } else if (child == stopNode) { - if (includeStopNode) { - child.setSelected(true); - selectedIds.add(child.key); - } - return false; - } else { - child.setSelected(true); - selectedIds.add(child.key); - } - } - } - selectionHasChanged = true; - - return true; - } - - /** - * Select a range between two nodes which have no relation to each other - * - * @param startNode - * The start node to start the selection from - * @param endNode - * The end node to end the selection to - */ - private void doNoRelationSelection(TreeNode startNode, TreeNode endNode) { - - TreeNode commonParent = getCommonGrandParent(startNode, endNode); - TreeNode startBranch = null, endBranch = null; - - // Find the children of the common parent - List children; - if (commonParent != null) { - children = commonParent.getChildren(); - } else { - children = getRootNodes(); - } - - // Find the start and end branches - for (TreeNode node : children) { - if (nodeIsInBranch(startNode, node)) { - startBranch = node; - } - if (nodeIsInBranch(endNode, node)) { - endBranch = node; - } - } - - // Swap nodes if necessary - if (children.indexOf(startBranch) > children.indexOf(endBranch)) { - TreeNode temp = startBranch; - startBranch = endBranch; - endBranch = temp; - - temp = startNode; - startNode = endNode; - endNode = temp; - } - - // Select all children under the start node - selectAllChildren(startNode, true); - TreeNode startParent = startNode.getParentNode(); - TreeNode currentNode = startNode; - while (startParent != null && startParent != commonParent) { - List startChildren = startParent.getChildren(); - for (int i = startChildren.indexOf(currentNode) + 1; i < startChildren - .size(); i++) { - selectAllChildren(startChildren.get(i), true); - } - - currentNode = startParent; - startParent = startParent.getParentNode(); - } - - // Select nodes until the end node is reached - for (int i = children.indexOf(startBranch) + 1; i <= children - .indexOf(endBranch); i++) { - selectAllChildrenUntil(children.get(i), endNode, true, true); - } - - // Ensure end node was selected - endNode.setSelected(true); - selectedIds.add(endNode.key); - selectionHasChanged = true; - } - - /** - * Examines the children of the branch node and returns true if a node is in - * that branch - * - * @param node - * The node to search for - * @param branch - * The branch to search in - * @return True if found, false if not found - */ - private boolean nodeIsInBranch(TreeNode node, TreeNode branch) { - if (node == branch) { - return true; - } - for (TreeNode child : branch.getChildren()) { - if (child == node) { - return true; - } - if (!child.isLeaf() && child.getState()) { - if (nodeIsInBranch(node, child)) { - return true; - } - } - } - return false; - } - - /** - * Selects a range of items which are in direct relation with each other.
- * NOTE: The start node MUST be before the end node! - * - * @param startNode - * - * @param endNode - */ - private void doRelationSelection(TreeNode startNode, TreeNode endNode) { - TreeNode currentNode = endNode; - while (currentNode != startNode) { - currentNode.setSelected(true); - selectedIds.add(currentNode.key); - - // Traverse children above the selection - List subChildren = currentNode.getParentNode() - .getChildren(); - if (subChildren.size() > 1) { - selectNodeRange(subChildren.iterator().next().key, - currentNode.key); - } else if (subChildren.size() == 1) { - TreeNode n = subChildren.get(0); - n.setSelected(true); - selectedIds.add(n.key); - } - - currentNode = currentNode.getParentNode(); - } - startNode.setSelected(true); - selectedIds.add(startNode.key); - selectionHasChanged = true; - } - - /** - * Selects a range of items which have the same parent. - * - * @param startNode - * The start node - * @param endNode - * The end node - */ - private void doSiblingSelection(TreeNode startNode, TreeNode endNode) { - TreeNode parent = startNode.getParentNode(); - - List children; - if (parent == null) { - // Topmost parent - children = getRootNodes(); - } else { - children = parent.getChildren(); - } - - // Swap start and end point if needed - if (children.indexOf(startNode) > children.indexOf(endNode)) { - TreeNode temp = startNode; - startNode = endNode; - endNode = temp; - } - - Iterator childIter = children.iterator(); - boolean startFound = false; - while (childIter.hasNext()) { - TreeNode node = childIter.next(); - if (node == startNode) { - startFound = true; - } - - if (startFound && node != endNode && node.getState()) { - selectAllChildren(node, true); - } else if (startFound && node != endNode) { - node.setSelected(true); - selectedIds.add(node.key); - } - - if (node == endNode) { - node.setSelected(true); - selectedIds.add(node.key); - break; - } - } - selectionHasChanged = true; - } - - /** - * Returns the first common parent of two nodes - * - * @param node1 - * The first node - * @param node2 - * The second node - * @return The common parent or null - */ - public TreeNode getCommonGrandParent(TreeNode node1, TreeNode node2) { - // If either one does not have a grand parent then return null - if (node1.getParentNode() == null || node2.getParentNode() == null) { - return null; - } - - // If the nodes are parents of each other then return null - if (node1.isGrandParentOf(node2) || node2.isGrandParentOf(node1)) { - return null; - } - - // Get parents of node1 - List parents1 = new ArrayList(); - TreeNode parent1 = node1.getParentNode(); - while (parent1 != null) { - parents1.add(parent1); - parent1 = parent1.getParentNode(); - } - - // Get parents of node2 - List parents2 = new ArrayList(); - TreeNode parent2 = node2.getParentNode(); - while (parent2 != null) { - parents2.add(parent2); - parent2 = parent2.getParentNode(); - } - - // Search the parents for the first common parent - for (int i = 0; i < parents1.size(); i++) { - parent1 = parents1.get(i); - for (int j = 0; j < parents2.size(); j++) { - parent2 = parents2.get(j); - if (parent1 == parent2) { - return parent1; - } - } - } - - return null; - } - - /** - * Sets the node currently in focus - * - * @param node - * The node to focus or null to remove the focus completely - * @param scrollIntoView - * Scroll the node into view - */ - public void setFocusedNode(TreeNode node, boolean scrollIntoView) { - // Unfocus previously focused node - if (focusedNode != null) { - focusedNode.setFocused(false); - - Roles.getTreeRole().removeAriaActivedescendantProperty( - focusedNode.getElement()); - } - - if (node != null) { - node.setFocused(true); - Roles.getTreeitemRole().setAriaSelectedState(node.getElement(), - SelectedValue.TRUE); - - /* - * FIXME: This code needs to be changed when the keyboard navigation - * doesn't immediately trigger a selection change anymore. - * - * Right now this function is called before and after the Tree is - * rebuilt when up/down arrow keys are pressed. This leads to the - * problem, that the newly selected item is announced too often with - * a screen reader. - * - * Behaviour is different when using the Tree with and without - * screen reader. - */ - if (node.key.equals(lastNodeKey)) { - Roles.getTreeRole().setAriaActivedescendantProperty( - getFocusElement(), Id.of(node.getElement())); - } else { - lastNodeKey = node.key; - } - } - - focusedNode = node; - - if (node != null && scrollIntoView) { - /* - * Delay scrolling the focused node into view if we are still - * rendering. #5396 - */ - if (!rendering) { - node.scrollIntoView(); - } else { - Scheduler.get().scheduleDeferred(new Command() { - @Override - public void execute() { - focusedNode.scrollIntoView(); - } - }); - } - } - } - - /** - * Focuses a node and scrolls it into view - * - * @param node - * The node to focus - */ - public void setFocusedNode(TreeNode node) { - setFocusedNode(node, true); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event - * .dom.client.FocusEvent) - */ - @Override - public void onFocus(FocusEvent event) { - treeHasFocus = true; - // If no node has focus, focus the first item in the tree - if (focusedNode == null && lastSelection == null && selectable) { - setFocusedNode(getFirstRootNode(), false); - } else if (focusedNode != null && selectable) { - setFocusedNode(focusedNode, false); - } else if (lastSelection != null && selectable) { - setFocusedNode(lastSelection, false); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event - * .dom.client.BlurEvent) - */ - @Override - public void onBlur(BlurEvent event) { - treeHasFocus = false; - if (focusedNode != null) { - focusedNode.setFocused(false); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google - * .gwt.event.dom.client.KeyPressEvent) - */ - @Override - public void onKeyPress(KeyPressEvent event) { - NativeEvent nativeEvent = event.getNativeEvent(); - int keyCode = nativeEvent.getKeyCode(); - if (keyCode == 0 && nativeEvent.getCharCode() == ' ') { - // Provide a keyCode for space to be compatible with FireFox - // keypress event - keyCode = CHARCODE_SPACE; - } - if (handleKeyNavigation(keyCode, - event.isControlKeyDown() || event.isMetaKeyDown(), - event.isShiftKeyDown())) { - event.preventDefault(); - event.stopPropagation(); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt - * .event.dom.client.KeyDownEvent) - */ - @Override - public void onKeyDown(KeyDownEvent event) { - if (handleKeyNavigation(event.getNativeEvent().getKeyCode(), - event.isControlKeyDown() || event.isMetaKeyDown(), - event.isShiftKeyDown())) { - event.preventDefault(); - event.stopPropagation(); - } - } - - /** - * Handles the keyboard navigation - * - * @param keycode - * The keycode of the pressed key - * @param ctrl - * Was ctrl pressed - * @param shift - * Was shift pressed - * @return Returns true if the key was handled, else false - */ - protected boolean handleKeyNavigation(int keycode, boolean ctrl, - boolean shift) { - // Navigate down - if (keycode == getNavigationDownKey()) { - TreeNode node = null; - // If node is open and has children then move in to the children - if (!focusedNode.isLeaf() && focusedNode.getState() - && focusedNode.getChildren().size() > 0) { - node = focusedNode.getChildren().get(0); - } - - // Else move down to the next sibling - else { - node = getNextSibling(focusedNode); - if (node == null) { - // Else jump to the parent and try to select the next - // sibling there - TreeNode current = focusedNode; - while (node == null && current.getParentNode() != null) { - node = getNextSibling(current.getParentNode()); - current = current.getParentNode(); - } - } - } - - if (node != null) { - setFocusedNode(node); - if (selectable) { - if (!ctrl && !shift) { - selectNode(node, true); - } else if (shift && isMultiselect) { - deselectAll(); - selectNodeRange(lastSelection.key, node.key); - } else if (shift) { - selectNode(node, true); - } - } - showTooltipForKeyboardNavigation(node); - } - return true; - } - - // Navigate up - if (keycode == getNavigationUpKey()) { - TreeNode prev = getPreviousSibling(focusedNode); - TreeNode node = null; - if (prev != null) { - node = getLastVisibleChildInTree(prev); - } else if (focusedNode.getParentNode() != null) { - node = focusedNode.getParentNode(); - } - if (node != null) { - setFocusedNode(node); - if (selectable) { - if (!ctrl && !shift) { - selectNode(node, true); - } else if (shift && isMultiselect) { - deselectAll(); - selectNodeRange(lastSelection.key, node.key); - } else if (shift) { - selectNode(node, true); - } - } - showTooltipForKeyboardNavigation(node); - } - return true; - } - - // Navigate left (close branch) - if (keycode == getNavigationLeftKey()) { - if (!focusedNode.isLeaf() && focusedNode.getState()) { - focusedNode.setState(false, true); - } else if (focusedNode.getParentNode() != null - && (focusedNode.isLeaf() || !focusedNode.getState())) { - - if (ctrl || !selectable) { - setFocusedNode(focusedNode.getParentNode()); - } else if (shift) { - doRelationSelection(focusedNode.getParentNode(), - focusedNode); - setFocusedNode(focusedNode.getParentNode()); - } else { - focusAndSelectNode(focusedNode.getParentNode()); - } - } - showTooltipForKeyboardNavigation(focusedNode); - return true; - } - - // Navigate right (open branch) - if (keycode == getNavigationRightKey()) { - if (!focusedNode.isLeaf() && !focusedNode.getState()) { - focusedNode.setState(true, true); - } else if (!focusedNode.isLeaf()) { - if (ctrl || !selectable) { - setFocusedNode(focusedNode.getChildren().get(0)); - } else if (shift) { - setSelected(focusedNode, true); - setFocusedNode(focusedNode.getChildren().get(0)); - setSelected(focusedNode, true); - } else { - focusAndSelectNode(focusedNode.getChildren().get(0)); - } - } - showTooltipForKeyboardNavigation(focusedNode); - return true; - } - - // Selection - if (keycode == getNavigationSelectKey()) { - if (!focusedNode.isSelected()) { - selectNode( - focusedNode, - (!isMultiselect || multiSelectMode == MULTISELECT_MODE_SIMPLE) - && selectable); - } else { - deselectNode(focusedNode); - } - return true; - } - - // Home selection - if (keycode == getNavigationStartKey()) { - TreeNode node = getFirstRootNode(); - if (ctrl || !selectable) { - setFocusedNode(node); - } else if (shift) { - deselectAll(); - selectNodeRange(focusedNode.key, node.key); - } else { - selectNode(node, true); - } - sendSelectionToServer(); - showTooltipForKeyboardNavigation(node); - return true; - } - - // End selection - if (keycode == getNavigationEndKey()) { - TreeNode lastNode = getLastRootNode(); - TreeNode node = getLastVisibleChildInTree(lastNode); - if (ctrl || !selectable) { - setFocusedNode(node); - } else if (shift) { - deselectAll(); - selectNodeRange(focusedNode.key, node.key); - } else { - selectNode(node, true); - } - sendSelectionToServer(); - showTooltipForKeyboardNavigation(node); - return true; - } - - return false; - } - - private void showTooltipForKeyboardNavigation(TreeNode node) { - if (connector != null) { - getClient().getVTooltip().showAssistive( - connector.getTooltipInfo(node.nodeCaptionSpan)); - } - } - - private void focusAndSelectNode(TreeNode node) { - /* - * Keyboard navigation doesn't work reliably if the tree is in - * multiselect mode as well as isNullSelectionAllowed = false. It first - * tries to deselect the old focused node, which fails since there must - * be at least one selection. After this the newly focused node is - * selected and we've ended up with two selected nodes even though we - * only navigated with the arrow keys. - * - * Because of this, we first select the next node and later de-select - * the old one. - */ - TreeNode oldFocusedNode = focusedNode; - setFocusedNode(node); - setSelected(focusedNode, true); - setSelected(oldFocusedNode, false); - } - - /** - * Traverses the tree to the bottom most child - * - * @param root - * The root of the tree - * @return The bottom most child - */ - private TreeNode getLastVisibleChildInTree(TreeNode root) { - if (root.isLeaf() || !root.getState() || root.getChildren().size() == 0) { - return root; - } - List children = root.getChildren(); - return getLastVisibleChildInTree(children.get(children.size() - 1)); - } - - /** - * Gets the next sibling in the tree - * - * @param node - * The node to get the sibling for - * @return The sibling node or null if the node is the last sibling - */ - private TreeNode getNextSibling(TreeNode node) { - TreeNode parent = node.getParentNode(); - List children; - if (parent == null) { - children = getRootNodes(); - } else { - children = parent.getChildren(); - } - - int idx = children.indexOf(node); - if (idx < children.size() - 1) { - return children.get(idx + 1); - } - - return null; - } - - /** - * Returns the previous sibling in the tree - * - * @param node - * The node to get the sibling for - * @return The sibling node or null if the node is the first sibling - */ - private TreeNode getPreviousSibling(TreeNode node) { - TreeNode parent = node.getParentNode(); - List children; - if (parent == null) { - children = getRootNodes(); - } else { - children = parent.getChildren(); - } - - int idx = children.indexOf(node); - if (idx > 0) { - return children.get(idx - 1); - } - - return null; - } - - /** - * Add this to the element mouse down event by using element.setPropertyJSO - * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again - * when the mouse is depressed in the mouse up event. - * - * @return Returns the JSO preventing text selection - */ - private native JavaScriptObject applyDisableTextSelectionIEHack() - /*-{ - return function(){ return false; }; - }-*/; - - /** - * Get the key that moves the selection head upwards. By default it is the - * up arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationUpKey() { - return KeyCodes.KEY_UP; - } - - /** - * Get the key that moves the selection head downwards. By default it is the - * down arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationDownKey() { - return KeyCodes.KEY_DOWN; - } - - /** - * Get the key that scrolls to the left in the table. By default it is the - * left arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationLeftKey() { - return KeyCodes.KEY_LEFT; - } - - /** - * Get the key that scroll to the right on the table. By default it is the - * right arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationRightKey() { - return KeyCodes.KEY_RIGHT; - } - - /** - * Get the key that selects an item in the table. By default it is the space - * bar key but by overriding this you can change the key to whatever you - * want. - * - * @return - */ - protected int getNavigationSelectKey() { - return CHARCODE_SPACE; - } - - /** - * Get the key the moves the selection one page up in the table. By default - * this is the Page Up key but by overriding this you can change the key to - * whatever you want. - * - * @return - */ - protected int getNavigationPageUpKey() { - return KeyCodes.KEY_PAGEUP; - } - - /** - * Get the key the moves the selection one page down in the table. By - * default this is the Page Down key but by overriding this you can change - * the key to whatever you want. - * - * @return - */ - protected int getNavigationPageDownKey() { - return KeyCodes.KEY_PAGEDOWN; - } - - /** - * Get the key the moves the selection to the beginning of the table. By - * default this is the Home key but by overriding this you can change the - * key to whatever you want. - * - * @return - */ - protected int getNavigationStartKey() { - return KeyCodes.KEY_HOME; - } - - /** - * Get the key the moves the selection to the end of the table. By default - * this is the End key but by overriding this you can change the key to - * whatever you want. - * - * @return - */ - protected int getNavigationEndKey() { - return KeyCodes.KEY_END; - } - - private final String SUBPART_NODE_PREFIX = "n"; - private final String EXPAND_IDENTIFIER = "expand"; - - /* - * In webkit, focus may have been requested for this component but not yet - * gained. Use this to trac if tree has gained the focus on webkit. See - * FocusImplSafari and #6373 - */ - private boolean treeHasFocus; - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.SubPartAware#getSubPartElement(java - * .lang.String) - */ - @Override - public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - if ("fe".equals(subPart)) { - if (BrowserInfo.get().isOpera() && focusedNode != null) { - return focusedNode.getElement(); - } - return getFocusElement(); - } - - if (subPart.startsWith(SUBPART_NODE_PREFIX + "[")) { - boolean expandCollapse = false; - - // Node - String[] nodes = subPart.split("/"); - TreeNode treeNode = null; - try { - for (String node : nodes) { - if (node.startsWith(SUBPART_NODE_PREFIX)) { - - // skip SUBPART_NODE_PREFIX"[" - node = node.substring(SUBPART_NODE_PREFIX.length() + 1); - // skip "]" - node = node.substring(0, node.length() - 1); - int position = Integer.parseInt(node); - if (treeNode == null) { - treeNode = getRootNodes().get(position); - } else { - treeNode = treeNode.getChildren().get(position); - } - } else if (node.startsWith(EXPAND_IDENTIFIER)) { - expandCollapse = true; - } - } - - if (expandCollapse) { - return treeNode.getElement(); - } else { - return DOM.asOld(treeNode.nodeCaptionSpan); - } - } catch (Exception e) { - // Invalid locator string or node could not be found - return null; - } - } - return null; - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.SubPartAware#getSubPartName(com.google - * .gwt.user.client.Element) - */ - @Override - public String getSubPartName(com.google.gwt.user.client.Element subElement) { - // Supported identifiers: - // - // n[index]/n[index]/n[index]{/expand} - // - // Ends with "/expand" if the target is expand/collapse indicator, - // otherwise ends with the node - - boolean isExpandCollapse = false; - - if (!getElement().isOrHasChild(subElement)) { - return null; - } - - if (subElement == getFocusElement()) { - return "fe"; - } - - TreeNode treeNode = WidgetUtil.findWidget(subElement, TreeNode.class); - if (treeNode == null) { - // Did not click on a node, let somebody else take care of the - // locator string - return null; - } - - if (subElement == treeNode.getElement()) { - // Targets expand/collapse arrow - isExpandCollapse = true; - } - - ArrayList positions = new ArrayList(); - while (treeNode.getParentNode() != null) { - positions.add(0, - treeNode.getParentNode().getChildren().indexOf(treeNode)); - treeNode = treeNode.getParentNode(); - } - positions.add(0, getRootNodes().indexOf(treeNode)); - - String locator = ""; - for (Integer i : positions) { - locator += SUBPART_NODE_PREFIX + "[" + i + "]/"; - } - - locator = locator.substring(0, locator.length() - 1); - if (isExpandCollapse) { - locator += "/" + EXPAND_IDENTIFIER; - } - return locator; - } - - @Override - public Action[] getActions() { - if (bodyActionKeys == null) { - return new Action[] {}; - } - final Action[] actions = new Action[bodyActionKeys.length]; - for (int i = 0; i < actions.length; i++) { - final String actionKey = bodyActionKeys[i]; - final TreeAction a = new TreeAction(this, null, actionKey); - a.setCaption(getActionCaption(actionKey)); - a.setIconUrl(getActionIcon(actionKey)); - actions[i] = a; - } - return actions; - } - - @Override - public ApplicationConnection getClient() { - return client; - } - - @Override - public String getPaintableId() { - return paintableId; - } - - private void handleBodyContextMenu(ContextMenuEvent event) { - if (!readonly && !disabled) { - if (bodyActionKeys != null) { - int left = event.getNativeEvent().getClientX(); - int top = event.getNativeEvent().getClientY(); - top += Window.getScrollTop(); - left += Window.getScrollLeft(); - client.getContextMenu().showAt(this, left, top); - } - event.stopPropagation(); - event.preventDefault(); - } - } - - public void registerAction(String key, String caption, String iconUrl) { - actionMap.put(key + "_c", caption); - if (iconUrl != null) { - actionMap.put(key + "_i", iconUrl); - } else { - actionMap.remove(key + "_i"); - } - - } - - public void registerNode(TreeNode treeNode) { - keyToNode.put(treeNode.key, treeNode); - } - - public void clearNodeToKeyMap() { - keyToNode.clear(); - } - - @Override - public void bindAriaCaption( - com.google.gwt.user.client.Element captionElement) { - AriaHelper.bindCaption(body, captionElement); - } - - /** - * Tell LayoutManager that a layout is needed later for this VTree - */ - private void doLayout() { - // IE8 needs a hack to measure the tree again after update - WidgetUtil.forceIE8Redraw(getElement()); - - // This calls LayoutManager setNeedsMeasure and layoutNow - Util.notifyParentOfSizeChange(this, false); - } -} diff --git a/client/src/com/vaadin/client/ui/VTreeTable.java b/client/src/com/vaadin/client/ui/VTreeTable.java deleted file mode 100644 index 0ba84af4bb..0000000000 --- a/client/src/com/vaadin/client/ui/VTreeTable.java +++ /dev/null @@ -1,904 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; - -import com.google.gwt.animation.client.Animation; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.SpanElement; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.Style.Visibility; -import com.google.gwt.dom.client.TableCellElement; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ComputedStyle; -import com.vaadin.client.UIDL; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.ui.VTreeTable.VTreeTableScrollBody.VTreeTableRow; - -public class VTreeTable extends VScrollTable { - - /** For internal use only. May be removed or replaced in the future. */ - public static class PendingNavigationEvent { - public final int keycode; - public final boolean ctrl; - public final boolean shift; - - public PendingNavigationEvent(int keycode, boolean ctrl, boolean shift) { - this.keycode = keycode; - this.ctrl = ctrl; - this.shift = shift; - } - - @Override - public String toString() { - String string = "Keyboard event: " + keycode; - if (ctrl) { - string += " + ctrl"; - } - if (shift) { - string += " + shift"; - } - return string; - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public boolean collapseRequest; - - private boolean selectionPending; - - /** For internal use only. May be removed or replaced in the future. */ - public int colIndexOfHierarchy; - - /** For internal use only. May be removed or replaced in the future. */ - public String collapsedRowKey; - - /** For internal use only. May be removed or replaced in the future. */ - public VTreeTableScrollBody scrollBody; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean animationsEnabled; - - /** For internal use only. May be removed or replaced in the future. */ - public LinkedList pendingNavigationEvents = new LinkedList(); - - /** For internal use only. May be removed or replaced in the future. */ - public boolean focusParentResponsePending; - - @Override - protected VScrollTableBody createScrollBody() { - scrollBody = new VTreeTableScrollBody(); - return scrollBody; - } - - /* - * Overridden to allow animation of expands and collapses of nodes. - */ - @Override - public void addAndRemoveRows(UIDL partialRowAdditions) { - if (partialRowAdditions == null) { - return; - } - - if (animationsEnabled) { - if (partialRowAdditions.hasAttribute("hide")) { - scrollBody.unlinkRowsAnimatedAndUpdateCacheWhenFinished( - partialRowAdditions.getIntAttribute("firstprowix"), - partialRowAdditions.getIntAttribute("numprows")); - } else { - scrollBody.insertRowsAnimated(partialRowAdditions, - partialRowAdditions.getIntAttribute("firstprowix"), - partialRowAdditions.getIntAttribute("numprows")); - discardRowsOutsideCacheWindow(); - } - } else { - super.addAndRemoveRows(partialRowAdditions); - } - } - - @Override - protected int getHierarchyColumnIndex() { - return colIndexOfHierarchy + (showRowHeaders ? 1 : 0); - } - - public class VTreeTableScrollBody extends VScrollTable.VScrollTableBody { - private int indentWidth = -1; - private int maxIndent = 0; - - protected VTreeTableScrollBody() { - super(); - } - - @Override - protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) { - if (uidl.hasAttribute("gen_html")) { - // This is a generated row. - return new VTreeTableGeneratedRow(uidl, aligns2); - } - return new VTreeTableRow(uidl, aligns2); - } - - public class VTreeTableRow extends - VScrollTable.VScrollTableBody.VScrollTableRow { - - private boolean isTreeCellAdded = false; - private SpanElement treeSpacer; - private boolean open; - private int depth; - private boolean canHaveChildren; - protected Widget widgetInHierarchyColumn; - - public VTreeTableRow(UIDL uidl, char[] aligns2) { - super(uidl, aligns2); - // this fix causes #15118 and doesn't work for treetable anyway - applyZeroWidthFix = false; - } - - @Override - public void addCell(UIDL rowUidl, String text, char align, - String style, boolean textIsHTML, boolean isSorted, - String description) { - super.addCell(rowUidl, text, align, style, textIsHTML, - isSorted, description); - - addTreeSpacer(rowUidl); - } - - protected boolean addTreeSpacer(UIDL rowUidl) { - if (cellShowsTreeHierarchy(getElement().getChildCount() - 1)) { - Element container = (Element) getElement().getLastChild() - .getFirstChild(); - - if (rowUidl.hasAttribute("icon")) { - Icon icon = client.getIcon(rowUidl - .getStringAttribute("icon")); - icon.setAlternateText("icon"); - container.insertFirst(icon.getElement()); - } - - String classname = "v-treetable-treespacer"; - if (rowUidl.getBooleanAttribute("ca")) { - canHaveChildren = true; - open = rowUidl.getBooleanAttribute("open"); - classname += open ? " v-treetable-node-open" - : " v-treetable-node-closed"; - } - - treeSpacer = Document.get().createSpanElement(); - - treeSpacer.setClassName(classname); - container.insertFirst(treeSpacer); - depth = rowUidl.hasAttribute("depth") ? rowUidl - .getIntAttribute("depth") : 0; - setIndent(); - isTreeCellAdded = true; - return true; - } - return false; - } - - private boolean cellShowsTreeHierarchy(int curColIndex) { - if (isTreeCellAdded) { - return false; - } - return curColIndex == getHierarchyColumnIndex(); - } - - @Override - public void onBrowserEvent(Event event) { - if (event.getEventTarget().cast() == treeSpacer - && treeSpacer.getClassName().contains("node")) { - if (event.getTypeInt() == Event.ONMOUSEUP) { - sendToggleCollapsedUpdate(getKey()); - } - return; - } - super.onBrowserEvent(event); - } - - @Override - public void addCell(UIDL rowUidl, Widget w, char align, - String style, boolean isSorted, String description) { - super.addCell(rowUidl, w, align, style, isSorted, description); - if (addTreeSpacer(rowUidl)) { - widgetInHierarchyColumn = w; - } - - } - - private void setIndent() { - if (getIndentWidth() > 0) { - treeSpacer.getParentElement().getStyle() - .setPaddingLeft(getIndent(), Unit.PX); - treeSpacer.getStyle().setWidth(getIndent(), Unit.PX); - int colWidth = getColWidth(getHierarchyColumnIndex()); - if (colWidth > 0 && getIndent() > colWidth) { - VTreeTable.this.setColWidth(getHierarchyColumnIndex(), - getIndent(), false); - } - } - } - - @Override - protected void onAttach() { - super.onAttach(); - if (getIndentWidth() < 0) { - detectIndent(this); - // If we detect indent here then the size of the hierarchy - // column is still wrong as it has been set when the indent - // was not known. - int w = getCellWidthFromDom(getHierarchyColumnIndex()); - if (w >= 0) { - setColWidth(getHierarchyColumnIndex(), w); - } - } - } - - private int getCellWidthFromDom(int cellIndex) { - final Element cell = DOM.getChild(getElement(), cellIndex); - String w = cell.getStyle().getProperty("width"); - if (w == null || "".equals(w) || !w.endsWith("px")) { - return -1; - } else { - return Integer.parseInt(w.substring(0, w.length() - 2)); - } - } - - private int getHierarchyAndIconWidth() { - int consumedSpace = treeSpacer.getOffsetWidth(); - if (treeSpacer.getParentElement().getChildCount() > 2) { - // icon next to tree spacer - consumedSpace += ((com.google.gwt.dom.client.Element) treeSpacer - .getNextSibling()).getOffsetWidth(); - } - return consumedSpace; - } - - @Override - protected void setCellWidth(int cellIx, int width) { - if (cellIx == getHierarchyColumnIndex()) { - // take indentation padding into account if this is the - // hierarchy column - int indent = getIndent(); - if (indent != -1) { - width = Math.max(width - indent, 0); - } - } - super.setCellWidth(cellIx, width); - } - - private int getIndent() { - return (depth + 1) * getIndentWidth(); - } - } - - protected class VTreeTableGeneratedRow extends VTreeTableRow { - private boolean spanColumns; - private boolean htmlContentAllowed; - - public VTreeTableGeneratedRow(UIDL uidl, char[] aligns) { - super(uidl, aligns); - addStyleName("v-table-generated-row"); - } - - public boolean isSpanColumns() { - return spanColumns; - } - - @Override - protected void initCellWidths() { - if (spanColumns) { - setSpannedColumnWidthAfterDOMFullyInited(); - } else { - super.initCellWidths(); - } - } - - private void setSpannedColumnWidthAfterDOMFullyInited() { - // Defer setting width on spanned columns to make sure that - // they are added to the DOM before trying to calculate - // widths. - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - - @Override - public void execute() { - if (showRowHeaders) { - setCellWidth(0, tHead.getHeaderCell(0) - .getWidthWithIndent()); - calcAndSetSpanWidthOnCell(1); - } else { - calcAndSetSpanWidthOnCell(0); - } - } - }); - } - - @Override - protected boolean isRenderHtmlInCells() { - return htmlContentAllowed; - } - - @Override - protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, - int visibleColumnIndex) { - htmlContentAllowed = uidl.getBooleanAttribute("gen_html"); - spanColumns = uidl.getBooleanAttribute("gen_span"); - - final Iterator cells = uidl.getChildIterator(); - if (spanColumns) { - int colCount = uidl.getChildCount(); - if (cells.hasNext()) { - final Object cell = cells.next(); - if (cell instanceof String) { - addSpannedCell(uidl, cell.toString(), aligns[0], - "", htmlContentAllowed, false, null, - colCount); - } else { - addSpannedCell(uidl, (Widget) cell, aligns[0], "", - false, colCount); - } - } - } else { - super.addCellsFromUIDL(uidl, aligns, col, - visibleColumnIndex); - } - } - - private void addSpannedCell(UIDL rowUidl, Widget w, char align, - String style, boolean sorted, int colCount) { - TableCellElement td = DOM.createTD().cast(); - td.setColSpan(colCount); - initCellWithWidget(w, align, style, sorted, td); - td.getStyle().setHeight(getRowHeight(), Unit.PX); - if (addTreeSpacer(rowUidl)) { - widgetInHierarchyColumn = w; - } - } - - private void addSpannedCell(UIDL rowUidl, String text, char align, - String style, boolean textIsHTML, boolean sorted, - String description, int colCount) { - // String only content is optimized by not using Label widget - final TableCellElement td = DOM.createTD().cast(); - td.setColSpan(colCount); - initCellWithText(text, align, style, textIsHTML, sorted, - description, td); - td.getStyle().setHeight(getRowHeight(), Unit.PX); - addTreeSpacer(rowUidl); - } - - @Override - protected void setCellWidth(int cellIx, int width) { - if (isSpanColumns()) { - if (showRowHeaders) { - if (cellIx == 0) { - super.setCellWidth(0, width); - } else { - // We need to recalculate the spanning TDs width for - // every cellIx in order to support column resizing. - calcAndSetSpanWidthOnCell(1); - } - } else { - // Same as above. - calcAndSetSpanWidthOnCell(0); - } - } else { - super.setCellWidth(cellIx, width); - } - } - - private void calcAndSetSpanWidthOnCell(final int cellIx) { - int spanWidth = 0; - for (int ix = (showRowHeaders ? 1 : 0); ix < tHead - .getVisibleCellCount(); ix++) { - spanWidth += tHead.getHeaderCell(ix).getOffsetWidth(); - } - WidgetUtil.setWidthExcludingPaddingAndBorder( - (Element) getElement().getChild(cellIx), spanWidth, 13, - false); - } - } - - private int getIndentWidth() { - return indentWidth; - } - - @Override - protected int getMaxIndent() { - return maxIndent; - } - - @Override - protected void calculateMaxIndent() { - int maxIndent = 0; - Iterator iterator = iterator(); - while (iterator.hasNext()) { - VTreeTableRow next = (VTreeTableRow) iterator.next(); - maxIndent = Math.max(maxIndent, next.getIndent()); - } - this.maxIndent = maxIndent; - } - - private void detectIndent(VTreeTableRow vTreeTableRow) { - indentWidth = vTreeTableRow.treeSpacer.getOffsetWidth(); - if (indentWidth == 0) { - indentWidth = -1; - return; - } - Iterator iterator = iterator(); - while (iterator.hasNext()) { - VTreeTableRow next = (VTreeTableRow) iterator.next(); - next.setIndent(); - } - calculateMaxIndent(); - } - - protected void unlinkRowsAnimatedAndUpdateCacheWhenFinished( - final int firstIndex, final int rows) { - List rowsToDelete = new ArrayList(); - for (int ix = firstIndex; ix < firstIndex + rows; ix++) { - VScrollTableRow row = getRowByRowIndex(ix); - if (row != null) { - rowsToDelete.add(row); - } - } - if (!rowsToDelete.isEmpty()) { - // #8810 Only animate if there's something to animate - RowCollapseAnimation anim = new RowCollapseAnimation( - rowsToDelete) { - @Override - protected void onComplete() { - super.onComplete(); - // Actually unlink the rows and update the cache after - // the - // animation is done. - unlinkAndReindexRows(firstIndex, rows); - discardRowsOutsideCacheWindow(); - ensureCacheFilled(); - } - }; - anim.run(150); - } - } - - protected List insertRowsAnimated(UIDL rowData, - int firstIndex, int rows) { - List insertedRows = insertAndReindexRows(rowData, - firstIndex, rows); - if (!insertedRows.isEmpty()) { - // Only animate if there's something to animate (#8810) - RowExpandAnimation anim = new RowExpandAnimation(insertedRows); - anim.run(150); - } - scrollBody.calculateMaxIndent(); - return insertedRows; - } - - /** - * Prepares the table for animation by copying the background colors of - * all TR elements to their respective TD elements if the TD element is - * transparent. This is needed, since if TDs have transparent - * backgrounds, the rows sliding behind them are visible. - */ - private class AnimationPreparator { - private final int lastItemIx; - - public AnimationPreparator(int lastItemIx) { - this.lastItemIx = lastItemIx; - } - - public void prepareTableForAnimation() { - int ix = lastItemIx; - VScrollTableRow row = null; - while ((row = getRowByRowIndex(ix)) != null) { - copyTRBackgroundsToTDs(row); - --ix; - } - } - - private void copyTRBackgroundsToTDs(VScrollTableRow row) { - Element tr = row.getElement(); - ComputedStyle cs = new ComputedStyle(tr); - String backgroundAttachment = cs - .getProperty("backgroundAttachment"); - String backgroundClip = cs.getProperty("backgroundClip"); - String backgroundColor = cs.getProperty("backgroundColor"); - String backgroundImage = cs.getProperty("backgroundImage"); - String backgroundOrigin = cs.getProperty("backgroundOrigin"); - for (int ix = 0; ix < tr.getChildCount(); ix++) { - Element td = tr.getChild(ix).cast(); - if (!elementHasBackground(td)) { - td.getStyle().setProperty("backgroundAttachment", - backgroundAttachment); - td.getStyle().setProperty("backgroundClip", - backgroundClip); - td.getStyle().setProperty("backgroundColor", - backgroundColor); - td.getStyle().setProperty("backgroundImage", - backgroundImage); - td.getStyle().setProperty("backgroundOrigin", - backgroundOrigin); - } - } - } - - private boolean elementHasBackground(Element element) { - ComputedStyle cs = new ComputedStyle(element); - String clr = cs.getProperty("backgroundColor"); - String img = cs.getProperty("backgroundImage"); - return !("rgba(0, 0, 0, 0)".equals(clr.trim()) - || "transparent".equals(clr.trim()) || img == null); - } - - public void restoreTableAfterAnimation() { - int ix = lastItemIx; - VScrollTableRow row = null; - while ((row = getRowByRowIndex(ix)) != null) { - restoreStyleForTDsInRow(row); - - --ix; - } - } - - private void restoreStyleForTDsInRow(VScrollTableRow row) { - Element tr = row.getElement(); - for (int ix = 0; ix < tr.getChildCount(); ix++) { - Element td = tr.getChild(ix).cast(); - td.getStyle().clearProperty("backgroundAttachment"); - td.getStyle().clearProperty("backgroundClip"); - td.getStyle().clearProperty("backgroundColor"); - td.getStyle().clearProperty("backgroundImage"); - td.getStyle().clearProperty("backgroundOrigin"); - } - } - } - - /** - * Animates row expansion using the GWT animation framework. - * - * The idea is as follows: - * - * 1. Insert all rows normally - * - * 2. Insert a newly created DIV containing a new TABLE element below - * the DIV containing the actual scroll table body. - * - * 3. Clone the rows that were inserted in step 1 and attach the clones - * to the new TABLE element created in step 2. - * - * 4. The new DIV from step 2 is absolutely positioned so that the last - * inserted row is just behind the row that was expanded. - * - * 5. Hide the contents of the originally inserted rows by setting the - * DIV.v-table-cell-wrapper to display:none;. - * - * 6. Set the height of the originally inserted rows to 0. - * - * 7. The animation loop slides the DIV from step 2 downwards, while at - * the same pace growing the height of each of the inserted rows from 0 - * to full height. The first inserted row grows from 0 to full and after - * this the second row grows from 0 to full, etc until all rows are full - * height. - * - * 8. Remove the DIV from step 2 - * - * 9. Restore display:block; to the DIV.v-table-cell-wrapper elements. - * - * 10. DONE - */ - private class RowExpandAnimation extends Animation { - - private final List rows; - private Element cloneDiv; - private Element cloneTable; - private AnimationPreparator preparator; - - /** - * @param rows - * List of rows to animate. Must not be empty. - */ - public RowExpandAnimation(List rows) { - this.rows = rows; - buildAndInsertAnimatingDiv(); - preparator = new AnimationPreparator(rows.get(0).getIndex() - 1); - preparator.prepareTableForAnimation(); - for (VScrollTableRow row : rows) { - cloneAndAppendRow(row); - row.addStyleName("v-table-row-animating"); - setCellWrapperDivsToDisplayNone(row); - row.setHeight(getInitialHeight()); - } - } - - protected String getInitialHeight() { - return "0px"; - } - - private void cloneAndAppendRow(VScrollTableRow row) { - Element clonedTR = null; - clonedTR = row.getElement().cloneNode(true).cast(); - clonedTR.getStyle().setVisibility(Visibility.VISIBLE); - cloneTable.appendChild(clonedTR); - } - - protected double getBaseOffset() { - return rows.get(0).getAbsoluteTop() - - rows.get(0).getParent().getAbsoluteTop() - - rows.size() * getRowHeight(); - } - - private void buildAndInsertAnimatingDiv() { - cloneDiv = DOM.createDiv(); - cloneDiv.addClassName("v-treetable-animation-clone-wrapper"); - cloneTable = DOM.createTable(); - cloneTable.addClassName("v-treetable-animation-clone"); - cloneDiv.appendChild(cloneTable); - insertAnimatingDiv(); - } - - private void insertAnimatingDiv() { - Element tableBody = getElement(); - Element tableBodyParent = tableBody.getParentElement(); - tableBodyParent.insertAfter(cloneDiv, tableBody); - } - - @Override - protected void onUpdate(double progress) { - animateDiv(progress); - animateRowHeights(progress); - } - - private void animateDiv(double progress) { - double offset = calculateDivOffset(progress, getRowHeight()); - - cloneDiv.getStyle().setTop(getBaseOffset() + offset, Unit.PX); - } - - private void animateRowHeights(double progress) { - double rh = getRowHeight(); - double vlh = calculateHeightOfAllVisibleLines(progress, rh); - int ix = 0; - - while (ix < rows.size()) { - double height = vlh < rh ? vlh : rh; - rows.get(ix).setHeight(height + "px"); - vlh -= height; - ix++; - } - } - - protected double calculateHeightOfAllVisibleLines(double progress, - double rh) { - return rows.size() * rh * progress; - } - - protected double calculateDivOffset(double progress, double rh) { - return progress * rows.size() * rh; - } - - @Override - protected void onComplete() { - preparator.restoreTableAfterAnimation(); - for (VScrollTableRow row : rows) { - resetCellWrapperDivsDisplayProperty(row); - row.removeStyleName("v-table-row-animating"); - } - Element tableBodyParent = getElement().getParentElement(); - tableBodyParent.removeChild(cloneDiv); - } - - private void setCellWrapperDivsToDisplayNone(VScrollTableRow row) { - Element tr = row.getElement(); - for (int ix = 0; ix < tr.getChildCount(); ix++) { - getWrapperDiv(tr, ix).getStyle().setDisplay(Display.NONE); - } - } - - private Element getWrapperDiv(Element tr, int tdIx) { - Element td = tr.getChild(tdIx).cast(); - return td.getChild(0).cast(); - } - - private void resetCellWrapperDivsDisplayProperty(VScrollTableRow row) { - Element tr = row.getElement(); - for (int ix = 0; ix < tr.getChildCount(); ix++) { - getWrapperDiv(tr, ix).getStyle().clearProperty("display"); - } - } - - } - - /** - * This is the inverse of the RowExpandAnimation and is implemented by - * extending it and overriding the calculation of offsets and heights. - */ - private class RowCollapseAnimation extends RowExpandAnimation { - - private final List rows; - - /** - * @param rows - * List of rows to animate. Must not be empty. - */ - public RowCollapseAnimation(List rows) { - super(rows); - this.rows = rows; - } - - @Override - protected String getInitialHeight() { - return getRowHeight() + "px"; - } - - @Override - protected double getBaseOffset() { - return getRowHeight(); - } - - @Override - protected double calculateHeightOfAllVisibleLines(double progress, - double rh) { - return rows.size() * rh * (1 - progress); - } - - @Override - protected double calculateDivOffset(double progress, double rh) { - return -super.calculateDivOffset(progress, rh); - } - } - } - - /** - * Icons rendered into first actual column in TreeTable, not to row header - * cell - */ - @Override - protected String buildCaptionHtmlSnippet(UIDL uidl) { - if (uidl.getTag().equals("column")) { - return super.buildCaptionHtmlSnippet(uidl); - } else { - String s = uidl.getStringAttribute("caption"); - return s; - } - } - - /** For internal use only. May be removed or replaced in the future. */ - @Override - public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { - if (collapseRequest || focusParentResponsePending) { - // Enqueue the event if there might be pending content changes from - // the server - if (pendingNavigationEvents.size() < 10) { - // Only keep 10 keyboard events in the queue - PendingNavigationEvent pendingNavigationEvent = new PendingNavigationEvent( - keycode, ctrl, shift); - pendingNavigationEvents.add(pendingNavigationEvent); - } - return true; - } - - VTreeTableRow focusedRow = (VTreeTableRow) getFocusedRow(); - if (focusedRow != null) { - if (focusedRow.canHaveChildren - && ((keycode == KeyCodes.KEY_RIGHT && !focusedRow.open) || (keycode == KeyCodes.KEY_LEFT && focusedRow.open))) { - if (!ctrl) { - client.updateVariable(paintableId, "selectCollapsed", true, - false); - } - sendSelectedRows(false); - sendToggleCollapsedUpdate(focusedRow.getKey()); - return true; - } else if (keycode == KeyCodes.KEY_RIGHT && focusedRow.open) { - // already expanded, move selection down if next is on a deeper - // level (is-a-child) - VTreeTableScrollBody body = (VTreeTableScrollBody) focusedRow - .getParent(); - Iterator iterator = body.iterator(); - VTreeTableRow next = null; - while (iterator.hasNext()) { - next = (VTreeTableRow) iterator.next(); - if (next == focusedRow) { - next = (VTreeTableRow) iterator.next(); - break; - } - } - if (next != null) { - if (next.depth > focusedRow.depth) { - selectionPending = true; - return super.handleNavigation(getNavigationDownKey(), - ctrl, shift); - } - } else { - // Note, a minor change here for a bit false behavior if - // cache rows is disabled + last visible row + no childs for - // the node - selectionPending = true; - return super.handleNavigation(getNavigationDownKey(), ctrl, - shift); - } - } else if (keycode == KeyCodes.KEY_LEFT) { - // already collapsed move selection up to parent node - // do on the server side as the parent is not necessary - // rendered on the client, could check if parent is visible if - // a performance issue arises - - client.updateVariable(paintableId, "focusParent", - focusedRow.getKey(), true); - - // Set flag that we should enqueue navigation events until we - // get a response to this request - focusParentResponsePending = true; - - return true; - } - } - return super.handleNavigation(keycode, ctrl, shift); - } - - private void sendToggleCollapsedUpdate(String rowKey) { - collapsedRowKey = rowKey; - collapseRequest = true; - client.updateVariable(paintableId, "toggleCollapsed", rowKey, true); - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (event.getTypeInt() == Event.ONKEYUP && selectionPending) { - sendSelectedRows(); - } - } - - @Override - protected void sendSelectedRows(boolean immediately) { - super.sendSelectedRows(immediately); - selectionPending = false; - } - - @Override - protected void reOrderColumn(String columnKey, int newIndex) { - super.reOrderColumn(columnKey, newIndex); - // current impl not intelligent enough to survive without visiting the - // server to redraw content - client.sendPendingVariableChanges(); - } - - @Override - public void setStyleName(String style) { - super.setStyleName(style + " v-treetable"); - } - - @Override - public void updateTotalRows(UIDL uidl) { - // Make sure that initializedAndAttached & al are not reset when the - // totalrows are updated on expand/collapse requests. - int newTotalRows = uidl.getIntAttribute("totalrows"); - setTotalRows(newTotalRows); - } - -} diff --git a/client/src/com/vaadin/client/ui/VTwinColSelect.java b/client/src/com/vaadin/client/ui/VTwinColSelect.java deleted file mode 100644 index 853bd8d456..0000000000 --- a/client/src/com/vaadin/client/ui/VTwinColSelect.java +++ /dev/null @@ -1,630 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.DoubleClickEvent; -import com.google.gwt.event.dom.client.DoubleClickHandler; -import com.google.gwt.event.dom.client.HasDoubleClickHandlers; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.MouseDownEvent; -import com.google.gwt.event.dom.client.MouseDownHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.ListBox; -import com.google.gwt.user.client.ui.Panel; -import com.vaadin.client.StyleConstants; -import com.vaadin.client.UIDL; -import com.vaadin.client.WidgetUtil; -import com.vaadin.shared.ui.twincolselect.TwinColSelectConstants; - -public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, - MouseDownHandler, DoubleClickHandler, SubPartAware { - - public static final String CLASSNAME = "v-select-twincol"; - - private static final int VISIBLE_COUNT = 10; - - private static final int DEFAULT_COLUMN_COUNT = 10; - - private final DoubleClickListBox options; - - private final DoubleClickListBox selections; - - /** For internal use only. May be removed or replaced in the future. */ - public FlowPanel captionWrapper; - - private HTML optionsCaption = null; - - private HTML selectionsCaption = null; - - private final VButton add; - - private final VButton remove; - - private final FlowPanel buttons; - - private final Panel panel; - - /** - * A ListBox which catches double clicks - * - */ - public class DoubleClickListBox extends ListBox implements - HasDoubleClickHandlers { - public DoubleClickListBox(boolean isMultipleSelect) { - super(isMultipleSelect); - } - - public DoubleClickListBox() { - super(); - } - - @Override - public HandlerRegistration addDoubleClickHandler( - DoubleClickHandler handler) { - return addDomHandler(handler, DoubleClickEvent.getType()); - } - } - - public VTwinColSelect() { - super(CLASSNAME); - - captionWrapper = new FlowPanel(); - - options = new DoubleClickListBox(); - options.addClickHandler(this); - options.addDoubleClickHandler(this); - options.setVisibleItemCount(VISIBLE_COUNT); - options.setStyleName(CLASSNAME + "-options"); - - selections = new DoubleClickListBox(); - selections.addClickHandler(this); - selections.addDoubleClickHandler(this); - selections.setVisibleItemCount(VISIBLE_COUNT); - selections.setStyleName(CLASSNAME + "-selections"); - - buttons = new FlowPanel(); - buttons.setStyleName(CLASSNAME + "-buttons"); - add = new VButton(); - add.setText(">>"); - add.addClickHandler(this); - remove = new VButton(); - remove.setText("<<"); - remove.addClickHandler(this); - - panel = ((Panel) optionsContainer); - - panel.add(captionWrapper); - captionWrapper.getElement().getStyle().setOverflow(Overflow.HIDDEN); - // Hide until there actually is a caption to prevent IE from rendering - // extra empty space - captionWrapper.setVisible(false); - - panel.add(options); - buttons.add(add); - final HTML br = new HTML(""); - br.setStyleName(CLASSNAME + "-deco"); - buttons.add(br); - buttons.add(remove); - panel.add(buttons); - panel.add(selections); - - options.addKeyDownHandler(this); - options.addMouseDownHandler(this); - - selections.addMouseDownHandler(this); - selections.addKeyDownHandler(this); - - updateEnabledState(); - } - - public HTML getOptionsCaption() { - if (optionsCaption == null) { - optionsCaption = new HTML(); - optionsCaption.setStyleName(CLASSNAME + "-caption-left"); - optionsCaption.getElement().getStyle() - .setFloat(com.google.gwt.dom.client.Style.Float.LEFT); - captionWrapper.add(optionsCaption); - } - - return optionsCaption; - } - - public HTML getSelectionsCaption() { - if (selectionsCaption == null) { - selectionsCaption = new HTML(); - selectionsCaption.setStyleName(CLASSNAME + "-caption-right"); - selectionsCaption.getElement().getStyle() - .setFloat(com.google.gwt.dom.client.Style.Float.RIGHT); - captionWrapper.add(selectionsCaption); - } - - return selectionsCaption; - } - - /** For internal use only. May be removed or replaced in the future. */ - public void updateCaptions(UIDL uidl) { - String leftCaption = (uidl - .hasAttribute(TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION) ? uidl - .getStringAttribute(TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION) - : null); - String rightCaption = (uidl - .hasAttribute(TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION) ? uidl - .getStringAttribute(TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION) - : null); - - boolean hasCaptions = (leftCaption != null || rightCaption != null); - - if (leftCaption == null) { - removeOptionsCaption(); - } else { - getOptionsCaption().setText(leftCaption); - - } - - if (rightCaption == null) { - removeSelectionsCaption(); - } else { - getSelectionsCaption().setText(rightCaption); - } - - captionWrapper.setVisible(hasCaptions); - } - - private void removeOptionsCaption() { - if (optionsCaption == null) { - return; - } - - if (optionsCaption.getParent() != null) { - captionWrapper.remove(optionsCaption); - } - - optionsCaption = null; - } - - private void removeSelectionsCaption() { - if (selectionsCaption == null) { - return; - } - - if (selectionsCaption.getParent() != null) { - captionWrapper.remove(selectionsCaption); - } - - selectionsCaption = null; - } - - @Override - public void buildOptions(UIDL uidl) { - options.setMultipleSelect(isMultiselect()); - selections.setMultipleSelect(isMultiselect()); - options.clear(); - selections.clear(); - for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { - final UIDL optionUidl = (UIDL) i.next(); - if (optionUidl.hasAttribute("selected")) { - selections.addItem(optionUidl.getStringAttribute("caption"), - optionUidl.getStringAttribute("key")); - } else { - options.addItem(optionUidl.getStringAttribute("caption"), - optionUidl.getStringAttribute("key")); - } - } - - if (getRows() > 0) { - options.setVisibleItemCount(getRows()); - selections.setVisibleItemCount(getRows()); - - } - } - - @Override - protected String[] getSelectedItems() { - final ArrayList selectedItemKeys = new ArrayList(); - for (int i = 0; i < selections.getItemCount(); i++) { - selectedItemKeys.add(selections.getValue(i)); - } - return selectedItemKeys.toArray(new String[selectedItemKeys.size()]); - } - - private boolean[] getSelectionBitmap(ListBox listBox) { - final boolean[] selectedIndexes = new boolean[listBox.getItemCount()]; - for (int i = 0; i < listBox.getItemCount(); i++) { - if (listBox.isItemSelected(i)) { - selectedIndexes[i] = true; - } else { - selectedIndexes[i] = false; - } - } - return selectedIndexes; - } - - private void addItem() { - Set movedItems = moveSelectedItems(options, selections); - selectedKeys.addAll(movedItems); - - client.updateVariable(paintableId, "selected", - selectedKeys.toArray(new String[selectedKeys.size()]), - isImmediate()); - } - - private void removeItem() { - Set movedItems = moveSelectedItems(selections, options); - selectedKeys.removeAll(movedItems); - - client.updateVariable(paintableId, "selected", - selectedKeys.toArray(new String[selectedKeys.size()]), - isImmediate()); - } - - private Set moveSelectedItems(ListBox source, ListBox target) { - final boolean[] sel = getSelectionBitmap(source); - final Set movedItems = new HashSet(); - int lastSelected = 0; - for (int i = 0; i < sel.length; i++) { - if (sel[i]) { - final int optionIndex = i - - (sel.length - source.getItemCount()); - movedItems.add(source.getValue(optionIndex)); - - // Move selection to another column - final String text = source.getItemText(optionIndex); - final String value = source.getValue(optionIndex); - target.addItem(text, value); - target.setItemSelected(target.getItemCount() - 1, true); - source.removeItem(optionIndex); - - if (source.getItemCount() > 0) { - lastSelected = optionIndex > 0 ? optionIndex - 1 : 0; - } - } - } - - if (source.getItemCount() > 0) { - source.setSelectedIndex(lastSelected); - } - - // If no items are left move the focus to the selections - if (source.getItemCount() == 0) { - target.setFocus(true); - } else { - source.setFocus(true); - } - - return movedItems; - } - - @Override - public void onClick(ClickEvent event) { - super.onClick(event); - if (event.getSource() == add) { - addItem(); - - } else if (event.getSource() == remove) { - removeItem(); - - } else if (event.getSource() == options) { - // unselect all in other list, to avoid mistakes (i.e wrong button) - final int c = selections.getItemCount(); - for (int i = 0; i < c; i++) { - selections.setItemSelected(i, false); - } - } else if (event.getSource() == selections) { - // unselect all in other list, to avoid mistakes (i.e wrong button) - final int c = options.getItemCount(); - for (int i = 0; i < c; i++) { - options.setItemSelected(i, false); - } - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void clearInternalHeights() { - selections.setHeight(""); - options.setHeight(""); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void setInternalHeights() { - int captionHeight = WidgetUtil.getRequiredHeight(captionWrapper); - int totalHeight = getOffsetHeight(); - - String selectHeight = (totalHeight - captionHeight) + "px"; - - selections.setHeight(selectHeight); - options.setHeight(selectHeight); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void clearInternalWidths() { - int cols = -1; - if (getColumns() > 0) { - cols = getColumns(); - } else { - cols = DEFAULT_COLUMN_COUNT; - } - - if (cols >= 0) { - String colWidth = cols + "em"; - String containerWidth = (2 * cols + 4) + "em"; - // Caption wrapper width == optionsSelect + buttons + - // selectionsSelect - String captionWrapperWidth = (2 * cols + 4 - 0.5) + "em"; - - options.setWidth(colWidth); - if (optionsCaption != null) { - optionsCaption.setWidth(colWidth); - } - selections.setWidth(colWidth); - if (selectionsCaption != null) { - selectionsCaption.setWidth(colWidth); - } - buttons.setWidth("3.5em"); - optionsContainer.setWidth(containerWidth); - captionWrapper.setWidth(captionWrapperWidth); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void setInternalWidths() { - getElement().getStyle().setPosition(Position.RELATIVE); - int bordersAndPaddings = WidgetUtil.measureHorizontalPaddingAndBorder( - buttons.getElement(), 0); - - int buttonWidth = WidgetUtil.getRequiredWidth(buttons); - int totalWidth = getOffsetWidth(); - - int spaceForSelect = (totalWidth - buttonWidth - bordersAndPaddings) / 2; - - options.setWidth(spaceForSelect + "px"); - if (optionsCaption != null) { - optionsCaption.setWidth(spaceForSelect + "px"); - } - - selections.setWidth(spaceForSelect + "px"); - if (selectionsCaption != null) { - selectionsCaption.setWidth(spaceForSelect + "px"); - } - captionWrapper.setWidth("100%"); - } - - @Override - public void setTabIndex(int tabIndex) { - options.setTabIndex(tabIndex); - selections.setTabIndex(tabIndex); - add.setTabIndex(tabIndex); - remove.setTabIndex(tabIndex); - } - - @Override - public void updateEnabledState() { - boolean enabled = isEnabled() && !isReadonly(); - options.setEnabled(enabled); - selections.setEnabled(enabled); - add.setEnabled(enabled); - remove.setEnabled(enabled); - add.setStyleName(StyleConstants.DISABLED, !enabled); - remove.setStyleName(StyleConstants.DISABLED, !enabled); - } - - @Override - public void focus() { - options.setFocus(true); - } - - /** - * Get the key that selects an item in the table. By default it is the Enter - * key but by overriding this you can change the key to whatever you want. - * - * @return - */ - protected int getNavigationSelectKey() { - return KeyCodes.KEY_ENTER; - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt - * .event.dom.client.KeyDownEvent) - */ - @Override - public void onKeyDown(KeyDownEvent event) { - int keycode = event.getNativeKeyCode(); - - // Catch tab and move between select:s - if (keycode == KeyCodes.KEY_TAB && event.getSource() == options) { - // Prevent default behavior - event.preventDefault(); - - // Remove current selections - for (int i = 0; i < options.getItemCount(); i++) { - options.setItemSelected(i, false); - } - - // Focus selections - selections.setFocus(true); - } - - if (keycode == KeyCodes.KEY_TAB && event.isShiftKeyDown() - && event.getSource() == selections) { - // Prevent default behavior - event.preventDefault(); - - // Remove current selections - for (int i = 0; i < selections.getItemCount(); i++) { - selections.setItemSelected(i, false); - } - - // Focus options - options.setFocus(true); - } - - if (keycode == getNavigationSelectKey()) { - // Prevent default behavior - event.preventDefault(); - - // Decide which select the selection was made in - if (event.getSource() == options) { - // Prevents the selection to become a single selection when - // using Enter key - // as the selection key (default) - options.setFocus(false); - - addItem(); - - } else if (event.getSource() == selections) { - // Prevents the selection to become a single selection when - // using Enter key - // as the selection key (default) - selections.setFocus(false); - - removeItem(); - } - } - - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google - * .gwt.event.dom.client.MouseDownEvent) - */ - @Override - public void onMouseDown(MouseDownEvent event) { - // Ensure that items are deselected when selecting - // from a different source. See #3699 for details. - if (event.getSource() == options) { - for (int i = 0; i < selections.getItemCount(); i++) { - selections.setItemSelected(i, false); - } - } else if (event.getSource() == selections) { - for (int i = 0; i < options.getItemCount(); i++) { - options.setItemSelected(i, false); - } - } - - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.DoubleClickHandler#onDoubleClick(com. - * google.gwt.event.dom.client.DoubleClickEvent) - */ - @Override - public void onDoubleClick(DoubleClickEvent event) { - if (event.getSource() == options) { - addItem(); - options.setSelectedIndex(-1); - options.setFocus(false); - } else if (event.getSource() == selections) { - removeItem(); - selections.setSelectedIndex(-1); - selections.setFocus(false); - } - - } - - private static final String SUBPART_OPTION_SELECT = "leftSelect"; - private static final String SUBPART_OPTION_SELECT_ITEM = SUBPART_OPTION_SELECT - + "-item"; - private static final String SUBPART_SELECTION_SELECT = "rightSelect"; - private static final String SUBPART_SELECTION_SELECT_ITEM = SUBPART_SELECTION_SELECT - + "-item"; - private static final String SUBPART_LEFT_CAPTION = "leftCaption"; - private static final String SUBPART_RIGHT_CAPTION = "rightCaption"; - private static final String SUBPART_ADD_BUTTON = "add"; - private static final String SUBPART_REMOVE_BUTTON = "remove"; - - @Override - public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - if (SUBPART_OPTION_SELECT.equals(subPart)) { - return options.getElement(); - } else if (subPart.startsWith(SUBPART_OPTION_SELECT_ITEM)) { - String idx = subPart.substring(SUBPART_OPTION_SELECT_ITEM.length()); - return (com.google.gwt.user.client.Element) options.getElement() - .getChild(Integer.parseInt(idx)); - } else if (SUBPART_SELECTION_SELECT.equals(subPart)) { - return selections.getElement(); - } else if (subPart.startsWith(SUBPART_SELECTION_SELECT_ITEM)) { - String idx = subPart.substring(SUBPART_SELECTION_SELECT_ITEM - .length()); - return (com.google.gwt.user.client.Element) selections.getElement() - .getChild(Integer.parseInt(idx)); - } else if (optionsCaption != null - && SUBPART_LEFT_CAPTION.equals(subPart)) { - return optionsCaption.getElement(); - } else if (selectionsCaption != null - && SUBPART_RIGHT_CAPTION.equals(subPart)) { - return selectionsCaption.getElement(); - } else if (SUBPART_ADD_BUTTON.equals(subPart)) { - return add.getElement(); - } else if (SUBPART_REMOVE_BUTTON.equals(subPart)) { - return remove.getElement(); - } - - return null; - } - - @Override - public String getSubPartName(com.google.gwt.user.client.Element subElement) { - if (optionsCaption != null - && optionsCaption.getElement().isOrHasChild(subElement)) { - return SUBPART_LEFT_CAPTION; - } else if (selectionsCaption != null - && selectionsCaption.getElement().isOrHasChild(subElement)) { - return SUBPART_RIGHT_CAPTION; - } else if (options.getElement().isOrHasChild(subElement)) { - if (options.getElement() == subElement) { - return SUBPART_OPTION_SELECT; - } else { - int idx = WidgetUtil.getChildElementIndex(subElement); - return SUBPART_OPTION_SELECT_ITEM + idx; - } - } else if (selections.getElement().isOrHasChild(subElement)) { - if (selections.getElement() == subElement) { - return SUBPART_SELECTION_SELECT; - } else { - int idx = WidgetUtil.getChildElementIndex(subElement); - return SUBPART_SELECTION_SELECT_ITEM + idx; - } - } else if (add.getElement().isOrHasChild(subElement)) { - return SUBPART_ADD_BUTTON; - } else if (remove.getElement().isOrHasChild(subElement)) { - return SUBPART_REMOVE_BUTTON; - } - - return null; - } -} diff --git a/client/src/com/vaadin/client/ui/VUI.java b/client/src/com/vaadin/client/ui/VUI.java deleted file mode 100644 index 08641ad6ba..0000000000 --- a/client/src/com/vaadin/client/ui/VUI.java +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import java.util.ArrayList; - -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.dom.client.HasScrollHandlers; -import com.google.gwt.event.dom.client.ScrollEvent; -import com.google.gwt.event.dom.client.ScrollHandler; -import com.google.gwt.event.logical.shared.HasResizeHandlers; -import com.google.gwt.event.logical.shared.ResizeEvent; -import com.google.gwt.event.logical.shared.ResizeHandler; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.http.client.URL; -import com.google.gwt.user.client.History; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.SimplePanel; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorMap; -import com.vaadin.client.Focusable; -import com.vaadin.client.LayoutManager; -import com.vaadin.client.Profiler; -import com.vaadin.client.VConsole; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; -import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler; -import com.vaadin.client.ui.ui.UIConnector; -import com.vaadin.shared.ApplicationConstants; -import com.vaadin.shared.ui.ui.UIConstants; - -/** - * - */ -public class VUI extends SimplePanel implements ResizeHandler, - Window.ClosingHandler, ShortcutActionHandlerOwner, Focusable, - com.google.gwt.user.client.ui.Focusable, HasResizeHandlers, - HasScrollHandlers { - - private static int MONITOR_PARENT_TIMER_INTERVAL = 1000; - - /** For internal use only. May be removed or replaced in the future. */ - public String id; - - /** For internal use only. May be removed or replaced in the future. */ - public ShortcutActionHandler actionHandler; - - /* - * Last known window size used to detect whether VView should be layouted - * again. Detection must check window size, because the VView size might be - * fixed and thus not automatically adapt to changed window sizes. - */ - private int windowWidth; - private int windowHeight; - - /* - * Last know view size used to detect whether new dimensions should be sent - * to the server. - */ - private int viewWidth; - private int viewHeight; - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection connection; - - /** - * Keep track of possible parent size changes when an embedded application. - * - * Uses {@link #parentWidth} and {@link #parentHeight} as an optimization to - * keep track of when there is a real change. - */ - private Timer resizeTimer; - - /** stored width of parent for embedded application auto-resize */ - private int parentWidth; - - /** stored height of parent for embedded application auto-resize */ - private int parentHeight; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean immediate; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean resizeLazy = false; - - private HandlerRegistration historyHandlerRegistration; - - private TouchScrollHandler touchScrollHandler; - - /** - * The current URI fragment, used to avoid sending updates if nothing has - * changed. - *

- * For internal use only. May be removed or replaced in the future. - */ - public String currentFragment; - - /** - * Listener for URI fragment changes. Notifies the server of the new value - * whenever the value changes. - */ - private final ValueChangeHandler historyChangeHandler = new ValueChangeHandler() { - - @Override - public void onValueChange(ValueChangeEvent event) { - String newFragment = event.getValue(); - - // Send the location to the server if the fragment has changed - // and flush active connectors in UI. - if (!newFragment.equals(currentFragment) && connection != null) { - /* - * Ensure the fragment is properly encoded in all browsers - * (#10769) - * - * createUrlBuilder does not properly pass an empty fragment to - * UrlBuilder on Webkit browsers so do it manually (#11686) - */ - String location = Window.Location - .createUrlBuilder() - .setHash( - URL.decodeQueryString(Window.Location.getHash())) - .buildString(); - - currentFragment = newFragment; - connection.flushActiveConnector(); - connection.updateVariable(id, UIConstants.LOCATION_VARIABLE, - location, true); - } - } - }; - - private VLazyExecutor delayedResizeExecutor = new VLazyExecutor(200, - new ScheduledCommand() { - - @Override - public void execute() { - performSizeCheck(); - } - - }); - - private Element storedFocus; - - public VUI() { - super(); - // Allow focusing the view by using the focus() method, the view - // should not be in the document focus flow - getElement().setTabIndex(-1); - makeScrollable(); - } - - /** - * Start to periodically monitor for parent element resizes if embedded - * application (e.g. portlet). - */ - @Override - protected void onLoad() { - super.onLoad(); - if (isMonitoringParentSize()) { - resizeTimer = new Timer() { - - @Override - public void run() { - // trigger check to see if parent size has changed, - // recalculate layouts - performSizeCheck(); - resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL); - } - }; - resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL); - } - } - - @Override - protected void onAttach() { - super.onAttach(); - historyHandlerRegistration = History - .addValueChangeHandler(historyChangeHandler); - currentFragment = History.getToken(); - } - - @Override - protected void onDetach() { - super.onDetach(); - historyHandlerRegistration.removeHandler(); - historyHandlerRegistration = null; - } - - /** - * Stop monitoring for parent element resizes. - */ - - @Override - protected void onUnload() { - if (resizeTimer != null) { - resizeTimer.cancel(); - resizeTimer = null; - } - super.onUnload(); - } - - /** - * Called when the window or parent div might have been resized. - * - * This immediately checks the sizes of the window and the parent div (if - * monitoring it) and triggers layout recalculation if they have changed. - */ - protected void performSizeCheck() { - windowSizeMaybeChanged(Window.getClientWidth(), - Window.getClientHeight()); - } - - /** - * Called when the window or parent div might have been resized. - * - * This immediately checks the sizes of the window and the parent div (if - * monitoring it) and triggers layout recalculation if they have changed. - * - * @param newWindowWidth - * The new width of the window - * @param newWindowHeight - * The new height of the window - * - * @deprecated use {@link #performSizeCheck()} - */ - @Deprecated - protected void windowSizeMaybeChanged(int newWindowWidth, - int newWindowHeight) { - if (connection == null) { - // Connection is null if the timer fires before the first UIDL - // update - return; - } - - boolean changed = false; - ComponentConnector connector = ConnectorMap.get(connection) - .getConnector(this); - if (windowWidth != newWindowWidth) { - windowWidth = newWindowWidth; - changed = true; - connector.getLayoutManager().reportOuterWidth(connector, - newWindowWidth); - VConsole.log("New window width: " + windowWidth); - } - if (windowHeight != newWindowHeight) { - windowHeight = newWindowHeight; - changed = true; - connector.getLayoutManager().reportOuterHeight(connector, - newWindowHeight); - VConsole.log("New window height: " + windowHeight); - } - Element parentElement = getElement().getParentElement(); - if (isMonitoringParentSize() && parentElement != null) { - // check also for parent size changes - int newParentWidth = parentElement.getClientWidth(); - int newParentHeight = parentElement.getClientHeight(); - if (parentWidth != newParentWidth) { - parentWidth = newParentWidth; - changed = true; - VConsole.log("New parent width: " + parentWidth); - } - if (parentHeight != newParentHeight) { - parentHeight = newParentHeight; - changed = true; - VConsole.log("New parent height: " + parentHeight); - } - } - if (changed) { - /* - * If the window size has changed, layout the VView again and send - * new size to the server if the size changed. (Just checking VView - * size would cause us to ignore cases when a relatively sized VView - * should shrink as the content's size is fixed and would thus not - * automatically shrink.) - */ - VConsole.log("Running layout functions due to window or parent resize"); - - // update size to avoid (most) redundant re-layout passes - // there can still be an extra layout recalculation if webkit - // overflow fix updates the size in a deferred block - if (isMonitoringParentSize() && parentElement != null) { - parentWidth = parentElement.getClientWidth(); - parentHeight = parentElement.getClientHeight(); - } - - sendClientResized(); - - LayoutManager layoutManager = connector.getLayoutManager(); - if (layoutManager.isLayoutRunning()) { - layoutManager.layoutLater(); - } else { - layoutManager.layoutNow(); - } - } - } - - /** - * @return the name of the theme in use by this UI. - * @deprecated as of 7.3. Use {@link UIConnector#getActiveTheme()} instead. - */ - @Deprecated - public String getTheme() { - return ((UIConnector) ConnectorMap.get(connection).getConnector(this)) - .getActiveTheme(); - } - - /** - * Returns true if the body is NOT generated, i.e if someone else has made - * the page that we're running in. Otherwise we're in charge of the whole - * page. - * - * @return true if we're running embedded - */ - public boolean isEmbedded() { - return !getElement().getOwnerDocument().getBody().getClassName() - .contains(ApplicationConstants.GENERATED_BODY_CLASSNAME); - } - - /** - * Returns true if the size of the parent should be checked periodically and - * the application should react to its changes. - * - * @return true if size of parent should be tracked - */ - protected boolean isMonitoringParentSize() { - // could also perform a more specific check (Liferay portlet) - return isEmbedded(); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google - * .gwt.event.logical.shared.ResizeEvent) - */ - - @Override - public void onResize(ResizeEvent event) { - triggerSizeChangeCheck(); - } - - /** - * Called when a resize event is received. - * - * This may trigger a lazy refresh or perform the size check immediately - * depending on the browser used and whether the server side requests - * resizes to be lazy. - */ - private void triggerSizeChangeCheck() { - /* - * IE (pre IE9 at least) will give us some false resize events due to - * problems with scrollbars. Firefox 3 might also produce some extra - * events. We postpone both the re-layouting and the server side event - * for a while to deal with these issues. - * - * We may also postpone these events to avoid slowness when resizing the - * browser window. Constantly recalculating the layout causes the resize - * operation to be really slow with complex layouts. - */ - boolean lazy = resizeLazy || BrowserInfo.get().isIE8(); - - if (lazy) { - delayedResizeExecutor.trigger(); - } else { - performSizeCheck(); - } - } - - /** - * Send new dimensions to the server. - *

- * For internal use only. May be removed or replaced in the future. - */ - public void sendClientResized() { - Profiler.enter("VUI.sendClientResized"); - Element parentElement = getElement().getParentElement(); - int viewHeight = parentElement.getClientHeight(); - int viewWidth = parentElement.getClientWidth(); - - ResizeEvent.fire(this, viewWidth, viewHeight); - Profiler.leave("VUI.sendClientResized"); - } - - public native static void goTo(String url) - /*-{ - $wnd.location = url; - }-*/; - - @Override - public void onWindowClosing(Window.ClosingEvent event) { - // Change focus on this window in order to ensure that all state is - // collected from textfields - // TODO this is a naive hack, that only works with text fields and may - // cause some odd issues. Should be replaced with a decent solution, see - // also related BeforeShortcutActionListener interface. Same interface - // might be usable here. - VTextField.flushChangesFromFocusedTextField(); - } - - private native static void loadAppIdListFromDOM(ArrayList list) - /*-{ - var j; - for(j in $wnd.vaadin.vaadinConfigurations) { - // $entry not needed as function is not exported - list.@java.util.Collection::add(Ljava/lang/Object;)(j); - } - }-*/; - - @Override - public ShortcutActionHandler getShortcutActionHandler() { - return actionHandler; - } - - @Override - public void focus() { - setFocus(true); - } - - /** - * Ensures the widget is scrollable eg. after style name changes. - *

- * For internal use only. May be removed or replaced in the future. - */ - public void makeScrollable() { - if (touchScrollHandler == null) { - touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); - } - touchScrollHandler.addElement(getElement()); - } - - @Override - public HandlerRegistration addResizeHandler(ResizeHandler resizeHandler) { - return addHandler(resizeHandler, ResizeEvent.getType()); - } - - @Override - public HandlerRegistration addScrollHandler(ScrollHandler scrollHandler) { - return addHandler(scrollHandler, ScrollEvent.getType()); - } - - @Override - public int getTabIndex() { - return FocusUtil.getTabIndex(this); - } - - @Override - public void setAccessKey(char key) { - FocusUtil.setAccessKey(this, key); - } - - @Override - public void setFocus(boolean focused) { - FocusUtil.setFocus(this, focused); - } - - @Override - public void setTabIndex(int index) { - FocusUtil.setTabIndex(this, index); - } - - /** - * Allows to store the currently focused Element. - * - * Current use case is to store the focus when a Window is opened. Does - * currently handle only a single value. Needs to be extended for #12158 - * - * @param focusedElement - */ - public void storeFocus() { - storedFocus = WidgetUtil.getFocusedElement(); - } - - /** - * Restores the previously stored focus Element. - * - * Current use case is to restore the focus when a Window is closed. Does - * currently handle only a single value. Needs to be extended for #12158 - * - * @return the lastFocusElementBeforeDialogOpened - */ - public void focusStoredElement() { - if (storedFocus != null) { - storedFocus.focus(); - } - } - -} diff --git a/client/src/com/vaadin/client/ui/VUnknownComponent.java b/client/src/com/vaadin/client/ui/VUnknownComponent.java deleted file mode 100644 index 89907854de..0000000000 --- a/client/src/com/vaadin/client/ui/VUnknownComponent.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.VerticalPanel; -import com.vaadin.client.SimpleTree; - -public class VUnknownComponent extends Composite { - - com.google.gwt.user.client.ui.Label caption = new com.google.gwt.user.client.ui.Label(); - SimpleTree uidlTree; - protected VerticalPanel panel; - - public VUnknownComponent() { - panel = new VerticalPanel(); - panel.add(caption); - initWidget(panel); - setStyleName("vaadin-unknown"); - caption.setStyleName("vaadin-unknown-caption"); - } - - public void setCaption(String c) { - caption.getElement().setInnerHTML(c); - } -} diff --git a/client/src/com/vaadin/client/ui/VUpload.java b/client/src/com/vaadin/client/ui/VUpload.java deleted file mode 100644 index 2800acccf9..0000000000 --- a/client/src/com/vaadin/client/ui/VUpload.java +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.FormElement; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.ui.FileUpload; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.FormPanel; -import com.google.gwt.user.client.ui.Hidden; -import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ConnectorMap; -import com.vaadin.client.StyleConstants; -import com.vaadin.client.VConsole; -import com.vaadin.client.ui.upload.UploadConnector; -import com.vaadin.client.ui.upload.UploadIFrameOnloadStrategy; -import com.vaadin.shared.ui.upload.UploadServerRpc; - -/** - * - * Note, we are not using GWT FormPanel as we want to listen submitcomplete - * events even though the upload component is already detached. - * - */ -public class VUpload extends SimplePanel { - - private final class MyFileUpload extends FileUpload { - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (event.getTypeInt() == Event.ONCHANGE) { - if (immediate && fu.getFilename() != null - && !"".equals(fu.getFilename())) { - submit(); - } - } else if (BrowserInfo.get().isIE() - && event.getTypeInt() == Event.ONFOCUS) { - // IE and user has clicked on hidden textarea part of upload - // field. Manually open file selector, other browsers do it by - // default. - fireNativeClick(fu.getElement()); - // also remove focus to enable hack if user presses cancel - // button - fireNativeBlur(fu.getElement()); - } - } - } - - public static final String CLASSNAME = "v-upload"; - - /** - * FileUpload component that opens native OS dialog to select file. - *

- * For internal use only. May be removed or replaced in the future. - */ - public FileUpload fu = new MyFileUpload(); - - Panel panel = new FlowPanel(); - - UploadIFrameOnloadStrategy onloadstrategy = GWT - .create(UploadIFrameOnloadStrategy.class); - - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - - /** For internal use only. May be removed or replaced in the future. */ - public String paintableId; - - /** - * Button that initiates uploading. - *

- * For internal use only. May be removed or replaced in the future. - */ - public final VButton submitButton; - - /** - * When expecting big files, programmer may initiate some UI changes when - * uploading the file starts. Bit after submitting file we'll visit the - * server to check possible changes. - *

- * For internal use only. May be removed or replaced in the future. - */ - public Timer t; - - /** - * some browsers tries to send form twice if submit is called in button - * click handler, some don't submit at all without it, so we need to track - * if form is already being submitted - */ - private boolean submitted = false; - - private boolean enabled = true; - - private boolean immediate; - - private Hidden maxfilesize = new Hidden(); - - /** For internal use only. May be removed or replaced in the future. */ - public FormElement element; - - private com.google.gwt.dom.client.Element synthesizedFrame; - - /** For internal use only. May be removed or replaced in the future. */ - public int nextUploadId; - - public VUpload() { - super(com.google.gwt.dom.client.Document.get().createFormElement()); - - element = getElement().cast(); - setEncoding(getElement(), FormPanel.ENCODING_MULTIPART); - element.setMethod(FormPanel.METHOD_POST); - - setWidget(panel); - panel.add(maxfilesize); - panel.add(fu); - submitButton = new VButton(); - submitButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - if (immediate) { - // fire click on upload (eg. focused button and hit space) - fireNativeClick(fu.getElement()); - } else { - submit(); - } - } - }); - panel.add(submitButton); - - setStyleName(CLASSNAME); - } - - private static native void setEncoding(Element form, String encoding) - /*-{ - form.enctype = encoding; - // For IE8 - form.encoding = encoding; - }-*/; - - /** For internal use only. May be removed or replaced in the future. */ - public void setImmediate(boolean booleanAttribute) { - if (immediate != booleanAttribute) { - immediate = booleanAttribute; - if (immediate) { - fu.sinkEvents(Event.ONCHANGE); - fu.sinkEvents(Event.ONFOCUS); - } - } - setStyleName(getElement(), CLASSNAME + "-immediate", immediate); - } - - private static native void fireNativeClick(Element element) - /*-{ - element.click(); - }-*/; - - private static native void fireNativeBlur(Element element) - /*-{ - element.blur(); - }-*/; - - /** For internal use only. May be removed or replaced in the future. */ - public void disableUpload() { - setEnabledForSubmitButton(false); - if (!submitted) { - // Cannot disable the fileupload while submitting or the file won't - // be submitted at all - fu.getElement().setPropertyBoolean("disabled", true); - } - enabled = false; - } - - /** For internal use only. May be removed or replaced in the future. */ - public void enableUpload() { - setEnabledForSubmitButton(true); - fu.getElement().setPropertyBoolean("disabled", false); - enabled = true; - if (submitted) { - /* - * An old request is still in progress (most likely cancelled), - * ditching that target frame to make it possible to send a new - * file. A new target frame is created later." - */ - cleanTargetFrame(); - submitted = false; - } - } - - private void setEnabledForSubmitButton(boolean enabled) { - submitButton.setEnabled(enabled); - submitButton.setStyleName(StyleConstants.DISABLED, !enabled); - } - - /** - * Re-creates file input field and populates panel. This is needed as we - * want to clear existing values from our current file input field. - */ - private void rebuildPanel() { - panel.remove(submitButton); - panel.remove(fu); - fu = new MyFileUpload(); - fu.setName(paintableId + "_file"); - fu.getElement().setPropertyBoolean("disabled", !enabled); - panel.add(fu); - panel.add(submitButton); - if (immediate) { - fu.sinkEvents(Event.ONCHANGE); - } - } - - /** - * Called by JSNI (hooked via {@link #onloadstrategy}) - */ - private void onSubmitComplete() { - /* Needs to be run dereferred to avoid various browser issues. */ - Scheduler.get().scheduleDeferred(new Command() { - @Override - public void execute() { - if (submitted) { - if (client != null) { - if (t != null) { - t.cancel(); - } - VConsole.log("VUpload:Submit complete"); - ((UploadConnector) ConnectorMap.get(client) - .getConnector(VUpload.this)).getRpcProxy( - UploadServerRpc.class).poll(); - } - - rebuildPanel(); - - submitted = false; - enableUpload(); - if (!isAttached()) { - /* - * Upload is complete when upload is already abandoned. - */ - cleanTargetFrame(); - } - } - } - }); - } - - ScheduledCommand startUploadCmd = new ScheduledCommand() { - - @Override - public void execute() { - element.submit(); - submitted = true; - - disableUpload(); - - /* - * Visit server a moment after upload has started to see possible - * changes from UploadStarted event. Will be cleared on complete. - * - * Must get the id here as the upload can finish before the timer - * expires and in that case nextUploadId has been updated and is - * wrong. - */ - final int thisUploadId = nextUploadId; - t = new Timer() { - @Override - public void run() { - // Only visit the server if the upload has not already - // finished - if (thisUploadId == nextUploadId) { - VConsole.log("Visiting server to see if upload started event changed UI."); - client.updateVariable(paintableId, "pollForStart", - thisUploadId, true); - } - } - }; - t.schedule(800); - } - - }; - - /** For internal use only. May be removed or replaced in the future. */ - public void submit() { - if (submitted || !enabled) { - VConsole.log("Submit cancelled (disabled or already submitted)"); - return; - } - if (fu.getFilename().length() == 0) { - VConsole.log("Submitting empty selection (no file)"); - } - // flush possibly pending variable changes, so they will be handled - // before upload - client.sendPendingVariableChanges(); - - // This is done as deferred because sendPendingVariableChanges is also - // deferred and we want to start the upload only after the changes have - // been sent to the server - Scheduler.get().scheduleDeferred(startUploadCmd); - } - - /** For internal use only. May be removed or replaced in the future. */ - public void disableTitle(boolean disable) { - if (disable) { - // Disable title attribute for upload element. - if (BrowserInfo.get().isChrome()) { - // In Chrome title has to be set to " " to make it invisible - fu.setTitle(" "); - } else if (BrowserInfo.get().isFirefox()) { - // In FF title has to be set to empty string to make it - // invisible - // Method setTitle removes title attribute when it's an empty - // string, so setAttribute() should be used here - fu.getElement().setAttribute("title", ""); - } - // For other browsers absent title doesn't show default tooltip for - // input element - } else { - fu.setTitle(null); - } - } - - @Override - protected void onAttach() { - super.onAttach(); - if (client != null) { - ensureTargetFrame(); - } - } - - /** For internal use only. May be removed or replaced in the future. */ - public void ensureTargetFrame() { - if (synthesizedFrame == null) { - // Attach a hidden IFrame to the form. This is the target iframe to - // which the form will be submitted. We have to create the iframe - // using innerHTML, because setting an iframe's 'name' property - // dynamically doesn't work on most browsers. - DivElement dummy = Document.get().createDivElement(); - dummy.setInnerHTML(""); - getWidget().browserElement = DOM.getFirstChild(getWidget() - .getElement()); - } - resourceElement = getWidget().browserElement; - objectElement = null; - setResourceUrl(getResourceUrl("src")); - clearBrowserElement = false; - } else { - VConsole.error("Unknown Embedded type '" + getWidget().type - + "'"); - } - } else if (uidl.hasAttribute("mimetype")) { - // remove old style name related to type - if (getWidget().type != null) { - getWidget().removeStyleName( - VEmbedded.CLASSNAME + "-" + getWidget().type); - } - // remove old style name related to mime type - if (getWidget().mimetype != null) { - getWidget().removeStyleName( - VEmbedded.CLASSNAME + "-" + getWidget().mimetype); - } - final String mime = uidl.getStringAttribute("mimetype"); - if (mime.equals("application/x-shockwave-flash")) { - getWidget().mimetype = "flash"; - // Handle embedding of Flash - getWidget().addStyleName(VEmbedded.CLASSNAME + "-flash"); - getWidget().setHTML(getWidget().createFlashEmbed(uidl)); - - } else if (mime.equals("image/svg+xml")) { - getWidget().mimetype = "svg"; - getWidget().addStyleName(VEmbedded.CLASSNAME + "-svg"); - String data; - Map parameters = VEmbedded.getParameters(uidl); - ObjectElement obj = Document.get().createObjectElement(); - resourceElement = null; - if (parameters.get("data") == null) { - objectElement = obj; - data = getResourceUrl("src"); - setResourceUrl(data); - } else { - objectElement = null; - data = "data:image/svg+xml," + parameters.get("data"); - obj.setData(data); - } - getWidget().setHTML(""); - obj.setType(mime); - if (!isUndefinedWidth()) { - obj.getStyle().setProperty("width", "100%"); - } - if (!isUndefinedHeight()) { - obj.getStyle().setProperty("height", "100%"); - } - if (uidl.hasAttribute("classid")) { - obj.setAttribute("classid", - uidl.getStringAttribute("classid")); - } - if (uidl.hasAttribute("codebase")) { - obj.setAttribute("codebase", - uidl.getStringAttribute("codebase")); - } - if (uidl.hasAttribute("codetype")) { - obj.setAttribute("codetype", - uidl.getStringAttribute("codetype")); - } - if (uidl.hasAttribute("archive")) { - obj.setAttribute("archive", - uidl.getStringAttribute("archive")); - } - if (uidl.hasAttribute("standby")) { - obj.setAttribute("standby", - uidl.getStringAttribute("standby")); - } - getWidget().getElement().appendChild(obj); - if (uidl.hasAttribute(EmbeddedConstants.ALTERNATE_TEXT)) { - obj.setInnerText(uidl - .getStringAttribute(EmbeddedConstants.ALTERNATE_TEXT)); - } - } else { - VConsole.error("Unknown Embedded mimetype '" + mime + "'"); - } - } else { - VConsole.error("Unknown Embedded; no type or mimetype attribute"); - } - - if (clearBrowserElement) { - getWidget().browserElement = null; - } - } - - @Override - public VEmbedded getWidget() { - return (VEmbedded) super.getWidget(); - } - - @Override - public EmbeddedState getState() { - return (EmbeddedState) super.getState(); - } - - protected final ClickEventHandler clickEventHandler = new ClickEventHandler( - this) { - - @Override - protected void fireClick(NativeEvent event, - MouseEventDetails mouseDetails) { - getRpcProxy(EmbeddedServerRpc.class).click(mouseDetails); - } - - }; - -} diff --git a/client/src/com/vaadin/client/ui/flash/FlashConnector.java b/client/src/com/vaadin/client/ui/flash/FlashConnector.java deleted file mode 100644 index e859e9cbf1..0000000000 --- a/client/src/com/vaadin/client/ui/flash/FlashConnector.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.flash; - -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractComponentConnector; -import com.vaadin.client.ui.VFlash; -import com.vaadin.shared.ui.AbstractEmbeddedState; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.flash.FlashState; - -@Connect(com.vaadin.ui.Flash.class) -public class FlashConnector extends AbstractComponentConnector { - - @Override - public VFlash getWidget() { - return (VFlash) super.getWidget(); - } - - @Override - public FlashState getState() { - return (FlashState) super.getState(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - - super.onStateChanged(stateChangeEvent); - - getWidget().setSource( - getResourceUrl(AbstractEmbeddedState.SOURCE_RESOURCE)); - getWidget().setArchive(getState().archive); - getWidget().setClassId(getState().classId); - getWidget().setCodebase(getState().codebase); - getWidget().setCodetype(getState().codetype); - getWidget().setStandby(getState().standby); - getWidget().setAlternateText(getState().alternateText); - getWidget().setEmbedParams(getState().embedParams); - - getWidget().rebuildIfNeeded(); - } -} diff --git a/client/src/com/vaadin/client/ui/form/FormConnector.java b/client/src/com/vaadin/client/ui/form/FormConnector.java deleted file mode 100644 index 857c2bd40e..0000000000 --- a/client/src/com/vaadin/client/ui/form/FormConnector.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.form; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorHierarchyChangeEvent; -import com.vaadin.client.LayoutManager; -import com.vaadin.client.Paintable; -import com.vaadin.client.TooltipInfo; -import com.vaadin.client.UIDL; -import com.vaadin.client.VCaption; -import com.vaadin.client.ui.AbstractComponentContainerConnector; -import com.vaadin.client.ui.ShortcutActionHandler; -import com.vaadin.client.ui.VForm; -import com.vaadin.client.ui.layout.ElementResizeEvent; -import com.vaadin.client.ui.layout.ElementResizeListener; -import com.vaadin.client.ui.layout.MayScrollChildren; -import com.vaadin.shared.ui.ComponentStateUtil; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.form.FormState; -import com.vaadin.ui.Form; - -@Connect(Form.class) -public class FormConnector extends AbstractComponentContainerConnector - implements Paintable, MayScrollChildren { - - private final ElementResizeListener footerResizeListener = new ElementResizeListener() { - @Override - public void onElementResize(ElementResizeEvent e) { - VForm form = getWidget(); - - LayoutManager lm = getLayoutManager(); - int footerHeight = 0; - if (form.footer != null) { - footerHeight += lm.getOuterHeight(form.footer.getElement()); - } - - if (form.errorMessage.isVisible()) { - footerHeight += lm.getOuterHeight(form.errorMessage - .getElement()); - footerHeight -= lm.getMarginTop(form.errorMessage.getElement()); - form.errorMessage.getElement().getStyle() - .setMarginTop(-footerHeight, Unit.PX); - form.footerContainer.getStyle().clearMarginTop(); - } else { - form.footerContainer.getStyle().setMarginTop(-footerHeight, - Unit.PX); - } - - form.fieldContainer.getStyle().setPaddingBottom(footerHeight, - Unit.PX); - } - }; - - @Override - protected void init() { - getLayoutManager().addElementResizeListener( - getWidget().errorMessage.getElement(), footerResizeListener); - } - - @Override - public void onUnregister() { - VForm form = getWidget(); - getLayoutManager().removeElementResizeListener( - form.errorMessage.getElement(), footerResizeListener); - if (form.footer != null) { - getLayoutManager().removeElementResizeListener( - form.footer.getElement(), footerResizeListener); - } - } - - @Override - public boolean delegateCaptionHandling() { - return false; - } - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - getWidget().client = client; - getWidget().id = uidl.getId(); - - if (!isRealUpdate(uidl)) { - return; - } - - boolean legendEmpty = true; - if (getState().caption != null) { - VCaption.setCaptionText(getWidget().caption, getState()); - legendEmpty = false; - } else { - getWidget().caption.setInnerText(""); - } - if (getWidget().icon != null) { - getWidget().legend.removeChild(getWidget().icon.getElement()); - } - if (getIconUri() != null) { - getWidget().icon = client.getIcon(getIconUri()); - getWidget().legend.insertFirst(getWidget().icon.getElement()); - - legendEmpty = false; - } - if (legendEmpty) { - getWidget().addStyleDependentName("nocaption"); - } else { - getWidget().removeStyleDependentName("nocaption"); - } - - if (null != getState().errorMessage) { - getWidget().errorMessage.updateMessage(getState().errorMessage); - getWidget().errorMessage.setVisible(true); - } else { - getWidget().errorMessage.setVisible(false); - } - - if (ComponentStateUtil.hasDescription(getState())) { - getWidget().desc.setInnerHTML(getState().description); - if (getWidget().desc.getParentElement() == null) { - getWidget().fieldSet.insertAfter(getWidget().desc, - getWidget().legend); - } - } else { - getWidget().desc.setInnerHTML(""); - if (getWidget().desc.getParentElement() != null) { - getWidget().fieldSet.removeChild(getWidget().desc); - } - } - - // also recalculates size of the footer if undefined size form - see - // #3710 - client.runDescendentsLayout(getWidget()); - - // We may have actions attached - if (uidl.getChildCount() >= 1) { - UIDL childUidl = uidl.getChildByTagName("actions"); - if (childUidl != null) { - if (getWidget().shortcutHandler == null) { - getWidget().shortcutHandler = new ShortcutActionHandler( - getConnectorId(), client); - getWidget().keyDownRegistration = getWidget() - .addDomHandler(getWidget(), KeyDownEvent.getType()); - } - getWidget().shortcutHandler.updateActionMap(childUidl); - } - } else if (getWidget().shortcutHandler != null) { - getWidget().keyDownRegistration.removeHandler(); - getWidget().shortcutHandler = null; - getWidget().keyDownRegistration = null; - } - } - - @Override - public void updateCaption(ComponentConnector component) { - // NOP form don't render caption for neither field layout nor footer - // layout - } - - @Override - public VForm getWidget() { - return (VForm) super.getWidget(); - } - - @Override - public boolean isReadOnly() { - return super.isReadOnly() || getState().propertyReadOnly; - } - - @Override - public FormState getState() { - return (FormState) super.getState(); - } - - private ComponentConnector getFooter() { - return (ComponentConnector) getState().footer; - } - - private ComponentConnector getLayout() { - return (ComponentConnector) getState().layout; - } - - @Override - public void onConnectorHierarchyChange( - ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) { - Widget newFooterWidget = null; - ComponentConnector footer = getFooter(); - - if (footer != null) { - newFooterWidget = footer.getWidget(); - Widget currentFooter = getWidget().footer; - if (currentFooter != null) { - // Remove old listener - getLayoutManager().removeElementResizeListener( - currentFooter.getElement(), footerResizeListener); - } - getLayoutManager().addElementResizeListener( - newFooterWidget.getElement(), footerResizeListener); - } - getWidget().setFooterWidget(newFooterWidget); - - Widget newLayoutWidget = null; - ComponentConnector newLayout = getLayout(); - if (newLayout != null) { - newLayoutWidget = newLayout.getWidget(); - } - getWidget().setLayoutWidget(newLayoutWidget); - } - - @Override - public TooltipInfo getTooltipInfo(Element element) { - // Form shows its description and error message - // as a part of the actual layout - return null; - } - - @Override - public boolean hasTooltip() { - return false; - } -} diff --git a/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java b/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java deleted file mode 100644 index 3f0b4345c4..0000000000 --- a/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.formlayout; - -import java.util.HashMap; -import java.util.Map; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorHierarchyChangeEvent; -import com.vaadin.client.LayoutManager; -import com.vaadin.client.TooltipInfo; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractFieldConnector; -import com.vaadin.client.ui.AbstractLayoutConnector; -import com.vaadin.client.ui.PostLayoutListener; -import com.vaadin.client.ui.VFormLayout; -import com.vaadin.client.ui.VFormLayout.Caption; -import com.vaadin.client.ui.VFormLayout.ErrorFlag; -import com.vaadin.client.ui.VFormLayout.VFormLayoutTable; -import com.vaadin.client.ui.layout.ElementResizeEvent; -import com.vaadin.client.ui.layout.ElementResizeListener; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.MarginInfo; -import com.vaadin.shared.ui.orderedlayout.FormLayoutState; -import com.vaadin.ui.FormLayout; - -@Connect(FormLayout.class) -public class FormLayoutConnector extends AbstractLayoutConnector implements - PostLayoutListener { - - private Map oldMaxWidths = null; - - private static final ElementResizeListener dummyFirstCellResizeListener = new ElementResizeListener() { - @Override - public void onElementResize(ElementResizeEvent e) { - // Ignore event, listener added just to make measurements available - } - }; - - // Detects situations when there's something inside the FormLayout that - // prevents it from shrinking - private ElementResizeListener resizeListener = new ElementResizeListener() { - @Override - public void onElementResize(ElementResizeEvent e) { - LayoutManager layoutManager = getLayoutManager(); - double tableWidth = layoutManager - .getOuterWidthDouble(getWidget().table.getElement()); - double ownWidth = layoutManager.getInnerWidthDouble(getWidget() - .getElement()); - if (ownWidth < tableWidth) { - // Something inside the table prevents it from shrinking, - // temporarily force column widths - double excessWidth = tableWidth - ownWidth; - - // All td elements in the component column have the same width, - // so we only need to check the width of the first one to know - // how wide the column is. - Element firstComponentTd = findFirstComponentTd(); - if (firstComponentTd == null) { - // Can't do anything if there are no rows - return; - } - - double componentColWidth = layoutManager - .getOuterWidthDouble(firstComponentTd); - - if (componentColWidth == -1) { - // Didn't get a proper width reading, best to not touch - // anything - return; - } - - // Restrict content td width - // Round down to prevent interactions with fractional sizes of - // other columns - int targetWidth = (int) Math.floor(componentColWidth - - excessWidth); - - // Target might be negative if captions are wider than the total - // available width - targetWidth = Math.max(0, targetWidth); - - if (oldMaxWidths == null) { - oldMaxWidths = new HashMap(); - } - - for (ComponentConnector child : getChildComponents()) { - Element childElement = child.getWidget().getElement(); - if (!oldMaxWidths.containsKey(child)) { - oldMaxWidths.put(child, - childElement.getPropertyString("maxWidth")); - } - childElement.getStyle().setPropertyPx("maxWidth", - targetWidth); - layoutManager.reportOuterWidth(child, targetWidth); - } - } - } - }; - - @Override - protected void init() { - super.init(); - getLayoutManager().addElementResizeListener( - getWidget().table.getElement(), resizeListener); - getLayoutManager().addElementResizeListener(getWidget().getElement(), - resizeListener); - addComponentCellListener(); - } - - @Override - public void onUnregister() { - getLayoutManager().removeElementResizeListener( - getWidget().table.getElement(), resizeListener); - getLayoutManager().removeElementResizeListener( - getWidget().getElement(), resizeListener); - removeComponentCellListener(); - super.onUnregister(); - } - - @Override - public FormLayoutState getState() { - return (FormLayoutState) super.getState(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - VFormLayoutTable formLayoutTable = getWidget().table; - - formLayoutTable.setMargins(new MarginInfo(getState().marginsBitmask)); - formLayoutTable.setSpacing(getState().spacing); - - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - VFormLayout formLayout = getWidget(); - VFormLayoutTable formLayoutTable = getWidget().table; - - removeComponentCellListener(); - - int childId = 0; - - formLayoutTable.setRowCount(getChildComponents().size()); - - for (ComponentConnector child : getChildComponents()) { - Widget childWidget = child.getWidget(); - - Caption caption = formLayoutTable.getCaption(childWidget); - if (caption == null) { - caption = formLayout.new Caption(child); - caption.addClickHandler(formLayoutTable); - } - - ErrorFlag error = formLayoutTable.getError(childWidget); - if (error == null) { - error = formLayout.new ErrorFlag(child); - } - - formLayoutTable.setChild(childId, childWidget, caption, error); - childId++; - } - - for (ComponentConnector oldChild : event.getOldChildren()) { - if (oldChild.getParent() == this) { - continue; - } - - formLayoutTable.cleanReferences(oldChild.getWidget()); - } - - addComponentCellListener(); - } - - private void addComponentCellListener() { - Element td = findFirstComponentTd(); - if (td != null) { - getLayoutManager().addElementResizeListener(td, - dummyFirstCellResizeListener); - } - } - - private void removeComponentCellListener() { - Element td = findFirstComponentTd(); - if (td != null) { - getLayoutManager().removeElementResizeListener(td, - dummyFirstCellResizeListener); - } - } - - private Element findFirstComponentTd() { - VFormLayoutTable table = getWidget().table; - if (table.getRowCount() == 0) { - return null; - } else { - return table.getCellFormatter().getElement(0, - VFormLayoutTable.COLUMN_WIDGET); - } - } - - @Override - public void updateCaption(ComponentConnector component) { - getWidget().table.updateCaption(component.getWidget(), - component.getState(), component.isEnabled()); - boolean hideErrors = false; - - // FIXME This incorrectly depends on AbstractFieldConnector - if (component instanceof AbstractFieldConnector) { - hideErrors = ((AbstractFieldConnector) component).getState().hideErrors; - } - - getWidget().table.updateError(component.getWidget(), - component.getState().errorMessage, hideErrors); - } - - @Override - public VFormLayout getWidget() { - return (VFormLayout) super.getWidget(); - } - - @Override - public TooltipInfo getTooltipInfo(Element element) { - TooltipInfo info = null; - - if (element != getWidget().getElement()) { - Object node = WidgetUtil.findWidget(element, - VFormLayout.Caption.class); - - if (node != null) { - VFormLayout.Caption caption = (VFormLayout.Caption) node; - info = caption.getOwner().getTooltipInfo(element); - } else { - - node = WidgetUtil.findWidget(element, - VFormLayout.ErrorFlag.class); - - if (node != null) { - VFormLayout.ErrorFlag flag = (VFormLayout.ErrorFlag) node; - info = flag.getOwner().getTooltipInfo(element); - } - } - } - - if (info == null) { - info = super.getTooltipInfo(element); - } - - return info; - } - - @Override - public boolean hasTooltip() { - /* - * Tooltips are fetched from child connectors -> there's no quick way of - * checking whether there might a tooltip hiding somewhere - */ - return true; - } - - @Override - public void postLayout() { - if (oldMaxWidths != null) { - for (ComponentConnector child : getChildComponents()) { - Element childNode = child.getWidget().getElement(); - String oldValue = oldMaxWidths.get(child); - if (oldValue == null) { - childNode.getStyle().clearProperty("maxWidth"); - } else { - childNode.getStyle().setProperty("maxWidth", oldValue); - } - } - oldMaxWidths = null; - } - } - -} diff --git a/client/src/com/vaadin/client/ui/gridlayout/GridLayoutConnector.java b/client/src/com/vaadin/client/ui/gridlayout/GridLayoutConnector.java deleted file mode 100644 index 4d1ce692ad..0000000000 --- a/client/src/com/vaadin/client/ui/gridlayout/GridLayoutConnector.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.gridlayout; - -import java.util.Map.Entry; - -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorHierarchyChangeEvent; -import com.vaadin.client.DirectionalManagedLayout; -import com.vaadin.client.Paintable; -import com.vaadin.client.UIDL; -import com.vaadin.client.VCaption; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractComponentContainerConnector; -import com.vaadin.client.ui.LayoutClickEventHandler; -import com.vaadin.client.ui.VGridLayout; -import com.vaadin.client.ui.VGridLayout.Cell; -import com.vaadin.client.ui.layout.VLayoutSlot; -import com.vaadin.shared.Connector; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.LayoutClickRpc; -import com.vaadin.shared.ui.MarginInfo; -import com.vaadin.shared.ui.gridlayout.GridLayoutServerRpc; -import com.vaadin.shared.ui.gridlayout.GridLayoutState; -import com.vaadin.shared.ui.gridlayout.GridLayoutState.ChildComponentData; -import com.vaadin.ui.GridLayout; - -@Connect(GridLayout.class) -public class GridLayoutConnector extends AbstractComponentContainerConnector - implements Paintable, DirectionalManagedLayout { - - private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( - this) { - - @Override - protected ComponentConnector getChildComponent( - com.google.gwt.user.client.Element element) { - return getWidget().getComponent(element); - } - - @Override - protected LayoutClickRpc getLayoutClickRPC() { - return getRpcProxy(GridLayoutServerRpc.class); - } - - }; - - @Override - public void init() { - super.init(); - getWidget().client = getConnection(); - - getLayoutManager().registerDependency(this, - getWidget().spacingMeasureElement); - } - - @Override - public void onUnregister() { - VGridLayout layout = getWidget(); - getLayoutManager().unregisterDependency(this, - layout.spacingMeasureElement); - - // Unregister caption size dependencies - for (ComponentConnector child : getChildComponents()) { - Cell cell = layout.widgetToCell.get(child.getWidget()); - cell.slot.setCaption(null); - } - } - - @Override - public GridLayoutState getState() { - return (GridLayoutState) super.getState(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - clickEventHandler.handleEventHandlerRegistration(); - - getWidget().hideEmptyRowsAndColumns = getState().hideEmptyRowsAndColumns; - - } - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - VGridLayout layout = getWidget(); - - if (!isRealUpdate(uidl)) { - return; - } - - initSize(); - - for (Entry entry : getState().childData - .entrySet()) { - ComponentConnector child = (ComponentConnector) entry.getKey(); - - Cell cell = getCell(child); - - ChildComponentData childComponentData = entry.getValue(); - cell.updateCell(childComponentData); - } - - layout.colExpandRatioArray = uidl.getIntArrayAttribute("colExpand"); - layout.rowExpandRatioArray = uidl.getIntArrayAttribute("rowExpand"); - - layout.updateMarginStyleNames(new MarginInfo(getState().marginsBitmask)); - layout.updateSpacingStyleName(getState().spacing); - getLayoutManager().setNeedsLayout(this); - } - - private Cell getCell(ComponentConnector child) { - VGridLayout layout = getWidget(); - Cell cell = layout.widgetToCell.get(child.getWidget()); - - if (cell == null) { - ChildComponentData childComponentData = getState().childData - .get(child); - int row = childComponentData.row1; - int col = childComponentData.column1; - - cell = layout.createNewCell(row, col); - } - return cell; - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - VGridLayout layout = getWidget(); - - // clean non rendered components - for (ComponentConnector oldChild : event.getOldChildren()) { - if (oldChild.getParent() == this) { - continue; - } - - Widget childWidget = oldChild.getWidget(); - layout.remove(childWidget); - } - - initSize(); - - for (ComponentConnector componentConnector : getChildComponents()) { - Cell cell = getCell(componentConnector); - - cell.setComponent(componentConnector, getChildComponents()); - } - - } - - private void initSize() { - VGridLayout layout = getWidget(); - int cols = getState().columns; - int rows = getState().rows; - - layout.columnWidths = new int[cols]; - layout.rowHeights = new int[rows]; - layout.explicitRowRatios = getState().explicitRowRatios; - layout.explicitColRatios = getState().explicitColRatios; - layout.setSize(rows, cols); - } - - @Override - public void updateCaption(ComponentConnector childConnector) { - VGridLayout layout = getWidget(); - Cell cell = layout.widgetToCell.get(childConnector.getWidget()); - if (VCaption.isNeeded(childConnector.getState())) { - VLayoutSlot layoutSlot = cell.slot; - VCaption caption = layoutSlot.getCaption(); - if (caption == null) { - caption = new VCaption(childConnector, getConnection()); - Widget widget = childConnector.getWidget(); - - layout.setCaption(widget, caption); - } - caption.updateCaption(); - } else { - layout.setCaption(childConnector.getWidget(), null); - getLayoutManager().setNeedsLayout(this); - } - } - - @Override - public VGridLayout getWidget() { - return (VGridLayout) super.getWidget(); - } - - @Override - public void layoutVertically() { - getWidget().updateHeight(); - } - - @Override - public void layoutHorizontally() { - getWidget().updateWidth(); - } - - @Override - protected void updateWidgetSize(String newWidth, String newHeight) { - // Prevent the element from momentarily shrinking to zero size - // when the size is set to undefined by a state change but before - // it is recomputed in the layout phase. This may affect scroll - // position in some cases; see #13386. - if (!isUndefinedHeight()) { - getWidget().setHeight(newHeight); - } - if (!isUndefinedWidth()) { - getWidget().setWidth(newWidth); - } - } -} diff --git a/client/src/com/vaadin/client/ui/image/ImageConnector.java b/client/src/com/vaadin/client/ui/image/ImageConnector.java deleted file mode 100644 index e4ba4af070..0000000000 --- a/client/src/com/vaadin/client/ui/image/ImageConnector.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.image; - -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.event.dom.client.LoadEvent; -import com.google.gwt.event.dom.client.LoadHandler; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractComponentConnector; -import com.vaadin.client.ui.ClickEventHandler; -import com.vaadin.client.ui.VImage; -import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.ui.AbstractEmbeddedState; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.image.ImageServerRpc; -import com.vaadin.shared.ui.image.ImageState; - -@Connect(com.vaadin.ui.Image.class) -public class ImageConnector extends AbstractComponentConnector { - - @Override - protected void init() { - super.init(); - getWidget().addHandler(new LoadHandler() { - - @Override - public void onLoad(LoadEvent event) { - getLayoutManager().setNeedsMeasure(ImageConnector.this); - } - - }, LoadEvent.getType()); - } - - @Override - public VImage getWidget() { - return (VImage) super.getWidget(); - } - - @Override - public ImageState getState() { - return (ImageState) super.getState(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - clickEventHandler.handleEventHandlerRegistration(); - - String url = getResourceUrl(AbstractEmbeddedState.SOURCE_RESOURCE); - getWidget().setUrl(url != null ? url : ""); - - String alt = getState().alternateText; - // Some browsers turn a null alt text into a literal "null" - getWidget().setAltText(alt != null ? alt : ""); - } - - protected final ClickEventHandler clickEventHandler = new ClickEventHandler( - this) { - - @Override - protected void fireClick(NativeEvent event, - MouseEventDetails mouseDetails) { - getRpcProxy(ImageServerRpc.class).click(mouseDetails); - } - - }; - -} diff --git a/client/src/com/vaadin/client/ui/label/LabelConnector.java b/client/src/com/vaadin/client/ui/label/LabelConnector.java deleted file mode 100644 index fc94f27cf0..0000000000 --- a/client/src/com/vaadin/client/ui/label/LabelConnector.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.label; - -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.PreElement; -import com.vaadin.client.Profiler; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractComponentConnector; -import com.vaadin.client.ui.VLabel; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.Connect.LoadStyle; -import com.vaadin.shared.ui.label.LabelState; -import com.vaadin.ui.Label; - -@Connect(value = Label.class, loadStyle = LoadStyle.EAGER) -public class LabelConnector extends AbstractComponentConnector { - - @Override - public LabelState getState() { - return (LabelState) super.getState(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - boolean sinkOnloads = false; - Profiler.enter("LabelConnector.onStateChanged update content"); - switch (getState().contentMode) { - case PREFORMATTED: - PreElement preElement = Document.get().createPreElement(); - preElement.setInnerText(getState().text); - // clear existing content - getWidget().setHTML(""); - // add preformatted text to dom - getWidget().getElement().appendChild(preElement); - break; - - case TEXT: - getWidget().setText(getState().text); - break; - - case HTML: - case RAW: - sinkOnloads = true; - case XML: - getWidget().setHTML(getState().text); - break; - default: - getWidget().setText(""); - break; - - } - Profiler.leave("LabelConnector.onStateChanged update content"); - - if (sinkOnloads) { - Profiler.enter("LabelConnector.onStateChanged sinkOnloads"); - WidgetUtil.sinkOnloadForImages(getWidget().getElement()); - Profiler.leave("LabelConnector.onStateChanged sinkOnloads"); - } - } - - @Override - public VLabel getWidget() { - return (VLabel) super.getWidget(); - } - -} diff --git a/client/src/com/vaadin/client/ui/layout/ComponentConnectorLayoutSlot.java b/client/src/com/vaadin/client/ui/layout/ComponentConnectorLayoutSlot.java deleted file mode 100644 index b323fde1db..0000000000 --- a/client/src/com/vaadin/client/ui/layout/ComponentConnectorLayoutSlot.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.layout; - -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.LayoutManager; -import com.vaadin.client.VCaption; -import com.vaadin.client.ui.ManagedLayout; - -public class ComponentConnectorLayoutSlot extends VLayoutSlot { - - final ComponentConnector child; - final ManagedLayout layout; - - public ComponentConnectorLayoutSlot(String baseClassName, - ComponentConnector child, ManagedLayout layout) { - super(baseClassName, child.getWidget()); - this.child = child; - this.layout = layout; - } - - public ComponentConnector getChild() { - return child; - } - - @Override - protected int getCaptionHeight() { - VCaption caption = getCaption(); - return caption != null ? getLayoutManager().getOuterHeight( - caption.getElement()) : 0; - } - - @Override - protected int getCaptionWidth() { - VCaption caption = getCaption(); - return caption != null ? getLayoutManager().getOuterWidth( - caption.getElement()) : 0; - } - - public LayoutManager getLayoutManager() { - return layout.getLayoutManager(); - } - - @Override - public void setCaption(VCaption caption) { - VCaption oldCaption = getCaption(); - if (oldCaption != null) { - getLayoutManager().unregisterDependency(layout, - oldCaption.getElement()); - } - super.setCaption(caption); - if (caption != null) { - getLayoutManager().registerDependency( - (ManagedLayout) child.getParent(), caption.getElement()); - } - } - - @Override - protected void reportActualRelativeHeight(int allocatedHeight) { - getLayoutManager().reportOuterHeight(child, allocatedHeight); - } - - @Override - protected void reportActualRelativeWidth(int allocatedWidth) { - getLayoutManager().reportOuterWidth(child, allocatedWidth); - } - - @Override - public int getWidgetHeight() { - return getLayoutManager() - .getOuterHeight(child.getWidget().getElement()); - } - - @Override - public int getWidgetWidth() { - return getLayoutManager().getOuterWidth(child.getWidget().getElement()); - } - - @Override - public boolean isUndefinedHeight() { - return child.isUndefinedHeight(); - } - - @Override - public boolean isUndefinedWidth() { - return child.isUndefinedWidth(); - } - - @Override - public boolean isRelativeHeight() { - return child.isRelativeHeight(); - } - - @Override - public boolean isRelativeWidth() { - return child.isRelativeWidth(); - } -} diff --git a/client/src/com/vaadin/client/ui/layout/ElementResizeEvent.java b/client/src/com/vaadin/client/ui/layout/ElementResizeEvent.java deleted file mode 100644 index a1f75baff4..0000000000 --- a/client/src/com/vaadin/client/ui/layout/ElementResizeEvent.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.layout; - -import com.google.gwt.dom.client.Element; -import com.vaadin.client.LayoutManager; - -public class ElementResizeEvent { - private final Element element; - private final LayoutManager layoutManager; - - public ElementResizeEvent(LayoutManager layoutManager, Element element) { - this.layoutManager = layoutManager; - this.element = element; - } - - public Element getElement() { - return element; - } - - public LayoutManager getLayoutManager() { - return layoutManager; - } -} diff --git a/client/src/com/vaadin/client/ui/layout/ElementResizeListener.java b/client/src/com/vaadin/client/ui/layout/ElementResizeListener.java deleted file mode 100644 index 97ca34a8a4..0000000000 --- a/client/src/com/vaadin/client/ui/layout/ElementResizeListener.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.layout; - -public interface ElementResizeListener { - public void onElementResize(ElementResizeEvent e); -} diff --git a/client/src/com/vaadin/client/ui/layout/LayoutDependencyTree.java b/client/src/com/vaadin/client/ui/layout/LayoutDependencyTree.java deleted file mode 100644 index 27733bfbe3..0000000000 --- a/client/src/com/vaadin/client/ui/layout/LayoutDependencyTree.java +++ /dev/null @@ -1,763 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.layout; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.logging.Logger; - -import com.google.gwt.core.client.JsArrayString; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorMap; -import com.vaadin.client.FastStringMap; -import com.vaadin.client.FastStringSet; -import com.vaadin.client.HasComponentsConnector; -import com.vaadin.client.JsArrayObject; -import com.vaadin.client.Profiler; -import com.vaadin.client.ServerConnector; -import com.vaadin.client.Util; -import com.vaadin.client.VConsole; -import com.vaadin.client.ui.ManagedLayout; -import com.vaadin.shared.AbstractComponentState; - -/** - * Internal class used to keep track of layout dependencies during one layout - * run. This class is not intended to be used directly by applications. - * - * @author Vaadin Ltd - * @since 7.0.0 - */ -public class LayoutDependencyTree { - private class LayoutDependency { - private final ComponentConnector connector; - private final int direction; - - private boolean needsLayout = false; - private boolean needsMeasure = false; - - private boolean scrollingParentCached = false; - private ComponentConnector scrollingBoundary = null; - - private FastStringSet measureBlockers = FastStringSet.create(); - private FastStringSet layoutBlockers = FastStringSet.create(); - - public LayoutDependency(ComponentConnector connector, int direction) { - this.connector = connector; - this.direction = direction; - } - - private void addLayoutBlocker(ComponentConnector blocker) { - String blockerId = blocker.getConnectorId(); - if (!layoutBlockers.contains(blockerId)) { - boolean wasEmpty = layoutBlockers.isEmpty(); - layoutBlockers.add(blockerId); - if (wasEmpty) { - if (needsLayout) { - getLayoutQueue(direction).remove( - connector.getConnectorId()); - } else { - // Propagation already done if needsLayout is set - propagatePotentialLayout(); - } - } - } - } - - private void removeLayoutBlocker(ComponentConnector blocker) { - String blockerId = blocker.getConnectorId(); - if (layoutBlockers.contains(blockerId)) { - layoutBlockers.remove(blockerId); - if (layoutBlockers.isEmpty()) { - if (needsLayout) { - getLayoutQueue(direction).add( - connector.getConnectorId()); - } else { - propagateNoUpcomingLayout(); - } - } - } - } - - private void addMeasureBlocker(ComponentConnector blocker) { - String blockerId = blocker.getConnectorId(); - boolean alreadyAdded = measureBlockers.contains(blockerId); - if (alreadyAdded) { - return; - } - boolean wasEmpty = measureBlockers.isEmpty(); - measureBlockers.add(blockerId); - if (wasEmpty) { - if (needsMeasure) { - getMeasureQueue(direction).remove( - connector.getConnectorId()); - } else { - propagatePotentialResize(); - } - } - } - - private void removeMeasureBlocker(ComponentConnector blocker) { - String blockerId = blocker.getConnectorId(); - boolean alreadyRemoved = !measureBlockers.contains(blockerId); - if (alreadyRemoved) { - return; - } - measureBlockers.remove(blockerId); - if (measureBlockers.isEmpty()) { - if (needsMeasure) { - getMeasureQueue(direction).add(connector.getConnectorId()); - } else { - propagateNoUpcomingResize(); - } - } - } - - public void setNeedsMeasure(boolean needsMeasure) { - if (needsMeasure && !this.needsMeasure) { - // If enabling needsMeasure - this.needsMeasure = needsMeasure; - - if (measureBlockers.isEmpty()) { - // Add to queue if there are no blockers - getMeasureQueue(direction).add(connector.getConnectorId()); - // Only need to propagate if not already propagated when - // setting blockers - propagatePotentialResize(); - } - } else if (!needsMeasure && this.needsMeasure - && measureBlockers.isEmpty()) { - // Only disable if there are no blockers (elements gets measured - // in both directions even if there is a blocker in one - // direction) - this.needsMeasure = needsMeasure; - getMeasureQueue(direction).remove(connector.getConnectorId()); - propagateNoUpcomingResize(); - } - } - - public void setNeedsLayout(boolean needsLayout) { - if (!(connector instanceof ManagedLayout)) { - throw new IllegalStateException( - "Only managed layouts can need layout, layout attempted for " - + Util.getConnectorString(connector)); - } - if (needsLayout && !this.needsLayout) { - // If enabling needsLayout - this.needsLayout = needsLayout; - - if (layoutBlockers.isEmpty()) { - // Add to queue if there are no blockers - getLayoutQueue(direction).add(connector.getConnectorId()); - // Only need to propagate if not already propagated when - // setting blockers - propagatePotentialLayout(); - } - } else if (!needsLayout && this.needsLayout - && layoutBlockers.isEmpty()) { - // Only disable if there are no layout blockers - // (SimpleManagedLayout gets layouted in both directions - // even if there is a blocker in one direction) - this.needsLayout = needsLayout; - getLayoutQueue(direction).remove(connector.getConnectorId()); - propagateNoUpcomingLayout(); - } - } - - private void propagatePotentialResize() { - JsArrayString needsSizeForLayout = getNeedsSizeForLayout(); - int length = needsSizeForLayout.length(); - for (int i = 0; i < length; i++) { - String needsSizeId = needsSizeForLayout.get(i); - LayoutDependency layoutDependency = getDependency(needsSizeId, - direction); - layoutDependency.addLayoutBlocker(connector); - } - } - - private JsArrayString getNeedsSizeForLayout() { - // Find all connectors that need the size of this connector for - // layouting - - // Parent needs size if it isn't relative? - // Connector itself needs size if it isn't undefined? - // Children doesn't care? - - JsArrayString needsSize = JsArrayObject.createArray().cast(); - - if (!isUndefinedInDirection(connector, direction)) { - needsSize.push(connector.getConnectorId()); - } - if (!isRelativeInDirection(connector, direction)) { - ServerConnector parent = connector.getParent(); - if (parent instanceof ComponentConnector) { - needsSize.push(parent.getConnectorId()); - } - } - - return needsSize; - } - - private void propagateNoUpcomingResize() { - JsArrayString needsSizeForLayout = getNeedsSizeForLayout(); - int length = needsSizeForLayout.length(); - for (int i = 0; i < length; i++) { - String mightNeedLayoutId = needsSizeForLayout.get(i); - LayoutDependency layoutDependency = getDependency( - mightNeedLayoutId, direction); - layoutDependency.removeLayoutBlocker(connector); - } - } - - private void propagatePotentialLayout() { - JsArrayString resizedByLayout = getResizedByLayout(); - int length = resizedByLayout.length(); - for (int i = 0; i < length; i++) { - String sizeMightChangeId = resizedByLayout.get(i); - LayoutDependency layoutDependency = getDependency( - sizeMightChangeId, direction); - layoutDependency.addMeasureBlocker(connector); - } - } - - private JsArrayString getResizedByLayout() { - // Components that might get resized by a layout of this component - - // Parent never resized - // Connector itself resized if undefined - // Children resized if relative - - JsArrayString resized = JsArrayObject.createArray().cast(); - if (isUndefinedInDirection(connector, direction)) { - resized.push(connector.getConnectorId()); - } - - if (connector instanceof HasComponentsConnector) { - HasComponentsConnector container = (HasComponentsConnector) connector; - for (ComponentConnector child : container.getChildComponents()) { - if (!Util.shouldSkipMeasurementOfConnector(child, connector) - && isRelativeInDirection(child, direction)) { - resized.push(child.getConnectorId()); - } - } - } - - return resized; - } - - private void propagateNoUpcomingLayout() { - JsArrayString resizedByLayout = getResizedByLayout(); - int length = resizedByLayout.length(); - for (int i = 0; i < length; i++) { - String sizeMightChangeId = resizedByLayout.get(i); - LayoutDependency layoutDependency = getDependency( - sizeMightChangeId, direction); - layoutDependency.removeMeasureBlocker(connector); - } - } - - public void markSizeAsChanged() { - Profiler.enter("LayoutDependency.markSizeAsChanged phase 1"); - // When the size has changed, all that use that size should be - // layouted - JsArrayString needsSizeForLayout = getNeedsSizeForLayout(); - int length = needsSizeForLayout.length(); - for (int i = 0; i < length; i++) { - String connectorId = needsSizeForLayout.get(i); - LayoutDependency layoutDependency = getDependency(connectorId, - direction); - if (layoutDependency.connector instanceof ManagedLayout) { - Profiler.enter("LayoutDependency.markSizeAsChanged setNeedsLayout"); - layoutDependency.setNeedsLayout(true); - Profiler.leave("LayoutDependency.markSizeAsChanged setNeedsLayout"); - } else { - Profiler.enter("LayoutDependency.markSizeAsChanged propagatePostLayoutMeasure"); - // Should simulate setNeedsLayout(true) + markAsLayouted -> - // propagate needs measure - layoutDependency.propagatePostLayoutMeasure(); - Profiler.leave("LayoutDependency.markSizeAsChanged propagatePostLayoutMeasure"); - } - } - Profiler.leave("LayoutDependency.markSizeAsChanged phase 1"); - - Profiler.enter("LayoutDependency.markSizeAsChanged scrollbars"); - // Should also go through the hierarchy to discover appeared or - // disappeared scrollbars - ComponentConnector scrollingBoundary = getScrollingBoundary(connector); - if (scrollingBoundary != null) { - getDependency(scrollingBoundary.getConnectorId(), - getOppositeDirection()).setNeedsMeasure(true); - } - Profiler.leave("LayoutDependency.markSizeAsChanged scrollbars"); - - } - - /** - * Go up the hierarchy to find a component whose size might have changed - * in the other direction because changes to this component causes - * scrollbars to appear or disappear. - * - * @return - */ - private LayoutDependency findPotentiallyChangedScrollbar() { - ComponentConnector currentConnector = connector; - while (true) { - ServerConnector parent = currentConnector.getParent(); - if (!(parent instanceof ComponentConnector)) { - return null; - } - if (parent instanceof MayScrollChildren) { - return getDependency(currentConnector.getConnectorId(), - getOppositeDirection()); - } - currentConnector = (ComponentConnector) parent; - } - } - - private int getOppositeDirection() { - return direction == HORIZONTAL ? VERTICAL : HORIZONTAL; - } - - public void markAsLayouted() { - if (!layoutBlockers.isEmpty()) { - // Don't do anything if there are layout blockers (SimpleLayout - // gets layouted in both directions even if one direction is - // blocked) - return; - } - setNeedsLayout(false); - propagatePostLayoutMeasure(); - } - - private void propagatePostLayoutMeasure() { - Profiler.enter("LayoutDependency.propagatePostLayoutMeasure getResizedByLayout"); - JsArrayString resizedByLayout = getResizedByLayout(); - Profiler.leave("LayoutDependency.propagatePostLayoutMeasure getResizedByLayout"); - int length = resizedByLayout.length(); - for (int i = 0; i < length; i++) { - Profiler.enter("LayoutDependency.propagatePostLayoutMeasure setNeedsMeasure"); - String resizedId = resizedByLayout.get(i); - LayoutDependency layoutDependency = getDependency(resizedId, - direction); - layoutDependency.setNeedsMeasure(true); - Profiler.leave("LayoutDependency.propagatePostLayoutMeasure setNeedsMeasure"); - } - - // Special case for e.g. wrapping texts - Profiler.enter("LayoutDependency.propagatePostLayoutMeasure horizontal case"); - if (direction == HORIZONTAL && !connector.isUndefinedWidth() - && connector.isUndefinedHeight()) { - LayoutDependency dependency = getDependency( - connector.getConnectorId(), VERTICAL); - dependency.setNeedsMeasure(true); - } - Profiler.leave("LayoutDependency.propagatePostLayoutMeasure horizontal case"); - } - - @Override - public String toString() { - String s = getCompactConnectorString(connector) + "\n"; - if (direction == VERTICAL) { - s += "Vertical"; - } else { - s += "Horizontal"; - } - AbstractComponentState state = connector.getState(); - s += " sizing: " - + getSizeDefinition(direction == VERTICAL ? state.height - : state.width) + "\n"; - - if (needsLayout) { - s += "Needs layout\n"; - } - if (getLayoutQueue(direction).contains(connector.getConnectorId())) { - s += "In layout queue\n"; - } - s += "Layout blockers: " + blockersToString(layoutBlockers) + "\n"; - - if (needsMeasure) { - s += "Needs measure\n"; - } - if (getMeasureQueue(direction).contains(connector.getConnectorId())) { - s += "In measure queue\n"; - } - s += "Measure blockers: " + blockersToString(measureBlockers); - - return s; - } - - public boolean noMoreChangesExpected() { - return !needsLayout && !needsMeasure && layoutBlockers.isEmpty() - && measureBlockers.isEmpty(); - } - - } - - private static final int HORIZONTAL = 0; - private static final int VERTICAL = 1; - - @SuppressWarnings("unchecked") - private final FastStringMap[] dependenciesInDirection = new FastStringMap[] { - FastStringMap.create(), FastStringMap.create() }; - - private final FastStringSet[] measureQueueInDirection = new FastStringSet[] { - FastStringSet.create(), FastStringSet.create() }; - - private final FastStringSet[] layoutQueueInDirection = new FastStringSet[] { - FastStringSet.create(), FastStringSet.create() }; - - private final ApplicationConnection connection; - - public LayoutDependencyTree(ApplicationConnection connection) { - this.connection = connection; - } - - public void setNeedsMeasure(ComponentConnector connector, - boolean needsMeasure) { - setNeedsHorizontalMeasure(connector, needsMeasure); - setNeedsVerticalMeasure(connector, needsMeasure); - } - - /** - * @param connectorId - * @param needsMeasure - * - * @deprecated As of 7.4.2, use - * {@link #setNeedsMeasure(ComponentConnector, boolean)} for - * improved performance. - */ - @Deprecated - public void setNeedsMeasure(String connectorId, boolean needsMeasure) { - ComponentConnector connector = (ComponentConnector) ConnectorMap.get( - connection).getConnector(connectorId); - if (connector == null) { - return; - } - - setNeedsMeasure(connector, needsMeasure); - } - - public void setNeedsHorizontalMeasure(ComponentConnector connector, - boolean needsMeasure) { - LayoutDependency dependency = getDependency(connector, HORIZONTAL); - dependency.setNeedsMeasure(needsMeasure); - } - - public void setNeedsHorizontalMeasure(String connectorId, - boolean needsMeasure) { - // Ensure connector exists - ComponentConnector connector = (ComponentConnector) ConnectorMap.get( - connection).getConnector(connectorId); - if (connector == null) { - return; - } - - setNeedsHorizontalMeasure(connector, needsMeasure); - } - - public void setNeedsVerticalMeasure(ComponentConnector connector, - boolean needsMeasure) { - LayoutDependency dependency = getDependency(connector, VERTICAL); - dependency.setNeedsMeasure(needsMeasure); - } - - public void setNeedsVerticalMeasure(String connectorId, boolean needsMeasure) { - // Ensure connector exists - ComponentConnector connector = (ComponentConnector) ConnectorMap.get( - connection).getConnector(connectorId); - if (connector == null) { - return; - } - - setNeedsVerticalMeasure(connector, needsMeasure); - } - - private LayoutDependency getDependency(ComponentConnector connector, - int direction) { - return getDependency(connector.getConnectorId(), connector, direction); - } - - private LayoutDependency getDependency(String connectorId, int direction) { - return getDependency(connectorId, null, direction); - } - - private LayoutDependency getDependency(String connectorId, - ComponentConnector connector, int direction) { - FastStringMap dependencies = dependenciesInDirection[direction]; - LayoutDependency dependency = dependencies.get(connectorId); - if (dependency == null) { - if (connector == null) { - connector = (ComponentConnector) ConnectorMap.get(connection) - .getConnector(connectorId); - if (connector == null) { - getLogger().warning( - "No connector found for id " + connectorId - + " while creating LayoutDependency"); - return null; - } - } - dependency = new LayoutDependency(connector, direction); - dependencies.put(connectorId, dependency); - } - return dependency; - } - - private FastStringSet getLayoutQueue(int direction) { - return layoutQueueInDirection[direction]; - } - - private FastStringSet getMeasureQueue(int direction) { - return measureQueueInDirection[direction]; - } - - /** - * @param layout - * @param needsLayout - * - * @deprecated As of 7.0.1, use - * {@link #setNeedsHorizontalLayout(String, boolean)} for - * improved performance. - */ - @Deprecated - public void setNeedsHorizontalLayout(ManagedLayout layout, - boolean needsLayout) { - setNeedsHorizontalLayout(layout.getConnectorId(), needsLayout); - } - - public void setNeedsHorizontalLayout(String connectorId, boolean needsLayout) { - LayoutDependency dependency = getDependency(connectorId, HORIZONTAL); - if (dependency != null) { - dependency.setNeedsLayout(needsLayout); - } else { - getLogger().warning( - "No dependency found in setNeedsHorizontalLayout"); - } - } - - /** - * @param layout - * @param needsLayout - * - * @deprecated As of 7.0.1, use - * {@link #setNeedsVerticalLayout(String, boolean)} for improved - * performance. - */ - @Deprecated - public void setNeedsVerticalLayout(ManagedLayout layout, boolean needsLayout) { - setNeedsVerticalLayout(layout.getConnectorId(), needsLayout); - } - - public void setNeedsVerticalLayout(String connectorId, boolean needsLayout) { - LayoutDependency dependency = getDependency(connectorId, VERTICAL); - if (dependency != null) { - dependency.setNeedsLayout(needsLayout); - } else { - getLogger() - .warning("No dependency found in setNeedsVerticalLayout"); - } - - } - - public void markAsHorizontallyLayouted(ManagedLayout layout) { - LayoutDependency dependency = getDependency(layout.getConnectorId(), - HORIZONTAL); - dependency.markAsLayouted(); - } - - public void markAsVerticallyLayouted(ManagedLayout layout) { - LayoutDependency dependency = getDependency(layout.getConnectorId(), - VERTICAL); - dependency.markAsLayouted(); - } - - public void markHeightAsChanged(ComponentConnector connector) { - LayoutDependency dependency = getDependency(connector.getConnectorId(), - VERTICAL); - dependency.markSizeAsChanged(); - } - - public void markWidthAsChanged(ComponentConnector connector) { - LayoutDependency dependency = getDependency(connector.getConnectorId(), - HORIZONTAL); - dependency.markSizeAsChanged(); - } - - private static boolean isRelativeInDirection(ComponentConnector connector, - int direction) { - if (direction == HORIZONTAL) { - return connector.isRelativeWidth(); - } else { - return connector.isRelativeHeight(); - } - } - - private static boolean isUndefinedInDirection(ComponentConnector connector, - int direction) { - if (direction == VERTICAL) { - return connector.isUndefinedHeight(); - } else { - return connector.isUndefinedWidth(); - } - } - - private static String getCompactConnectorString(ServerConnector connector) { - return connector.getClass().getSimpleName() + " (" - + connector.getConnectorId() + ")"; - } - - private static String getSizeDefinition(String size) { - if (size == null || size.length() == 0) { - return "undefined"; - } else if (size.endsWith("%")) { - return "relative"; - } else { - return "fixed"; - } - } - - private String blockersToString(FastStringSet blockers) { - StringBuilder b = new StringBuilder("["); - - ConnectorMap connectorMap = ConnectorMap.get(connection); - JsArrayString blockersDump = blockers.dump(); - for (int i = 0; i < blockersDump.length(); i++) { - ServerConnector blocker = connectorMap.getConnector(blockersDump - .get(i)); - if (b.length() != 1) { - b.append(", "); - } - b.append(getCompactConnectorString(blocker)); - } - b.append(']'); - return b.toString(); - } - - public boolean hasConnectorsToMeasure() { - return !measureQueueInDirection[HORIZONTAL].isEmpty() - || !measureQueueInDirection[VERTICAL].isEmpty(); - } - - public boolean hasHorizontalConnectorToLayout() { - return !getLayoutQueue(HORIZONTAL).isEmpty(); - } - - public boolean hasVerticaConnectorToLayout() { - return !getLayoutQueue(VERTICAL).isEmpty(); - } - - /** - * @return - * @deprecated As of 7.0.1, use {@link #getHorizontalLayoutTargetsJsArray()} - * for improved performance. - */ - @Deprecated - public ManagedLayout[] getHorizontalLayoutTargets() { - return asManagedLayoutArray(getHorizontalLayoutTargetsJsArray()); - } - - /** - * @return - * @deprecated As of 7.0.1, use {@link #getVerticalLayoutTargetsJsArray()} - * for improved performance. - */ - @Deprecated - public ManagedLayout[] getVerticalLayoutTargets() { - return asManagedLayoutArray(getVerticalLayoutTargetsJsArray()); - } - - private ManagedLayout[] asManagedLayoutArray(JsArrayString connectorIdArray) { - int length = connectorIdArray.length(); - ConnectorMap connectorMap = ConnectorMap.get(connection); - ManagedLayout[] result = new ManagedLayout[length]; - for (int i = 0; i < length; i++) { - result[i] = (ManagedLayout) connectorMap - .getConnector(connectorIdArray.get(i)); - } - return result; - } - - public JsArrayString getHorizontalLayoutTargetsJsArray() { - return getLayoutQueue(HORIZONTAL).dump(); - } - - public JsArrayString getVerticalLayoutTargetsJsArray() { - return getLayoutQueue(VERTICAL).dump(); - } - - /** - * @return - * @deprecated As of 7.0.1, use {@link #getMeasureTargetsJsArray()} for - * improved performance. - */ - @Deprecated - public Collection getMeasureTargets() { - JsArrayString targetIds = getMeasureTargetsJsArray(); - int length = targetIds.length(); - ArrayList targets = new ArrayList( - length); - ConnectorMap connectorMap = ConnectorMap.get(connection); - - for (int i = 0; i < length; i++) { - targets.add((ComponentConnector) connectorMap - .getConnector(targetIds.get(i))); - } - return targets; - } - - public JsArrayString getMeasureTargetsJsArray() { - FastStringSet allMeasuredTargets = FastStringSet.create(); - allMeasuredTargets.addAll(getMeasureQueue(HORIZONTAL)); - allMeasuredTargets.addAll(getMeasureQueue(VERTICAL)); - return allMeasuredTargets.dump(); - } - - public void logDependencyStatus(ComponentConnector connector) { - VConsole.log("===="); - String connectorId = connector.getConnectorId(); - VConsole.log(getDependency(connectorId, HORIZONTAL).toString()); - VConsole.log(getDependency(connectorId, VERTICAL).toString()); - } - - public boolean noMoreChangesExpected(ComponentConnector connector) { - return getDependency(connector.getConnectorId(), HORIZONTAL) - .noMoreChangesExpected() - && getDependency(connector.getConnectorId(), VERTICAL) - .noMoreChangesExpected(); - } - - public ComponentConnector getScrollingBoundary(ComponentConnector connector) { - LayoutDependency dependency = getDependency(connector.getConnectorId(), - HORIZONTAL); - if (!dependency.scrollingParentCached) { - ServerConnector parent = dependency.connector.getParent(); - if (parent instanceof MayScrollChildren) { - dependency.scrollingBoundary = connector; - } else if (parent instanceof ComponentConnector) { - dependency.scrollingBoundary = getScrollingBoundary((ComponentConnector) parent); - } else { - // No scrolling parent - } - - dependency.scrollingParentCached = true; - } - return dependency.scrollingBoundary; - } - - private static Logger getLogger() { - return Logger.getLogger(LayoutDependencyTree.class.getName()); - } - -} diff --git a/client/src/com/vaadin/client/ui/layout/Margins.java b/client/src/com/vaadin/client/ui/layout/Margins.java deleted file mode 100644 index 75839c9ce8..0000000000 --- a/client/src/com/vaadin/client/ui/layout/Margins.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.layout; - -public class Margins { - - private int marginTop; - private int marginBottom; - private int marginLeft; - private int marginRight; - - private int horizontal = 0; - private int vertical = 0; - - public Margins(int marginTop, int marginBottom, int marginLeft, - int marginRight) { - super(); - this.marginTop = marginTop; - this.marginBottom = marginBottom; - this.marginLeft = marginLeft; - this.marginRight = marginRight; - - updateHorizontal(); - updateVertical(); - } - - public int getMarginTop() { - return marginTop; - } - - public int getMarginBottom() { - return marginBottom; - } - - public int getMarginLeft() { - return marginLeft; - } - - public int getMarginRight() { - return marginRight; - } - - public int getHorizontal() { - return horizontal; - } - - public int getVertical() { - return vertical; - } - - public void setMarginTop(int marginTop) { - this.marginTop = marginTop; - updateVertical(); - } - - public void setMarginBottom(int marginBottom) { - this.marginBottom = marginBottom; - updateVertical(); - } - - public void setMarginLeft(int marginLeft) { - this.marginLeft = marginLeft; - updateHorizontal(); - } - - public void setMarginRight(int marginRight) { - this.marginRight = marginRight; - updateHorizontal(); - } - - private void updateVertical() { - vertical = marginTop + marginBottom; - } - - private void updateHorizontal() { - horizontal = marginLeft + marginRight; - } - - @Override - public String toString() { - return "Margins [marginLeft=" + marginLeft + ",marginTop=" + marginTop - + ",marginRight=" + marginRight + ",marginBottom=" - + marginBottom + "]"; - } -} diff --git a/client/src/com/vaadin/client/ui/layout/MayScrollChildren.java b/client/src/com/vaadin/client/ui/layout/MayScrollChildren.java deleted file mode 100644 index 2a0b821646..0000000000 --- a/client/src/com/vaadin/client/ui/layout/MayScrollChildren.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.layout; - -import com.vaadin.client.HasComponentsConnector; - -public interface MayScrollChildren extends HasComponentsConnector { - -} diff --git a/client/src/com/vaadin/client/ui/layout/VLayoutSlot.java b/client/src/com/vaadin/client/ui/layout/VLayoutSlot.java deleted file mode 100644 index bc0c6739bb..0000000000 --- a/client/src/com/vaadin/client/ui/layout/VLayoutSlot.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.layout; - -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.VCaption; -import com.vaadin.shared.ui.AlignmentInfo; - -public abstract class VLayoutSlot { - - private final Element wrapper = Document.get().createDivElement(); - - private AlignmentInfo alignment; - private VCaption caption; - private final Widget widget; - - private double expandRatio; - - public VLayoutSlot(String baseClassName, Widget widget) { - this.widget = widget; - - wrapper.setClassName(baseClassName + "-slot"); - } - - public VCaption getCaption() { - return caption; - } - - public void setCaption(VCaption caption) { - if (this.caption != null) { - this.caption.removeFromParent(); - } - this.caption = caption; - if (caption != null) { - // Physical attach. - DOM.insertBefore(wrapper, caption.getElement(), widget.getElement()); - Style style = caption.getElement().getStyle(); - style.setPosition(Position.ABSOLUTE); - style.setTop(0, Unit.PX); - } - } - - public AlignmentInfo getAlignment() { - return alignment; - } - - public Widget getWidget() { - return widget; - } - - public void setAlignment(AlignmentInfo alignment) { - this.alignment = alignment; - // if alignment is something other than topLeft then we need to align - // the component inside this slot - if (alignment != null && (!alignment.isLeft() || !alignment.isTop())) { - widget.getElement().getStyle().setPosition(Position.ABSOLUTE); - } - } - - public void positionHorizontally(double currentLocation, - double allocatedSpace, double marginRight) { - Style style = wrapper.getStyle(); - - double availableWidth = allocatedSpace; - - VCaption caption = getCaption(); - Style captionStyle = caption != null ? caption.getElement().getStyle() - : null; - int captionWidth = getCaptionWidth(); - - boolean captionAboveCompnent; - if (caption == null) { - captionAboveCompnent = false; - style.clearPaddingRight(); - } else { - captionAboveCompnent = !caption.shouldBePlacedAfterComponent(); - if (!captionAboveCompnent) { - availableWidth -= captionWidth; - if (availableWidth < 0) { - availableWidth = 0; - } - captionStyle.clearLeft(); - captionStyle.setRight(0, Unit.PX); - style.setPaddingRight(captionWidth, Unit.PX); - } else { - captionStyle.setLeft(0, Unit.PX); - captionStyle.clearRight(); - style.clearPaddingRight(); - } - } - - if (marginRight > 0) { - style.setMarginRight(marginRight, Unit.PX); - } else { - style.clearMarginRight(); - } - - style.setPropertyPx("width", (int) availableWidth); - - double allocatedContentWidth = 0; - if (isRelativeWidth()) { - String percentWidth = getWidget().getElement().getStyle() - .getWidth(); - double percentage = parsePercent(percentWidth); - allocatedContentWidth = availableWidth * (percentage / 100); - reportActualRelativeWidth(Math.round((float) allocatedContentWidth)); - } - - style.setLeft(Math.round(currentLocation), Unit.PX); - AlignmentInfo alignment = getAlignment(); - if (!alignment.isLeft()) { - double usedWidth; - if (isRelativeWidth()) { - usedWidth = allocatedContentWidth; - } else { - usedWidth = getWidgetWidth(); - } - - double padding = (allocatedSpace - usedWidth); - if (alignment.isHorizontalCenter()) { - padding = padding / 2; - } - - long roundedPadding = Math.round(padding); - if (captionAboveCompnent) { - captionStyle.setLeft(roundedPadding, Unit.PX); - } - widget.getElement().getStyle().setLeft(roundedPadding, Unit.PX); - } else { - if (captionAboveCompnent) { - captionStyle.setLeft(0, Unit.PX); - } - // Reset left when changing back to align left - widget.getElement().getStyle().clearLeft(); - } - - } - - private double parsePercent(String size) { - return Double.parseDouble(size.replaceAll("%", "")); - } - - public void positionVertically(double currentLocation, - double allocatedSpace, double marginBottom) { - Style style = wrapper.getStyle(); - - double contentHeight = allocatedSpace; - - int captionHeight; - VCaption caption = getCaption(); - if (caption == null || caption.shouldBePlacedAfterComponent()) { - style.clearPaddingTop(); - captionHeight = 0; - } else { - captionHeight = getCaptionHeight(); - contentHeight -= captionHeight; - if (contentHeight < 0) { - contentHeight = 0; - } - style.setPaddingTop(captionHeight, Unit.PX); - } - - if (marginBottom > 0) { - style.setMarginBottom(marginBottom, Unit.PX); - } else { - style.clearMarginBottom(); - } - - style.setHeight(contentHeight, Unit.PX); - - double allocatedContentHeight = 0; - if (isRelativeHeight()) { - String height = getWidget().getElement().getStyle().getHeight(); - double percentage = parsePercent(height); - allocatedContentHeight = contentHeight * (percentage / 100); - reportActualRelativeHeight(Math - .round((float) allocatedContentHeight)); - } - - style.setTop(currentLocation, Unit.PX); - double padding = 0; - AlignmentInfo alignment = getAlignment(); - if (!alignment.isTop()) { - double usedHeight; - if (isRelativeHeight()) { - usedHeight = captionHeight + allocatedContentHeight; - } else { - usedHeight = getUsedHeight(); - } - if (alignment.isVerticalCenter()) { - padding = (allocatedSpace - usedHeight) / 2d; - } else { - padding = (allocatedSpace - usedHeight); - } - padding += captionHeight; - - widget.getElement().getStyle().setTop(padding, Unit.PX); - } else { - // Reset top when changing back to align top - widget.getElement().getStyle().clearTop(); - - } - } - - protected void reportActualRelativeHeight(int allocatedHeight) { - // Default implementation does nothing - } - - protected void reportActualRelativeWidth(int allocatedWidth) { - // Default implementation does nothing - } - - public void positionInDirection(double currentLocation, - double allocatedSpace, double endingMargin, boolean isVertical) { - if (isVertical) { - positionVertically(currentLocation, allocatedSpace, endingMargin); - } else { - positionHorizontally(currentLocation, allocatedSpace, endingMargin); - } - } - - public int getWidgetSizeInDirection(boolean isVertical) { - return isVertical ? getWidgetHeight() : getWidgetWidth(); - } - - public int getUsedWidth() { - int widgetWidth = getWidgetWidth(); - if (caption == null) { - return widgetWidth; - } else if (caption.shouldBePlacedAfterComponent()) { - return widgetWidth + getCaptionWidth(); - } else { - return Math.max(widgetWidth, getCaptionWidth()); - } - } - - public int getUsedHeight() { - int widgetHeight = getWidgetHeight(); - if (caption == null) { - return widgetHeight; - } else if (caption.shouldBePlacedAfterComponent()) { - return Math.max(widgetHeight, getCaptionHeight()); - } else { - return widgetHeight + getCaptionHeight(); - } - } - - public int getUsedSizeInDirection(boolean isVertical) { - return isVertical ? getUsedHeight() : getUsedWidth(); - } - - protected abstract int getCaptionHeight(); - - protected abstract int getCaptionWidth(); - - public abstract int getWidgetHeight(); - - public abstract int getWidgetWidth(); - - public abstract boolean isUndefinedHeight(); - - public abstract boolean isUndefinedWidth(); - - public boolean isUndefinedInDirection(boolean isVertical) { - return isVertical ? isUndefinedHeight() : isUndefinedWidth(); - } - - public abstract boolean isRelativeHeight(); - - public abstract boolean isRelativeWidth(); - - public boolean isRelativeInDirection(boolean isVertical) { - return isVertical ? isRelativeHeight() : isRelativeWidth(); - } - - public com.google.gwt.user.client.Element getWrapperElement() { - return DOM.asOld(wrapper); - } - - public void setExpandRatio(double expandRatio) { - this.expandRatio = expandRatio; - } - - public double getExpandRatio() { - return expandRatio; - } -} diff --git a/client/src/com/vaadin/client/ui/link/LinkConnector.java b/client/src/com/vaadin/client/ui/link/LinkConnector.java deleted file mode 100644 index 1e77fb51b4..0000000000 --- a/client/src/com/vaadin/client/ui/link/LinkConnector.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.link; - -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.user.client.DOM; -import com.vaadin.client.VCaption; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractComponentConnector; -import com.vaadin.client.ui.Icon; -import com.vaadin.client.ui.VLink; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.link.LinkConstants; -import com.vaadin.shared.ui.link.LinkState; -import com.vaadin.ui.Link; - -@Connect(Link.class) -public class LinkConnector extends AbstractComponentConnector { - - @Override - public LinkState getState() { - return (LinkState) super.getState(); - } - - @Override - public boolean delegateCaptionHandling() { - return false; - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - getWidget().enabled = isEnabled(); - - if (stateChangeEvent.hasPropertyChanged("resources")) { - getWidget().src = getResourceUrl(LinkConstants.HREF_RESOURCE); - if (getWidget().src == null) { - getWidget().anchor.removeAttribute("href"); - } else { - getWidget().anchor.setAttribute("href", getWidget().src); - } - } - - getWidget().target = getState().target; - if (getWidget().target == null) { - getWidget().anchor.removeAttribute("target"); - } else { - getWidget().anchor.setAttribute("target", getWidget().target); - } - - getWidget().borderStyle = getState().targetBorder; - getWidget().targetWidth = getState().targetWidth; - getWidget().targetHeight = getState().targetHeight; - - // Set link caption - VCaption.setCaptionText(getWidget().captionElement, getState()); - - // handle error - if (null != getState().errorMessage) { - if (getWidget().errorIndicatorElement == null) { - getWidget().errorIndicatorElement = DOM.createDiv(); - DOM.setElementProperty(getWidget().errorIndicatorElement, - "className", "v-errorindicator"); - } - DOM.insertChild(getWidget().getElement(), - getWidget().errorIndicatorElement, 0); - } else if (getWidget().errorIndicatorElement != null) { - getWidget().errorIndicatorElement.getStyle().setDisplay( - Display.NONE); - } - - if (getWidget().icon != null) { - getWidget().anchor.removeChild(getWidget().icon.getElement()); - getWidget().icon = null; - } - Icon icon = getIcon(); - if (icon != null) { - getWidget().icon = icon; - getWidget().anchor.insertBefore(icon.getElement(), - getWidget().captionElement); - } - } - - @Override - public VLink getWidget() { - return (VLink) super.getWidget(); - } -} diff --git a/client/src/com/vaadin/client/ui/listselect/ListSelectConnector.java b/client/src/com/vaadin/client/ui/listselect/ListSelectConnector.java deleted file mode 100644 index b867a3358c..0000000000 --- a/client/src/com/vaadin/client/ui/listselect/ListSelectConnector.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.listselect; - -import com.vaadin.client.ui.VListSelect; -import com.vaadin.client.ui.optiongroup.OptionGroupBaseConnector; -import com.vaadin.shared.ui.Connect; -import com.vaadin.ui.ListSelect; - -@Connect(ListSelect.class) -public class ListSelectConnector extends OptionGroupBaseConnector { - - @Override - public VListSelect getWidget() { - return (VListSelect) super.getWidget(); - } -} diff --git a/client/src/com/vaadin/client/ui/menubar/MenuBar.java b/client/src/com/vaadin/client/ui/menubar/MenuBar.java deleted file mode 100644 index 833fb5a38a..0000000000 --- a/client/src/com/vaadin/client/ui/menubar/MenuBar.java +++ /dev/null @@ -1,637 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.menubar; - -/* - * Copyright 2007 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -// COPIED HERE DUE package privates in GWT -import java.util.ArrayList; -import java.util.List; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.PopupListener; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ui.VOverlay; - -/** - * A standard menu bar widget. A menu bar can contain any number of menu items, - * each of which can either fire a {@link com.google.gwt.user.client.Command} or - * open a cascaded menu bar. - * - *

- * - *

- * - *

CSS Style Rules

- *
    - *
  • .gwt-MenuBar { the menu bar itself }
  • - *
  • .gwt-MenuBar .gwt-MenuItem { menu items }
  • - *
  • - * .gwt-MenuBar .gwt-MenuItem-selected { selected menu items }
  • - *
- * - *

- *

Example

- * {@example com.google.gwt.examples.MenuBarExample} - *

- * - * @deprecated - */ -@Deprecated -public class MenuBar extends Widget implements PopupListener { - - private final Element body; - private final Element table; - private final Element outer; - - private final ArrayList items = new ArrayList(); - private MenuBar parentMenu; - private PopupPanel popup; - private MenuItem selectedItem; - private MenuBar shownChildMenu; - private final boolean vertical; - private boolean autoOpen; - - /** - * Creates an empty horizontal menu bar. - */ - public MenuBar() { - this(false); - } - - /** - * Creates an empty menu bar. - * - * @param vertical - * true to orient the menu bar vertically - */ - public MenuBar(boolean vertical) { - super(); - - table = DOM.createTable(); - body = DOM.createTBody(); - DOM.appendChild(table, body); - - if (!vertical) { - final Element tr = DOM.createTR(); - DOM.appendChild(body, tr); - } - - this.vertical = vertical; - - outer = DOM.createDiv(); - DOM.appendChild(outer, table); - setElement(outer); - - sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT); - setStyleName("gwt-MenuBar"); - } - - /** - * Adds a menu item to the bar. - * - * @param item - * the item to be added - */ - public void addItem(MenuItem item) { - Element tr; - if (vertical) { - tr = DOM.createTR(); - DOM.appendChild(body, tr); - } else { - tr = DOM.getChild(body, 0); - } - - DOM.appendChild(tr, item.getElement()); - - item.setParentMenu(this); - item.setSelectionStyle(false); - items.add(item); - } - - /** - * Adds a menu item to the bar, that will fire the given command when it is - * selected. - * - * @param text - * the item's text - * @param asHTML - * true to treat the specified text as html - * @param cmd - * the command to be fired - * @return the {@link MenuItem} object created - */ - public MenuItem addItem(String text, boolean asHTML, Command cmd) { - final MenuItem item = new MenuItem(text, asHTML, cmd); - addItem(item); - return item; - } - - /** - * Adds a menu item to the bar, that will open the specified menu when it is - * selected. - * - * @param text - * the item's text - * @param asHTML - * true to treat the specified text as html - * @param popup - * the menu to be cascaded from it - * @return the {@link MenuItem} object created - */ - public MenuItem addItem(String text, boolean asHTML, MenuBar popup) { - final MenuItem item = new MenuItem(text, asHTML, popup); - addItem(item); - return item; - } - - /** - * Adds a menu item to the bar, that will fire the given command when it is - * selected. - * - * @param text - * the item's text - * @param cmd - * the command to be fired - * @return the {@link MenuItem} object created - */ - public MenuItem addItem(String text, Command cmd) { - final MenuItem item = new MenuItem(text, cmd); - addItem(item); - return item; - } - - /** - * Adds a menu item to the bar, that will open the specified menu when it is - * selected. - * - * @param text - * the item's text - * @param popup - * the menu to be cascaded from it - * @return the {@link MenuItem} object created - */ - public MenuItem addItem(String text, MenuBar popup) { - final MenuItem item = new MenuItem(text, popup); - addItem(item); - return item; - } - - /** - * Removes all menu items from this menu bar. - */ - public void clearItems() { - final Element container = getItemContainerElement(); - while (DOM.getChildCount(container) > 0) { - DOM.removeChild(container, DOM.getChild(container, 0)); - } - items.clear(); - } - - /** - * Gets whether this menu bar's child menus will open when the mouse is - * moved over it. - * - * @return true if child menus will auto-open - */ - public boolean getAutoOpen() { - return autoOpen; - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - - final MenuItem item = findItem(DOM.eventGetTarget(event)); - switch (DOM.eventGetType(event)) { - case Event.ONCLICK: { - // Fire an item's command when the user clicks on it. - if (item != null) { - doItemAction(item, true); - } - break; - } - - case Event.ONMOUSEOVER: { - if (item != null) { - itemOver(item); - } - break; - } - - case Event.ONMOUSEOUT: { - if (item != null) { - itemOver(null); - } - break; - } - } - } - - @Override - public void onPopupClosed(PopupPanel sender, boolean autoClosed) { - // If the menu popup was auto-closed, close all of its parents as well. - if (autoClosed) { - closeAllParents(); - } - - // When the menu popup closes, remember that no item is - // currently showing a popup menu. - onHide(); - shownChildMenu = null; - popup = null; - } - - /** - * Removes the specified menu item from the bar. - * - * @param item - * the item to be removed - */ - public void removeItem(MenuItem item) { - final int idx = items.indexOf(item); - if (idx == -1) { - return; - } - - final Element container = getItemContainerElement(); - DOM.removeChild(container, DOM.getChild(container, idx)); - items.remove(idx); - } - - /** - * Sets whether this menu bar's child menus will open when the mouse is - * moved over it. - * - * @param autoOpen - * true to cause child menus to auto-open - */ - public void setAutoOpen(boolean autoOpen) { - this.autoOpen = autoOpen; - } - - /** - * Returns a list containing the MenuItem objects in the menu - * bar. If there are no items in the menu bar, then an empty - * List object will be returned. - * - * @return a list containing the MenuItem objects in the menu - * bar - */ - public List getItems() { - return items; - } - - /** - * Returns the MenuItem that is currently selected - * (highlighted) by the user. If none of the items in the menu are currently - * selected, then null will be returned. - * - * @return the MenuItem that is currently selected, or - * null if no items are currently selected - */ - public MenuItem getSelectedItem() { - return selectedItem; - } - - /** - * Gets the first item from the menu or null if no items. - * - * @since 7.2.6 - * @return the first item from the menu or null if no items. - */ - public MenuItem getFirstItem() { - return items != null && items.size() > 0 ? items.get(0) : null; - } - - /** - * Gest the last item from the menu or null if no items. - * - * @since 7.2.6 - * @return the last item from the menu or null if no items. - */ - public MenuItem getLastItem() { - return items != null && items.size() > 0 ? items.get(items.size() - 1) - : null; - } - - /** - * Gets the index of the selected item. - * - * @since 7.2.6 - * @return the index of the selected item. - */ - public int getSelectedIndex() { - return items != null ? items.indexOf(getSelectedItem()) : -1; - } - - @Override - protected void onDetach() { - // When the menu is detached, make sure to close all of its children. - if (popup != null) { - popup.hide(); - } - - super.onDetach(); - } - - /* - * Closes all parent menu popups. - */ - void closeAllParents() { - MenuBar curMenu = this; - while (curMenu != null) { - curMenu.close(); - - if ((curMenu.parentMenu == null) && (curMenu.selectedItem != null)) { - curMenu.selectedItem.setSelectionStyle(false); - curMenu.selectedItem = null; - } - - curMenu = curMenu.parentMenu; - } - } - - /* - * Performs the action associated with the given menu item. If the item has - * a popup associated with it, the popup will be shown. If it has a command - * associated with it, and 'fireCommand' is true, then the command will be - * fired. Popups associated with other items will be hidden. - * - * @param item the item whose popup is to be shown. @param fireCommand - * true if the item's command should be fired, - * false otherwise. - */ - protected void doItemAction(final MenuItem item, boolean fireCommand) { - // If the given item is already showing its menu, we're done. - if ((shownChildMenu != null) && (item.getSubMenu() == shownChildMenu)) { - return; - } - - // If another item is showing its menu, then hide it. - if (shownChildMenu != null) { - shownChildMenu.onHide(); - popup.hide(); - } - - // If the item has no popup, optionally fire its command. - if (item.getSubMenu() == null) { - if (fireCommand) { - // Close this menu and all of its parents. - closeAllParents(); - - // Fire the item's command. - final Command cmd = item.getCommand(); - if (cmd != null) { - Scheduler.get().scheduleDeferred(cmd); - } - } - return; - } - - // Ensure that the item is selected. - selectItem(item); - - // Create a new popup for this item, and position it next to - // the item (below if this is a horizontal menu bar, to the - // right if it's a vertical bar). - popup = new VOverlay(true) { - { - setWidget(item.getSubMenu()); - item.getSubMenu().onShow(); - setOwner(MenuBar.this); - } - - @Override - public boolean onEventPreview(Event event) { - // Hook the popup panel's event preview. We use this to keep it - // from - // auto-hiding when the parent menu is clicked. - switch (DOM.eventGetType(event)) { - case Event.ONCLICK: - // If the event target is part of the parent menu, suppress - // the - // event altogether. - final Element target = DOM.eventGetTarget(event); - final Element parentMenuElement = item.getParentMenu() - .getElement(); - if (DOM.isOrHasChild(parentMenuElement, target)) { - return false; - } - break; - } - - return super.onEventPreview(event); - } - }; - popup.addPopupListener(this); - - if (vertical) { - popup.setPopupPosition( - item.getAbsoluteLeft() + item.getOffsetWidth(), - item.getAbsoluteTop()); - } else { - popup.setPopupPosition(item.getAbsoluteLeft(), - item.getAbsoluteTop() + item.getOffsetHeight()); - } - - shownChildMenu = item.getSubMenu(); - item.getSubMenu().parentMenu = this; - - // Show the popup, ensuring that the menubar's event preview remains on - // top - // of the popup's. - popup.show(); - } - - void itemOver(MenuItem item) { - if (item == null) { - // Don't clear selection if the currently selected item's menu is - // showing. - if ((selectedItem != null) - && (shownChildMenu == selectedItem.getSubMenu())) { - return; - } - } - - // Style the item selected when the mouse enters. - selectItem(item); - - // If child menus are being shown, or this menu is itself - // a child menu, automatically show an item's child menu - // when the mouse enters. - if (item != null) { - if ((shownChildMenu != null) || (parentMenu != null) || autoOpen) { - doItemAction(item, false); - } - } - } - - public void selectItem(MenuItem item) { - if (item == selectedItem) { - scrollItemIntoView(item); - return; - } - - if (selectedItem != null) { - selectedItem.setSelectionStyle(false); - } - - if (item != null) { - item.setSelectionStyle(true); - } - - selectedItem = item; - - scrollItemIntoView(item); - } - - /* - * Scroll the specified item into view. - */ - private void scrollItemIntoView(MenuItem item) { - if (item != null) { - item.getElement().scrollIntoView(); - } - } - - /** - * Scroll the selected item into view. - * - * @since 7.2.6 - */ - public void scrollSelectionIntoView() { - scrollItemIntoView(selectedItem); - } - - /** - * Sets the menu scroll enabled or disabled. - * - * @since 7.2.6 - * @param enabled - * the enabled state of the scroll. - */ - public void setScrollEnabled(boolean enabled) { - if (enabled) { - if (vertical) { - outer.getStyle().setOverflowY(Overflow.AUTO); - } else { - outer.getStyle().setOverflowX(Overflow.AUTO); - } - - } else { - if (vertical) { - outer.getStyle().clearOverflowY(); - } else { - outer.getStyle().clearOverflowX(); - } - } - } - - /** - * Gets whether the scroll is activate for this menu. - * - * @since 7.2.6 - * @return true if the scroll is active, otherwise false. - */ - public boolean isScrollActive() { - // Element element = getElement(); - // return element.getOffsetHeight() > DOM.getChild(element, 0) - // .getOffsetHeight(); - int outerHeight = outer.getOffsetHeight(); - int tableHeight = table.getOffsetHeight(); - return outerHeight < tableHeight; - } - - /** - * Gets the preferred height of the menu. - * - * @since 7.2.6 - */ - protected int getPreferredHeight() { - return table.getOffsetHeight(); - } - - /** - * Closes this menu (if it is a popup). - */ - private void close() { - if (parentMenu != null) { - parentMenu.popup.hide(); - } - } - - private MenuItem findItem(Element hItem) { - for (int i = 0; i < items.size(); ++i) { - final MenuItem item = items.get(i); - if (DOM.isOrHasChild(item.getElement(), hItem)) { - return item; - } - } - - return null; - } - - private Element getItemContainerElement() { - if (vertical) { - return body; - } else { - return DOM.getChild(body, 0); - } - } - - /* - * This method is called when a menu bar is hidden, so that it can hide any - * child popups that are currently being shown. - */ - private void onHide() { - if (shownChildMenu != null) { - shownChildMenu.onHide(); - popup.hide(); - } - } - - /* - * This method is called when a menu bar is shown. - */ - private void onShow() { - // Select the first item when a menu is shown. - if (items.size() > 0) { - selectItem(items.get(0)); - } - } -} diff --git a/client/src/com/vaadin/client/ui/menubar/MenuBarConnector.java b/client/src/com/vaadin/client/ui/menubar/MenuBarConnector.java deleted file mode 100644 index 03eeb85165..0000000000 --- a/client/src/com/vaadin/client/ui/menubar/MenuBarConnector.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.menubar; - -import java.util.Iterator; -import java.util.Stack; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Element; -import com.google.gwt.user.client.Command; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.Paintable; -import com.vaadin.client.TooltipInfo; -import com.vaadin.client.UIDL; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.ui.AbstractComponentConnector; -import com.vaadin.client.ui.ImageIcon; -import com.vaadin.client.ui.SimpleManagedLayout; -import com.vaadin.client.ui.VMenuBar; -import com.vaadin.shared.ui.ComponentStateUtil; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.menubar.MenuBarConstants; -import com.vaadin.shared.ui.menubar.MenuBarState; - -@Connect(com.vaadin.ui.MenuBar.class) -public class MenuBarConnector extends AbstractComponentConnector implements - Paintable, SimpleManagedLayout { - - /** - * This method must be implemented to update the client-side component from - * UIDL data received from server. - * - * This method is called when the page is loaded for the first time, and - * every time UI changes in the component are received from the server. - */ - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().htmlContentAllowed = uidl - .hasAttribute(MenuBarConstants.HTML_CONTENT_ALLOWED); - - getWidget().openRootOnHover = uidl - .getBooleanAttribute(MenuBarConstants.OPEN_ROOT_MENU_ON_HOWER); - - getWidget().enabled = isEnabled(); - - // For future connections - getWidget().client = client; - getWidget().uidlId = uidl.getId(); - - // Empty the menu every time it receives new information - if (!getWidget().getItems().isEmpty()) { - getWidget().clearItems(); - } - - UIDL options = uidl.getChildUIDL(0); - - if (null != getState() - && !ComponentStateUtil.isUndefinedWidth(getState())) { - UIDL moreItemUIDL = options.getChildUIDL(0); - StringBuffer itemHTML = new StringBuffer(); - - if (moreItemUIDL.hasAttribute("icon")) { - itemHTML.append("\"\""); - } - - String moreItemText = moreItemUIDL.getStringAttribute("text"); - if ("".equals(moreItemText)) { - moreItemText = "►"; - } - itemHTML.append(moreItemText); - - getWidget().moreItem = GWT.create(VMenuBar.CustomMenuItem.class); - getWidget().moreItem.setHTML(itemHTML.toString()); - getWidget().moreItem.setCommand(VMenuBar.emptyCommand); - - getWidget().collapsedRootItems = new VMenuBar(true, getWidget()); - getWidget().moreItem.setSubMenu(getWidget().collapsedRootItems); - getWidget().moreItem.addStyleName(getWidget().getStylePrimaryName() - + "-more-menuitem"); - } - - UIDL uidlItems = uidl.getChildUIDL(1); - Iterator itr = uidlItems.getChildIterator(); - Stack> iteratorStack = new Stack>(); - Stack menuStack = new Stack(); - VMenuBar currentMenu = getWidget(); - - while (itr.hasNext()) { - UIDL item = (UIDL) itr.next(); - VMenuBar.CustomMenuItem currentItem = null; - - final int itemId = item.getIntAttribute("id"); - - boolean itemHasCommand = item.hasAttribute("command"); - boolean itemIsCheckable = item - .hasAttribute(MenuBarConstants.ATTRIBUTE_CHECKED); - - String itemHTML = getWidget().buildItemHTML(item); - - Command cmd = null; - if (!item.hasAttribute("separator")) { - if (itemHasCommand || itemIsCheckable) { - // Construct a command that fires onMenuClick(int) with the - // item's id-number - cmd = new Command() { - @Override - public void execute() { - getWidget().hostReference.onMenuClick(itemId); - } - }; - } - } - - currentItem = currentMenu.addItem(itemHTML.toString(), cmd); - currentItem.updateFromUIDL(item, client); - - if (item.getChildCount() > 0) { - menuStack.push(currentMenu); - iteratorStack.push(itr); - itr = item.getChildIterator(); - currentMenu = new VMenuBar(true, currentMenu); - client.getVTooltip().connectHandlersToWidget(currentMenu); - // this is the top-level style that also propagates to items - - // any item specific styles are set above in - // currentItem.updateFromUIDL(item, client) - if (ComponentStateUtil.hasStyles(getState())) { - for (String style : getState().styles) { - currentMenu.addStyleDependentName(style); - } - } - currentItem.setSubMenu(currentMenu); - } - - while (!itr.hasNext() && !iteratorStack.empty()) { - boolean hasCheckableItem = false; - for (VMenuBar.CustomMenuItem menuItem : currentMenu.getItems()) { - hasCheckableItem = hasCheckableItem - || menuItem.isCheckable(); - } - if (hasCheckableItem) { - currentMenu.addStyleDependentName("check-column"); - } else { - currentMenu.removeStyleDependentName("check-column"); - } - - itr = iteratorStack.pop(); - currentMenu = menuStack.pop(); - } - }// while - - getLayoutManager().setNeedsHorizontalLayout(this); - - }// updateFromUIDL - - @Override - public VMenuBar getWidget() { - return (VMenuBar) super.getWidget(); - } - - @Override - public MenuBarState getState() { - return (MenuBarState) super.getState(); - } - - @Override - public void layout() { - getWidget().iLayout(); - } - - @Override - public TooltipInfo getTooltipInfo(Element element) { - TooltipInfo info = null; - - // Check content of widget to find tooltip for element - if (element != getWidget().getElement()) { - - VMenuBar.CustomMenuItem item = getWidget().getMenuItemWithElement( - element); - if (item != null) { - info = item.getTooltip(); - } - } - - // Use default tooltip if nothing found from DOM three - if (info == null) { - info = super.getTooltipInfo(element); - } - - return info; - } - - @Override - public boolean hasTooltip() { - /* - * Item tooltips are not processed until updateFromUIDL, so we can't be - * sure that there are no tooltips during onStateChange when this method - * is used. - */ - return true; - } -} diff --git a/client/src/com/vaadin/client/ui/menubar/MenuItem.java b/client/src/com/vaadin/client/ui/menubar/MenuItem.java deleted file mode 100644 index bf2fbf8feb..0000000000 --- a/client/src/com/vaadin/client/ui/menubar/MenuItem.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.menubar; - -/* - * Copyright 2007 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -// COPIED HERE DUE package privates in GWT -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.HasHTML; -import com.google.gwt.user.client.ui.UIObject; - -/** - * A widget that can be placed in a - * {@link com.google.gwt.user.client.ui.MenuBar}. Menu items can either fire a - * {@link com.google.gwt.user.client.Command} when they are clicked, or open a - * cascading sub-menu. - * - * @deprecated - */ -@Deprecated -public class MenuItem extends UIObject implements HasHTML { - - private static final String DEPENDENT_STYLENAME_SELECTED_ITEM = "selected"; - - private Command command; - private MenuBar parentMenu, subMenu; - - /** - * Constructs a new menu item that fires a command when it is selected. - * - * @param text - * the item's text - * @param cmd - * the command to be fired when it is selected - */ - public MenuItem(String text, Command cmd) { - this(text, false); - setCommand(cmd); - } - - /** - * Constructs a new menu item that fires a command when it is selected. - * - * @param text - * the item's text - * @param asHTML - * true to treat the specified text as html - * @param cmd - * the command to be fired when it is selected - */ - public MenuItem(String text, boolean asHTML, Command cmd) { - this(text, asHTML); - setCommand(cmd); - } - - /** - * Constructs a new menu item that cascades to a sub-menu when it is - * selected. - * - * @param text - * the item's text - * @param subMenu - * the sub-menu to be displayed when it is selected - */ - public MenuItem(String text, MenuBar subMenu) { - this(text, false); - setSubMenu(subMenu); - } - - /** - * Constructs a new menu item that cascades to a sub-menu when it is - * selected. - * - * @param text - * the item's text - * @param asHTML - * true to treat the specified text as html - * @param subMenu - * the sub-menu to be displayed when it is selected - */ - public MenuItem(String text, boolean asHTML, MenuBar subMenu) { - this(text, asHTML); - setSubMenu(subMenu); - } - - MenuItem(String text, boolean asHTML) { - setElement(DOM.createTD()); - setSelectionStyle(false); - - if (asHTML) { - setHTML(text); - } else { - setText(text); - } - setStyleName("gwt-MenuItem"); - } - - /** - * Gets the command associated with this item. - * - * @return this item's command, or null if none exists - */ - public Command getCommand() { - return command; - } - - @Override - public String getHTML() { - return DOM.getInnerHTML(getElement()); - } - - /** - * Gets the menu that contains this item. - * - * @return the parent menu, or null if none exists. - */ - public MenuBar getParentMenu() { - return parentMenu; - } - - /** - * Gets the sub-menu associated with this item. - * - * @return this item's sub-menu, or null if none exists - */ - public MenuBar getSubMenu() { - return subMenu; - } - - @Override - public String getText() { - return DOM.getInnerText(getElement()); - } - - /** - * Sets the command associated with this item. - * - * @param cmd - * the command to be associated with this item - */ - public void setCommand(Command cmd) { - command = cmd; - } - - @Override - public void setHTML(String html) { - DOM.setInnerHTML(getElement(), html); - } - - /** - * Sets the sub-menu associated with this item. - * - * @param subMenu - * this item's new sub-menu - */ - public void setSubMenu(MenuBar subMenu) { - this.subMenu = subMenu; - } - - @Override - public void setText(String text) { - DOM.setInnerText(getElement(), text); - } - - void setParentMenu(MenuBar parentMenu) { - this.parentMenu = parentMenu; - } - - void setSelectionStyle(boolean selected) { - if (selected) { - addStyleDependentName(DEPENDENT_STYLENAME_SELECTED_ITEM); - } else { - removeStyleDependentName(DEPENDENT_STYLENAME_SELECTED_ITEM); - } - } -} diff --git a/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java b/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java deleted file mode 100644 index 65d4a1eb9b..0000000000 --- a/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.nativebutton; - -import com.google.gwt.user.client.DOM; -import com.vaadin.client.VCaption; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractComponentConnector; -import com.vaadin.client.ui.ConnectorFocusAndBlurHandler; -import com.vaadin.client.ui.Icon; -import com.vaadin.client.ui.VNativeButton; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.button.ButtonServerRpc; -import com.vaadin.shared.ui.button.NativeButtonState; -import com.vaadin.ui.NativeButton; - -@Connect(NativeButton.class) -public class NativeButtonConnector extends AbstractComponentConnector { - - @Override - public void init() { - super.init(); - - getWidget().buttonRpcProxy = getRpcProxy(ButtonServerRpc.class); - getWidget().client = getConnection(); - getWidget().paintableId = getConnectorId(); - - ConnectorFocusAndBlurHandler.addHandlers(this); - } - - @Override - public boolean delegateCaptionHandling() { - return false; - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - getWidget().disableOnClick = getState().disableOnClick; - - // Set text - VCaption.setCaptionText(getWidget(), getState()); - - // handle error - if (null != getState().errorMessage) { - if (getWidget().errorIndicatorElement == null) { - getWidget().errorIndicatorElement = DOM.createSpan(); - getWidget().errorIndicatorElement - .setClassName("v-errorindicator"); - } - getWidget().getElement().insertBefore( - getWidget().errorIndicatorElement, - getWidget().captionElement); - - } else if (getWidget().errorIndicatorElement != null) { - getWidget().getElement().removeChild( - getWidget().errorIndicatorElement); - getWidget().errorIndicatorElement = null; - } - - if (getWidget().icon != null) { - getWidget().getElement().removeChild(getWidget().icon.getElement()); - getWidget().icon = null; - } - Icon icon = getIcon(); - if (icon != null) { - getWidget().icon = icon; - getWidget().getElement().insertBefore(icon.getElement(), - getWidget().captionElement); - icon.setAlternateText(getState().iconAltText); - } - - } - - @Override - public VNativeButton getWidget() { - return (VNativeButton) super.getWidget(); - } - - @Override - public NativeButtonState getState() { - return (NativeButtonState) super.getState(); - } -} diff --git a/client/src/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java b/client/src/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java deleted file mode 100644 index d6ff2015b4..0000000000 --- a/client/src/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.nativeselect; - -import com.vaadin.client.ui.ConnectorFocusAndBlurHandler; -import com.vaadin.client.ui.VNativeSelect; -import com.vaadin.client.ui.optiongroup.OptionGroupBaseConnector; -import com.vaadin.shared.ui.Connect; -import com.vaadin.ui.NativeSelect; - -@Connect(NativeSelect.class) -public class NativeSelectConnector extends OptionGroupBaseConnector { - - @Override - protected void init() { - super.init(); - ConnectorFocusAndBlurHandler.addHandlers(this, getWidget().getSelect()); - } - - @Override - public VNativeSelect getWidget() { - return (VNativeSelect) super.getWidget(); - } -} diff --git a/client/src/com/vaadin/client/ui/optiongroup/OptionGroupBaseConnector.java b/client/src/com/vaadin/client/ui/optiongroup/OptionGroupBaseConnector.java deleted file mode 100644 index 0757bc395b..0000000000 --- a/client/src/com/vaadin/client/ui/optiongroup/OptionGroupBaseConnector.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.optiongroup; - -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.Paintable; -import com.vaadin.client.StyleConstants; -import com.vaadin.client.UIDL; -import com.vaadin.client.ui.AbstractFieldConnector; -import com.vaadin.client.ui.VNativeButton; -import com.vaadin.client.ui.VOptionGroupBase; -import com.vaadin.client.ui.VTextField; -import com.vaadin.shared.ui.select.AbstractSelectState; - -public abstract class OptionGroupBaseConnector extends AbstractFieldConnector - implements Paintable { - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - // Save details - getWidget().client = client; - getWidget().paintableId = uidl.getId(); - - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().selectedKeys = uidl.getStringArrayVariableAsSet("selected"); - - getWidget().setReadonly(isReadOnly()); - getWidget().multiselect = "multi".equals(uidl - .getStringAttribute("selectmode")); - getWidget().immediate = getState().immediate; - getWidget().nullSelectionAllowed = uidl - .getBooleanAttribute("nullselect"); - getWidget().nullSelectionItemAvailable = uidl - .getBooleanAttribute("nullselectitem"); - - if (uidl.hasAttribute("cols")) { - getWidget().cols = uidl.getIntAttribute("cols"); - } - if (uidl.hasAttribute("rows")) { - getWidget().rows = uidl.getIntAttribute("rows"); - } - - final UIDL ops = uidl.getChildUIDL(0); - - if (getWidget().getColumns() > 0) { - getWidget().container.setWidth(getWidget().getColumns() + "em"); - if (getWidget().container != getWidget().optionsContainer) { - getWidget().optionsContainer.setWidth("100%"); - } - } - - getWidget().buildOptions(ops); - - if (uidl.getBooleanAttribute("allownewitem")) { - if (getWidget().newItemField == null) { - getWidget().newItemButton = new VNativeButton(); - getWidget().newItemButton.setText("+"); - getWidget().newItemButton.addClickHandler(getWidget()); - getWidget().newItemButton - .addStyleName(StyleConstants.UI_WIDGET); - getWidget().newItemField = new VTextField(); - getWidget().newItemField.client = getConnection(); - getWidget().newItemField.paintableId = getConnectorId(); - getWidget().newItemField.addKeyPressHandler(getWidget()); - getWidget().newItemField.addStyleName(StyleConstants.UI_WIDGET); - - } - getWidget().newItemField.setEnabled(getWidget().isEnabled() - && !getWidget().isReadonly()); - getWidget().newItemButton.setEnabled(getWidget().isEnabled() - && !getWidget().isReadonly()); - - if (getWidget().newItemField == null - || getWidget().newItemField.getParent() != getWidget().container) { - getWidget().container.add(getWidget().newItemField); - getWidget().container.add(getWidget().newItemButton); - final int w = getWidget().container.getOffsetWidth() - - getWidget().newItemButton.getOffsetWidth(); - getWidget().newItemField.setWidth(Math.max(w, 0) + "px"); - } - } else if (getWidget().newItemField != null) { - getWidget().container.remove(getWidget().newItemField); - getWidget().container.remove(getWidget().newItemButton); - } - - getWidget().setTabIndex(getState().tabIndex); - - } - - @Override - public VOptionGroupBase getWidget() { - return (VOptionGroupBase) super.getWidget(); - } - - @Override - public AbstractSelectState getState() { - return (AbstractSelectState) super.getState(); - } -} diff --git a/client/src/com/vaadin/client/ui/optiongroup/OptionGroupConnector.java b/client/src/com/vaadin/client/ui/optiongroup/OptionGroupConnector.java deleted file mode 100644 index f9bdf455f6..0000000000 --- a/client/src/com/vaadin/client/ui/optiongroup/OptionGroupConnector.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.optiongroup; - -import java.util.ArrayList; - -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.ui.CheckBox; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.UIDL; -import com.vaadin.client.ui.VOptionGroup; -import com.vaadin.shared.EventId; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.optiongroup.OptionGroupConstants; -import com.vaadin.shared.ui.optiongroup.OptionGroupState; -import com.vaadin.ui.OptionGroup; - -@Connect(OptionGroup.class) -public class OptionGroupConnector extends OptionGroupBaseConnector { - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - getWidget().htmlContentAllowed = uidl - .hasAttribute(OptionGroupConstants.HTML_CONTENT_ALLOWED); - - super.updateFromUIDL(uidl, client); - - getWidget().sendFocusEvents = client.hasEventListeners(this, - EventId.FOCUS); - getWidget().sendBlurEvents = client.hasEventListeners(this, - EventId.BLUR); - - if (getWidget().focusHandlers != null) { - for (HandlerRegistration reg : getWidget().focusHandlers) { - reg.removeHandler(); - } - getWidget().focusHandlers.clear(); - getWidget().focusHandlers = null; - - for (HandlerRegistration reg : getWidget().blurHandlers) { - reg.removeHandler(); - } - getWidget().blurHandlers.clear(); - getWidget().blurHandlers = null; - } - - if (getWidget().sendFocusEvents || getWidget().sendBlurEvents) { - getWidget().focusHandlers = new ArrayList(); - getWidget().blurHandlers = new ArrayList(); - - // add focus and blur handlers to checkboxes / radio buttons - for (Widget wid : getWidget().panel) { - if (wid instanceof CheckBox) { - getWidget().focusHandlers.add(((CheckBox) wid) - .addFocusHandler(getWidget())); - getWidget().blurHandlers.add(((CheckBox) wid) - .addBlurHandler(getWidget())); - } - } - } - } - - @Override - public VOptionGroup getWidget() { - return (VOptionGroup) super.getWidget(); - } - - @Override - public OptionGroupState getState() { - return (OptionGroupState) super.getState(); - } -} diff --git a/client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java b/client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java deleted file mode 100644 index dbd530dde1..0000000000 --- a/client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java +++ /dev/null @@ -1,710 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.orderedlayout; - -import java.util.List; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorHierarchyChangeEvent; -import com.vaadin.client.LayoutManager; -import com.vaadin.client.Profiler; -import com.vaadin.client.ServerConnector; -import com.vaadin.client.TooltipInfo; -import com.vaadin.client.Util; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; -import com.vaadin.client.ui.AbstractFieldConnector; -import com.vaadin.client.ui.AbstractLayoutConnector; -import com.vaadin.client.ui.Icon; -import com.vaadin.client.ui.LayoutClickEventHandler; -import com.vaadin.client.ui.aria.AriaHelper; -import com.vaadin.client.ui.layout.ElementResizeEvent; -import com.vaadin.client.ui.layout.ElementResizeListener; -import com.vaadin.shared.AbstractFieldState; -import com.vaadin.shared.ComponentConstants; -import com.vaadin.shared.communication.URLReference; -import com.vaadin.shared.ui.AlignmentInfo; -import com.vaadin.shared.ui.LayoutClickRpc; -import com.vaadin.shared.ui.MarginInfo; -import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutServerRpc; -import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutState; - -/** - * Base class for vertical and horizontal ordered layouts - */ -public abstract class AbstractOrderedLayoutConnector extends - AbstractLayoutConnector { - - /* - * Handlers & Listeners - */ - - private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( - this) { - - @Override - protected ComponentConnector getChildComponent( - com.google.gwt.user.client.Element element) { - return Util.getConnectorForElement(getConnection(), getWidget(), - element); - } - - @Override - protected LayoutClickRpc getLayoutClickRPC() { - return getRpcProxy(AbstractOrderedLayoutServerRpc.class); - } - }; - - private StateChangeHandler childStateChangeHandler = new StateChangeHandler() { - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - // Child state has changed, update stuff it hasn't already been done - updateInternalState(); - - /* - * Some changes must always be done after each child's own state - * change handler has been run because it might have changed some - * styles that are overridden here. - */ - ServerConnector child = stateChangeEvent.getConnector(); - if (child instanceof ComponentConnector) { - ComponentConnector component = (ComponentConnector) child; - Slot slot = getWidget().getSlot(component.getWidget()); - - slot.setRelativeWidth(component.isRelativeWidth()); - slot.setRelativeHeight(component.isRelativeHeight()); - } - } - }; - - private ElementResizeListener slotCaptionResizeListener = new ElementResizeListener() { - @Override - public void onElementResize(ElementResizeEvent e) { - - // Get all needed element references - Element captionElement = e.getElement(); - - // Caption position determines if the widget element is the first or - // last child inside the caption wrap - CaptionPosition pos = getWidget().getCaptionPositionFromElement( - captionElement.getParentElement()); - - // The default is the last child - Element widgetElement = captionElement.getParentElement() - .getLastChild().cast(); - - // ...but if caption position is bottom or right, the widget is the - // first child - if (pos == CaptionPosition.BOTTOM || pos == CaptionPosition.RIGHT) { - widgetElement = captionElement.getParentElement() - .getFirstChildElement().cast(); - } - - if (captionElement == widgetElement) { - // Caption element already detached - Slot slot = getWidget().getSlot(widgetElement); - if (slot != null) { - slot.setCaptionResizeListener(null); - } - return; - } - - String widgetWidth = widgetElement.getStyle().getWidth(); - String widgetHeight = widgetElement.getStyle().getHeight(); - - if (widgetHeight.endsWith("%") - && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { - getWidget().updateCaptionOffset(captionElement); - } else if (widgetWidth.endsWith("%") - && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) { - getWidget().updateCaptionOffset(captionElement); - } - - updateLayoutHeight(); - - if (needsExpand()) { - getWidget().updateExpandCompensation(); - } - } - }; - - private ElementResizeListener childComponentResizeListener = new ElementResizeListener() { - @Override - public void onElementResize(ElementResizeEvent e) { - updateLayoutHeight(); - if (needsExpand()) { - getWidget().updateExpandCompensation(); - } - } - }; - - private ElementResizeListener spacingResizeListener = new ElementResizeListener() { - @Override - public void onElementResize(ElementResizeEvent e) { - if (needsExpand()) { - getWidget().updateExpandCompensation(); - } - } - }; - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.AbstractComponentConnector#init() - */ - @Override - public void init() { - super.init(); - getWidget().setLayoutManager(getLayoutManager()); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.AbstractLayoutConnector#getState() - */ - @Override - public AbstractOrderedLayoutState getState() { - return (AbstractOrderedLayoutState) super.getState(); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.AbstractComponentConnector#getWidget() - */ - @Override - public VAbstractOrderedLayout getWidget() { - return (VAbstractOrderedLayout) super.getWidget(); - } - - /** - * Keep track of whether any child has relative height. Used to determine - * whether measurements are needed to make relative child heights work - * together with undefined container height. - */ - private boolean hasChildrenWithRelativeHeight = false; - - /** - * Keep track of whether any child has relative width. Used to determine - * whether measurements are needed to make relative child widths work - * together with undefined container width. - */ - private boolean hasChildrenWithRelativeWidth = false; - - /** - * Keep track of whether any child is middle aligned. Used to determine if - * measurements are needed to make middle aligned children work. - */ - private boolean hasChildrenWithMiddleAlignment = false; - - /** - * Keeps track of whether slots should be expanded based on available space. - */ - private boolean needsExpand = false; - - /** - * The id of the previous response for which state changes have been - * processed. If this is the same as the - * {@link ApplicationConnection#getLastSeenServerSyncId()}, it means that we can - * skip some quite expensive calculations because we know that the state - * hasn't changed since the last time the values were calculated. - */ - private int processedResponseId = -1; - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.HasComponentsConnector#updateCaption(com.vaadin - * .client.ComponentConnector) - */ - @Override - public void updateCaption(ComponentConnector connector) { - /* - * Don't directly update captions here to avoid calling e.g. - * updateLayoutHeight() before everything is initialized. - * updateInternalState() will ensure all captions are updated when - * appropriate. - */ - updateInternalState(); - } - - private void updateCaptionInternal(ComponentConnector child) { - Slot slot = getWidget().getSlot(child.getWidget()); - - String caption = child.getState().caption; - URLReference iconUrl = child.getState().resources - .get(ComponentConstants.ICON_RESOURCE); - String iconUrlString = iconUrl != null ? iconUrl.getURL() : null; - Icon icon = child.getConnection().getIcon(iconUrlString); - - List styles = child.getState().styles; - String error = child.getState().errorMessage; - boolean showError = error != null; - if (child.getState() instanceof AbstractFieldState) { - AbstractFieldState abstractFieldState = (AbstractFieldState) child - .getState(); - showError = showError && !abstractFieldState.hideErrors; - } - boolean required = false; - if (child instanceof AbstractFieldConnector) { - required = ((AbstractFieldConnector) child).isRequired(); - } - boolean enabled = child.isEnabled(); - - if (slot.hasCaption() && null == caption) { - slot.setCaptionResizeListener(null); - } - - slot.setCaption(caption, icon, styles, error, showError, required, - enabled, child.getState().captionAsHtml); - - AriaHelper.handleInputRequired(child.getWidget(), required); - AriaHelper.handleInputInvalid(child.getWidget(), showError); - AriaHelper.bindCaption(child.getWidget(), slot.getCaptionElement()); - - if (slot.hasCaption()) { - CaptionPosition pos = slot.getCaptionPosition(); - slot.setCaptionResizeListener(slotCaptionResizeListener); - if (child.isRelativeHeight() - && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { - getWidget().updateCaptionOffset(slot.getCaptionElement()); - } else if (child.isRelativeWidth() - && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) { - getWidget().updateCaptionOffset(slot.getCaptionElement()); - } - } - - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.AbstractComponentContainerConnector# - * onConnectorHierarchyChange - * (com.vaadin.client.ConnectorHierarchyChangeEvent) - */ - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - Profiler.enter("AOLC.onConnectorHierarchyChange"); - - List previousChildren = event.getOldChildren(); - int currentIndex = 0; - VAbstractOrderedLayout layout = getWidget(); - - // remove spacing as it is exists as separate elements that cannot be - // removed easily after reordering the contents - Profiler.enter("AOLC.onConnectorHierarchyChange temporarily remove spacing"); - layout.setSpacing(false); - Profiler.leave("AOLC.onConnectorHierarchyChange temporarily remove spacing"); - - for (ComponentConnector child : getChildComponents()) { - Profiler.enter("AOLC.onConnectorHierarchyChange add children"); - Slot slot = layout.getSlot(child.getWidget()); - if (slot.getParent() != layout) { - Profiler.enter("AOLC.onConnectorHierarchyChange add state change handler"); - child.addStateChangeHandler(childStateChangeHandler); - Profiler.leave("AOLC.onConnectorHierarchyChange add state change handler"); - } - Profiler.enter("AOLC.onConnectorHierarchyChange addOrMoveSlot"); - layout.addOrMoveSlot(slot, currentIndex++, false); - Profiler.leave("AOLC.onConnectorHierarchyChange addOrMoveSlot"); - - Profiler.leave("AOLC.onConnectorHierarchyChange add children"); - } - - // re-add spacing for the elements that should have it - Profiler.enter("AOLC.onConnectorHierarchyChange setSpacing"); - // spacings were removed above - if (getState().spacing) { - layout.setSpacing(true); - } - Profiler.leave("AOLC.onConnectorHierarchyChange setSpacing"); - - for (ComponentConnector child : previousChildren) { - Profiler.enter("AOLC.onConnectorHierarchyChange remove children"); - if (child.getParent() != this) { - Slot slot = layout.getSlot(child.getWidget()); - slot.setWidgetResizeListener(null); - if (slot.hasCaption()) { - slot.setCaptionResizeListener(null); - } - slot.setSpacingResizeListener(null); - child.removeStateChangeHandler(childStateChangeHandler); - layout.removeWidget(child.getWidget()); - } - Profiler.leave("AOLC.onConnectorHierarchyChange remove children"); - } - Profiler.leave("AOLC.onConnectorHierarchyChange"); - - updateInternalState(); - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.client.ui.AbstractComponentConnector#onStateChanged(com.vaadin - * .client.communication.StateChangeEvent) - */ - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - clickEventHandler.handleEventHandlerRegistration(); - getWidget().setMargin(new MarginInfo(getState().marginsBitmask)); - getWidget().setSpacing(getState().spacing); - - updateInternalState(); - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.client.ui.AbstractComponentConnector#getTooltipInfo(com.google - * .gwt.dom.client.Element) - */ - @Override - public TooltipInfo getTooltipInfo(com.google.gwt.dom.client.Element element) { - if (element != getWidget().getElement()) { - Slot slot = WidgetUtil.findWidget(element, Slot.class); - if (slot != null && slot.getCaptionElement() != null - && slot.getParent() == getWidget() - && slot.getCaptionElement().isOrHasChild(element)) { - ComponentConnector connector = Util.findConnectorFor(slot - .getWidget()); - if (connector != null) { - return connector.getTooltipInfo(element); - } - } - } - return super.getTooltipInfo(element); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.AbstractComponentConnector#hasTooltip() - */ - @Override - public boolean hasTooltip() { - /* - * Tooltips are fetched from child connectors -> there's no quick way of - * checking whether there might a tooltip hiding somewhere - */ - return true; - } - - /** - * Updates DOM properties and listeners based on the current state of this - * layout and its children. - */ - private void updateInternalState() { - // Avoid updating again for the same data - int lastResponseId = getConnection().getLastSeenServerSyncId(); - if (processedResponseId == lastResponseId) { - return; - } - Profiler.enter("AOLC.updateInternalState"); - // Remember that everything is updated for this response - processedResponseId = lastResponseId; - - hasChildrenWithRelativeHeight = false; - hasChildrenWithRelativeWidth = false; - - hasChildrenWithMiddleAlignment = false; - - needsExpand = getWidget().vertical ? !isUndefinedHeight() - : !isUndefinedWidth(); - - boolean onlyZeroExpands = true; - if (needsExpand) { - for (ComponentConnector child : getChildComponents()) { - double expandRatio = getState().childData.get(child).expandRatio; - if (expandRatio != 0) { - onlyZeroExpands = false; - break; - } - } - } - - // First update bookkeeping for all children - for (ComponentConnector child : getChildComponents()) { - Slot slot = getWidget().getSlot(child.getWidget()); - - slot.setRelativeWidth(child.isRelativeWidth()); - slot.setRelativeHeight(child.isRelativeHeight()); - - if (child.delegateCaptionHandling()) { - updateCaptionInternal(child); - } - - // Update slot style names - List childStyles = child.getState().styles; - if (childStyles == null) { - getWidget().setSlotStyleNames(child.getWidget(), - (String[]) null); - } else { - getWidget().setSlotStyleNames(child.getWidget(), - childStyles.toArray(new String[childStyles.size()])); - } - - AlignmentInfo alignment = new AlignmentInfo( - getState().childData.get(child).alignmentBitmask); - slot.setAlignment(alignment); - - if (alignment.isVerticalCenter()) { - hasChildrenWithMiddleAlignment = true; - } - - double expandRatio = onlyZeroExpands ? 1 : getState().childData - .get(child).expandRatio; - - slot.setExpandRatio(expandRatio); - - if (child.isRelativeHeight()) { - hasChildrenWithRelativeHeight = true; - } - if (child.isRelativeWidth()) { - hasChildrenWithRelativeWidth = true; - } - } - - if (needsFixedHeight()) { - // Add resize listener to ensure the widget itself is measured - getLayoutManager().addElementResizeListener( - getWidget().getElement(), childComponentResizeListener); - } else { - getLayoutManager().removeElementResizeListener( - getWidget().getElement(), childComponentResizeListener); - } - - // Then update listeners based on bookkeeping - updateAllSlotListeners(); - - // Update the layout at this point to ensure it's OK even if we get no - // element resize events - updateLayoutHeight(); - if (needsExpand()) { - getWidget().updateExpandedSizes(); - // updateExpandedSizes causes fixed size components to temporarily - // lose their size. updateExpandCompensation must be delayed until - // the browser has a chance to measure them. - Scheduler.get().scheduleFinally(new ScheduledCommand() { - @Override - public void execute() { - getWidget().updateExpandCompensation(); - } - }); - } else { - getWidget().clearExpand(); - } - - Profiler.leave("AOLC.updateInternalState"); - } - - /** - * Does the layout need a fixed height? - */ - private boolean needsFixedHeight() { - boolean isVertical = getWidget().vertical; - - if (isVertical) { - // Doesn't need height fix for vertical layouts - return false; - } - - else if (!isUndefinedHeight()) { - // Fix not needed unless the height is undefined - return false; - } - - else if (!hasChildrenWithRelativeHeight - && !hasChildrenWithMiddleAlignment) { - // Already works if there are no relative heights or middle aligned - // children - return false; - } - - return true; - } - - /** - * Does the layout need to expand? - */ - private boolean needsExpand() { - return needsExpand; - } - - /** - * Add slot listeners - */ - private void updateAllSlotListeners() { - for (ComponentConnector child : getChildComponents()) { - updateSlotListeners(child); - } - } - - /** - * Add/remove necessary ElementResizeListeners for one slot. This should be - * called after each update to the slot's or it's widget. - */ - private void updateSlotListeners(ComponentConnector child) { - Slot slot = getWidget().getSlot(child.getWidget()); - - // Clear all possible listeners first - slot.setWidgetResizeListener(null); - if (slot.hasCaption()) { - slot.setCaptionResizeListener(null); - } - if (slot.hasSpacing()) { - slot.setSpacingResizeListener(null); - } - - // Add all necessary listeners - if (needsFixedHeight()) { - slot.setWidgetResizeListener(childComponentResizeListener); - if (slot.hasCaption()) { - slot.setCaptionResizeListener(slotCaptionResizeListener); - } - } else if ((hasChildrenWithRelativeHeight || hasChildrenWithRelativeWidth) - && slot.hasCaption()) { - /* - * If the slot has caption, we need to listen for its size changes - * in order to update the padding/margin offset for relative sized - * components. - * - * TODO might only be needed if the caption is in the same direction - * as the relative size? - */ - slot.setCaptionResizeListener(slotCaptionResizeListener); - } - - if (needsExpand()) { - // TODO widget resize only be needed for children without expand? - slot.setWidgetResizeListener(childComponentResizeListener); - if (slot.hasSpacing()) { - slot.setSpacingResizeListener(spacingResizeListener); - } - } - } - - /** - * Re-calculate the layout height - */ - private void updateLayoutHeight() { - if (needsFixedHeight()) { - int h = getMaxHeight(); - if (h < 0) { - // Postpone change if there are elements that have not yet been - // measured - return; - } - h += getLayoutManager().getBorderHeight(getWidget().getElement()) - + getLayoutManager().getPaddingHeight( - getWidget().getElement()); - getWidget().getElement().getStyle().setHeight(h, Unit.PX); - getLayoutManager().setNeedsMeasure(this); - } - } - - /** - * Measures the maximum height of the layout in pixels - */ - private int getMaxHeight() { - int highestNonRelative = -1; - int highestRelative = -1; - - LayoutManager layoutManager = getLayoutManager(); - - for (ComponentConnector child : getChildComponents()) { - Widget childWidget = child.getWidget(); - Slot slot = getWidget().getSlot(childWidget); - Element captionElement = slot.getCaptionElement(); - CaptionPosition captionPosition = slot.getCaptionPosition(); - - int pixelHeight = layoutManager.getOuterHeight(childWidget - .getElement()); - if (pixelHeight == -1) { - // Height has not yet been measured -> postpone actions that - // depend on the max height - return -1; - } - - boolean hasRelativeHeight = slot.hasRelativeHeight(); - - boolean captionSizeShouldBeAddedtoComponentHeight = captionPosition == CaptionPosition.TOP - || captionPosition == CaptionPosition.BOTTOM; - boolean includeCaptionHeight = captionElement != null - && captionSizeShouldBeAddedtoComponentHeight; - - if (includeCaptionHeight) { - int captionHeight = layoutManager - .getOuterHeight(captionElement) - - getLayoutManager().getMarginHeight(captionElement); - if (captionHeight == -1) { - // Height has not yet been measured -> postpone actions that - // depend on the max height - return -1; - } - pixelHeight += captionHeight; - } - - if (!hasRelativeHeight) { - if (pixelHeight > highestNonRelative) { - highestNonRelative = pixelHeight; - } - } else { - if (pixelHeight > highestRelative) { - highestRelative = pixelHeight; - } - } - } - return highestNonRelative > -1 ? highestNonRelative : highestRelative; - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.AbstractComponentConnector#onUnregister() - */ - @Override - public void onUnregister() { - // Cleanup all ElementResizeListeners - for (ComponentConnector child : getChildComponents()) { - Slot slot = getWidget().getSlot(child.getWidget()); - if (slot.hasCaption()) { - slot.setCaptionResizeListener(null); - } - - if (slot.getSpacingElement() != null) { - slot.setSpacingResizeListener(null); - } - - slot.setWidgetResizeListener(null); - } - - super.onUnregister(); - } -} diff --git a/client/src/com/vaadin/client/ui/orderedlayout/CaptionPosition.java b/client/src/com/vaadin/client/ui/orderedlayout/CaptionPosition.java deleted file mode 100644 index 885dc1ecd7..0000000000 --- a/client/src/com/vaadin/client/ui/orderedlayout/CaptionPosition.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.orderedlayout; - -/** - * Defines where the caption should be placed - */ -public enum CaptionPosition { - TOP, RIGHT, BOTTOM, LEFT -} diff --git a/client/src/com/vaadin/client/ui/orderedlayout/HorizontalLayoutConnector.java b/client/src/com/vaadin/client/ui/orderedlayout/HorizontalLayoutConnector.java deleted file mode 100644 index 2cd1acd78b..0000000000 --- a/client/src/com/vaadin/client/ui/orderedlayout/HorizontalLayoutConnector.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.orderedlayout; - -import com.vaadin.client.ui.VHorizontalLayout; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.Connect.LoadStyle; -import com.vaadin.shared.ui.orderedlayout.HorizontalLayoutState; -import com.vaadin.ui.HorizontalLayout; - -/** - * Connects the client widget {@link VHorizontalLayout} with the Vaadin server - * side counterpart {@link HorizontalLayout} - */ -@Connect(value = HorizontalLayout.class, loadStyle = LoadStyle.EAGER) -public class HorizontalLayoutConnector extends AbstractOrderedLayoutConnector { - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.client.ui.orderedlayout.AbstractOrderedLayoutConnector#getWidget - * () - */ - @Override - public VHorizontalLayout getWidget() { - return (VHorizontalLayout) super.getWidget(); - } - - @Override - public HorizontalLayoutState getState() { - return (HorizontalLayoutState) super.getState(); - } - -} diff --git a/client/src/com/vaadin/client/ui/orderedlayout/Slot.java b/client/src/com/vaadin/client/ui/orderedlayout/Slot.java deleted file mode 100644 index 564c15472f..0000000000 --- a/client/src/com/vaadin/client/ui/orderedlayout/Slot.java +++ /dev/null @@ -1,832 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.orderedlayout; - -import java.util.List; - -import com.google.gwt.aria.client.Roles; -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.LayoutManager; -import com.vaadin.client.StyleConstants; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.ui.FontIcon; -import com.vaadin.client.ui.Icon; -import com.vaadin.client.ui.ImageIcon; -import com.vaadin.client.ui.layout.ElementResizeEvent; -import com.vaadin.client.ui.layout.ElementResizeListener; -import com.vaadin.shared.ui.AlignmentInfo; - -/** - * Represents a slot which contains the actual widget in the layout. - */ -public class Slot extends SimplePanel { - - private static final String ALIGN_CLASS_PREFIX = "v-align-"; - - // this must be set at construction time and not changed afterwards - private VAbstractOrderedLayout layout; - - public static final String SLOT_CLASSNAME = "v-slot"; - - private Element spacer; - private Element captionWrap; - private Element caption; - private Element captionText; - private Icon icon; - private Element errorIcon; - private Element requiredIcon; - - private ElementResizeListener captionResizeListener; - - private ElementResizeListener widgetResizeListener; - - private ElementResizeListener spacingResizeListener; - - /* - * This listener is applied only in IE8 to workaround browser issue where - * IE8 forgets to update the error indicator position when the slot gets - * resized by widget resizing itself. #11693 - */ - private ElementResizeListener ie8CaptionElementResizeUpdateListener = new ElementResizeListener() { - - @Override - public void onElementResize(ElementResizeEvent e) { - Element caption = getCaptionElement(); - if (caption != null) { - WidgetUtil.forceIE8Redraw(caption); - } - } - }; - - // Caption is placed after component unless there is some part which - // moves it above. - private CaptionPosition captionPosition = CaptionPosition.RIGHT; - - private AlignmentInfo alignment; - - private double expandRatio = -1; - - /** - * Constructs a slot. - * - * When using this constructor, the layout and widget must be set before any - * other operations are performed on the slot. - * - * @since 7.6 - */ - public Slot() { - setStyleName(SLOT_CLASSNAME); - } - - /** - * Set the layout in which this slot is. This method must be called exactly - * once at slot construction time when using the default constructor. - * - * The method should normally only be called by - * {@link VAbstractOrderedLayout#createSlot(Widget)}. - * - * @since 7.6 - * @param layout - * the layout containing the slot - */ - public void setLayout(VAbstractOrderedLayout layout) { - this.layout = layout; - } - - /** - * Constructs a slot. - * - * @param layout - * The layout to which this slot belongs - * @param widget - * The widget to put in the slot - * @deprecated use {@link GWT#create(Class)}, {@link #setWidget(Widget)} and - * {@link #setLayout(VAbstractOrderedLayout)} instead - */ - @Deprecated - public Slot(VAbstractOrderedLayout layout, Widget widget) { - setLayout(layout); - setStyleName(SLOT_CLASSNAME); - setWidget(widget); - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.SimplePanel#remove(com.google.gwt.user - * .client.ui.Widget) - */ - @Override - public boolean remove(Widget w) { - detachListeners(); - return super.remove(w); - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.SimplePanel#setWidget(com.google.gwt - * .user.client.ui.Widget) - */ - @Override - public void setWidget(Widget w) { - detachListeners(); - super.setWidget(w); - attachListeners(); - } - - /** - * Attaches resize listeners to the widget, caption and spacing elements - */ - private void attachListeners() { - if (getWidget() != null && layout.getLayoutManager() != null) { - LayoutManager lm = layout.getLayoutManager(); - if (getCaptionElement() != null && captionResizeListener != null) { - lm.addElementResizeListener(getCaptionElement(), - captionResizeListener); - } - if (widgetResizeListener != null) { - lm.addElementResizeListener(getWidget().getElement(), - widgetResizeListener); - } - if (getSpacingElement() != null && spacingResizeListener != null) { - lm.addElementResizeListener(getSpacingElement(), - spacingResizeListener); - } - - if (BrowserInfo.get().isIE8()) { - lm.addElementResizeListener(getWidget().getElement(), - ie8CaptionElementResizeUpdateListener); - } - } - } - - /** - * Detaches resize listeners from the widget, caption and spacing elements - */ - private void detachListeners() { - if (getWidget() != null && layout.getLayoutManager() != null) { - LayoutManager lm = layout.getLayoutManager(); - if (getCaptionElement() != null && captionResizeListener != null) { - lm.removeElementResizeListener(getCaptionElement(), - captionResizeListener); - } - if (widgetResizeListener != null) { - lm.removeElementResizeListener(getWidget().getElement(), - widgetResizeListener); - } - // in many cases, the listener has already been removed by - // setSpacing(false) - if (getSpacingElement() != null && spacingResizeListener != null) { - lm.removeElementResizeListener(getSpacingElement(), - spacingResizeListener); - } - - if (BrowserInfo.get().isIE8()) { - lm.removeElementResizeListener(getWidget().getElement(), - ie8CaptionElementResizeUpdateListener); - } - } - } - - public ElementResizeListener getCaptionResizeListener() { - return captionResizeListener; - } - - public void setCaptionResizeListener( - ElementResizeListener captionResizeListener) { - detachListeners(); - this.captionResizeListener = captionResizeListener; - attachListeners(); - } - - public ElementResizeListener getWidgetResizeListener() { - return widgetResizeListener; - } - - public void setWidgetResizeListener( - ElementResizeListener widgetResizeListener) { - detachListeners(); - this.widgetResizeListener = widgetResizeListener; - attachListeners(); - } - - public ElementResizeListener getSpacingResizeListener() { - return spacingResizeListener; - } - - public void setSpacingResizeListener( - ElementResizeListener spacingResizeListener) { - detachListeners(); - this.spacingResizeListener = spacingResizeListener; - attachListeners(); - } - - /** - * Returns the alignment for the slot - * - */ - public AlignmentInfo getAlignment() { - return alignment; - } - - /** - * Sets the style names for the slot containing the widget - * - * @param stylenames - * The style names for the slot - */ - protected void setStyleNames(String... stylenames) { - setStyleName(SLOT_CLASSNAME); - if (stylenames != null) { - for (String stylename : stylenames) { - addStyleDependentName(stylename); - } - } - - // Ensure alignment style names are correct - setAlignment(alignment); - } - - /** - * Sets how the widget is aligned inside the slot - * - * @param alignment - * The alignment inside the slot - */ - public void setAlignment(AlignmentInfo alignment) { - this.alignment = alignment; - - if (alignment != null && alignment.isHorizontalCenter()) { - addStyleName(ALIGN_CLASS_PREFIX + "center"); - removeStyleName(ALIGN_CLASS_PREFIX + "right"); - } else if (alignment != null && alignment.isRight()) { - addStyleName(ALIGN_CLASS_PREFIX + "right"); - removeStyleName(ALIGN_CLASS_PREFIX + "center"); - } else { - removeStyleName(ALIGN_CLASS_PREFIX + "right"); - removeStyleName(ALIGN_CLASS_PREFIX + "center"); - } - - if (alignment != null && alignment.isVerticalCenter()) { - addStyleName(ALIGN_CLASS_PREFIX + "middle"); - removeStyleName(ALIGN_CLASS_PREFIX + "bottom"); - } else if (alignment != null && alignment.isBottom()) { - addStyleName(ALIGN_CLASS_PREFIX + "bottom"); - removeStyleName(ALIGN_CLASS_PREFIX + "middle"); - } else { - removeStyleName(ALIGN_CLASS_PREFIX + "middle"); - removeStyleName(ALIGN_CLASS_PREFIX + "bottom"); - } - } - - /** - * Set how the slot should be expanded relative to the other slots. 0 means - * that the slot should not participate in the division of space based on - * the expand ratios but instead be allocated space based on its natural - * size. Other values causes the slot to get a share of the otherwise - * unallocated space in proportion to the slot's expand ratio value. - * - * @param expandRatio - * The ratio of the space the slot should occupy - * - */ - public void setExpandRatio(double expandRatio) { - this.expandRatio = expandRatio; - } - - /** - * Get the expand ratio for the slot. The expand ratio describes how the - * slot should be resized compared to other slots in the layout - * - * @return the expand ratio of the slot - * - * @see #setExpandRatio(double) - */ - public double getExpandRatio() { - return expandRatio; - } - - /** - * Set the spacing for the slot. The spacing determines if there should be - * empty space around the slot when the slot. - * - * @param spacing - * Should spacing be enabled - */ - public void setSpacing(boolean spacing) { - if (spacing && spacer == null) { - spacer = DOM.createDiv(); - spacer.addClassName("v-spacing"); - - /* - * This has to be done here for the initial render. In other cases - * where the spacer already exists onAttach will handle it. - */ - getElement().getParentElement().insertBefore(spacer, getElement()); - } else if (!spacing && spacer != null) { - // Remove listener before spacer to avoid memory leak - LayoutManager lm = layout.getLayoutManager(); - if (lm != null && spacingResizeListener != null) { - lm.removeElementResizeListener(spacer, spacingResizeListener); - } - - spacer.removeFromParent(); - spacer = null; - } - } - - /** - * Get the element which is added to make the spacing - * - * @return - */ - public com.google.gwt.user.client.Element getSpacingElement() { - return DOM.asOld(spacer); - } - - /** - * Does the slot have spacing - */ - public boolean hasSpacing() { - return getSpacingElement() != null; - } - - /** - * Get the vertical amount in pixels of the spacing - */ - protected int getVerticalSpacing() { - if (spacer == null) { - return 0; - } else if (layout.getLayoutManager() != null) { - return layout.getLayoutManager().getOuterHeight(spacer); - } - return spacer.getOffsetHeight(); - } - - /** - * Get the horizontal amount of pixels of the spacing - * - * @return - */ - protected int getHorizontalSpacing() { - if (spacer == null) { - return 0; - } else if (layout.getLayoutManager() != null) { - return layout.getLayoutManager().getOuterWidth(spacer); - } - return spacer.getOffsetWidth(); - } - - /** - * Set the position of the caption relative to the slot - * - * @param captionPosition - * The position of the caption - */ - public void setCaptionPosition(CaptionPosition captionPosition) { - if (caption == null) { - return; - } - captionWrap.removeClassName("v-caption-on-" - + this.captionPosition.name().toLowerCase()); - - this.captionPosition = captionPosition; - if (captionPosition == CaptionPosition.BOTTOM - || captionPosition == CaptionPosition.RIGHT) { - captionWrap.appendChild(caption); - } else { - captionWrap.insertFirst(caption); - } - - captionWrap.addClassName("v-caption-on-" - + captionPosition.name().toLowerCase()); - } - - /** - * Get the position of the caption relative to the slot - */ - public CaptionPosition getCaptionPosition() { - return captionPosition; - } - - /** - * Set the caption of the slot - * - * @param captionText - * The text of the caption - * @param iconUrl - * The icon URL, must already be run trough translateVaadinUri() - * @param styles - * The style names - * @param error - * The error message - * @param showError - * Should the error message be shown - * @param required - * Is the (field) required - * @param enabled - * Is the component enabled - * - * @deprecated Use - * {@link #setCaption(String, Icon, List, String, boolean, boolean, boolean)} - * instead - */ - @Deprecated - public void setCaption(String captionText, String iconUrl, - List styles, String error, boolean showError, - boolean required, boolean enabled) { - Icon icon; - if (FontIcon.isFontIconUri(iconUrl)) { - icon = GWT.create(FontIcon.class); - } else { - icon = GWT.create(ImageIcon.class); - } - icon.setUri(iconUrl); - - setCaption(captionText, icon, styles, error, showError, required, - enabled); - } - - /** - * Set the caption of the slot as text - * - * @param captionText - * The text of the caption - * @param icon - * The icon - * @param styles - * The style names - * @param error - * The error message - * @param showError - * Should the error message be shown - * @param required - * Is the (field) required - * @param enabled - * Is the component enabled - */ - public void setCaption(String captionText, Icon icon, List styles, - String error, boolean showError, boolean required, boolean enabled) { - setCaption(captionText, icon, styles, error, showError, required, - enabled, false); - } - - /** - * Set the caption of the slot - * - * @param captionText - * The text of the caption - * @param icon - * The icon - * @param styles - * The style names - * @param error - * The error message - * @param showError - * Should the error message be shown - * @param required - * Is the (field) required - * @param enabled - * Is the component enabled - * @param captionAsHtml - * true if the caption should be rendered as HTML, false - * otherwise - */ - public void setCaption(String captionText, Icon icon, List styles, - String error, boolean showError, boolean required, boolean enabled, - boolean captionAsHtml) { - - // TODO place for optimization: check if any of these have changed - // since last time, and only run those changes - - // Caption wrappers - Widget widget = getWidget(); - final Element focusedElement = WidgetUtil.getFocusedElement(); - // By default focus will not be lost - boolean focusLost = false; - if (captionText != null || icon != null || error != null || required) { - if (caption == null) { - caption = DOM.createDiv(); - captionWrap = DOM.createDiv(); - captionWrap.addClassName(StyleConstants.UI_WIDGET); - captionWrap.addClassName("v-has-caption"); - getElement().appendChild(captionWrap); - orphan(widget); - captionWrap.appendChild(widget.getElement()); - adopt(widget); - - // Made changes to DOM. Focus can be lost if it was in the - // widget. - focusLost = (focusedElement == null ? false : widget - .getElement().isOrHasChild(focusedElement)); - } - } else if (caption != null) { - orphan(widget); - getElement().appendChild(widget.getElement()); - adopt(widget); - captionWrap.removeFromParent(); - caption = null; - captionWrap = null; - - // Made changes to DOM. Focus can be lost if it was in the widget. - focusLost = (focusedElement == null ? false : widget.getElement() - .isOrHasChild(focusedElement)); - } - - // Caption text - if (captionText != null) { - if (this.captionText == null) { - this.captionText = DOM.createSpan(); - this.captionText.addClassName("v-captiontext"); - caption.appendChild(this.captionText); - } - if (captionText.trim().equals("")) { - this.captionText.setInnerHTML(" "); - } else { - if (captionAsHtml) { - this.captionText.setInnerHTML(captionText); - } else { - this.captionText.setInnerText(captionText); - } - } - } else if (this.captionText != null) { - this.captionText.removeFromParent(); - this.captionText = null; - } - - // Icon - if (this.icon != null) { - this.icon.getElement().removeFromParent(); - } - if (icon != null) { - caption.insertFirst(icon.getElement()); - } - this.icon = icon; - - // Required - if (required) { - if (requiredIcon == null) { - requiredIcon = DOM.createSpan(); - // TODO decide something better (e.g. use CSS to insert the - // character) - requiredIcon.setInnerHTML("*"); - requiredIcon.setClassName("v-required-field-indicator"); - - // The star should not be read by the screen reader, as it is - // purely visual. Required state is set at the element level for - // the screen reader. - Roles.getTextboxRole().setAriaHiddenState(requiredIcon, true); - } - caption.appendChild(requiredIcon); - } else if (requiredIcon != null) { - requiredIcon.removeFromParent(); - requiredIcon = null; - } - - // Error - if (error != null && showError) { - if (errorIcon == null) { - errorIcon = DOM.createSpan(); - errorIcon.setClassName("v-errorindicator"); - } - caption.appendChild(errorIcon); - } else if (errorIcon != null) { - errorIcon.removeFromParent(); - errorIcon = null; - } - - if (caption != null) { - // Styles - caption.setClassName("v-caption"); - - if (styles != null) { - for (String style : styles) { - caption.addClassName("v-caption-" + style); - } - } - - if (enabled) { - caption.removeClassName("v-disabled"); - } else { - caption.addClassName("v-disabled"); - } - - // Caption position - if (captionText != null || icon != null) { - setCaptionPosition(CaptionPosition.TOP); - } else { - setCaptionPosition(CaptionPosition.RIGHT); - } - } - - if (focusLost) { - // Find out what element is currently focused. - Element currentFocus = WidgetUtil.getFocusedElement(); - if (currentFocus != null - && currentFocus.equals(Document.get().getBody())) { - // Focus has moved to BodyElement and should be moved back to - // original location. This happened because of adding or - // removing the captionWrap - focusedElement.focus(); - } else if (currentFocus != focusedElement) { - // Focus is either moved somewhere else on purpose or IE has - // lost it. Investigate further. - Timer focusTimer = new Timer() { - - @Override - public void run() { - if (WidgetUtil.getFocusedElement() == null) { - // This should never become an infinite loop and - // even if it does it will be stopped once something - // is done with the browser. - schedule(25); - } else if (WidgetUtil.getFocusedElement().equals( - Document.get().getBody())) { - // Focus found it's way to BodyElement. Now it can - // be restored - focusedElement.focus(); - } - } - }; - if (BrowserInfo.get().isIE8()) { - // IE8 can't fix the focus immediately. It will fail. - focusTimer.schedule(25); - } else { - // Newer IE versions can handle things immediately. - focusTimer.run(); - } - } - } - } - - /** - * Does the slot have a caption - */ - public boolean hasCaption() { - return caption != null; - } - - /** - * Get the slots caption element - */ - public com.google.gwt.user.client.Element getCaptionElement() { - return DOM.asOld(caption); - } - - private boolean relativeWidth = false; - - /** - * Set if the slot has a relative width - * - * @param relativeWidth - * True if slot uses relative width, false if the slot has a - * static width - */ - public void setRelativeWidth(boolean relativeWidth) { - this.relativeWidth = relativeWidth; - updateRelativeSize(relativeWidth, "width"); - } - - public boolean hasRelativeWidth() { - return relativeWidth; - } - - private boolean relativeHeight = false; - - /** - * Set if the slot has a relative height - * - * @param relativeHeight - * True if the slot uses a relative height, false if the slot has - * a static height - */ - public void setRelativeHeight(boolean relativeHeight) { - this.relativeHeight = relativeHeight; - updateRelativeSize(relativeHeight, "height"); - } - - public boolean hasRelativeHeight() { - return relativeHeight; - } - - /** - * Updates the captions size if the slot is relative - * - * @param isRelativeSize - * Is the slot relatively sized - * @param direction - * The direction of the caption - */ - private void updateRelativeSize(boolean isRelativeSize, String direction) { - if (isRelativeSize && hasCaption()) { - captionWrap.getStyle().setProperty(direction, - getWidget().getElement().getStyle().getProperty(direction)); - captionWrap.addClassName("v-has-" + direction); - } else if (hasCaption()) { - if (direction.equals("height")) { - captionWrap.getStyle().clearHeight(); - } else { - captionWrap.getStyle().clearWidth(); - } - captionWrap.removeClassName("v-has-" + direction); - captionWrap.getStyle().clearPaddingTop(); - captionWrap.getStyle().clearPaddingRight(); - captionWrap.getStyle().clearPaddingBottom(); - captionWrap.getStyle().clearPaddingLeft(); - caption.getStyle().clearMarginTop(); - caption.getStyle().clearMarginRight(); - caption.getStyle().clearMarginBottom(); - caption.getStyle().clearMarginLeft(); - } - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt - * .user.client.Event) - */ - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (DOM.eventGetType(event) == Event.ONLOAD && icon != null - && icon.getElement() == DOM.eventGetTarget(event)) { - if (layout.getLayoutManager() != null) { - layout.getLayoutManager().layoutLater(); - } else { - layout.updateCaptionOffset(caption); - } - } - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.SimplePanel#getContainerElement() - */ - @Override - protected com.google.gwt.user.client.Element getContainerElement() { - if (captionWrap == null) { - return getElement(); - } else { - return DOM.asOld(captionWrap); - } - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.Widget#onDetach() - */ - @Override - protected void onDetach() { - if (spacer != null) { - spacer.removeFromParent(); - } - super.onDetach(); - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.Widget#onAttach() - */ - @Override - protected void onAttach() { - super.onAttach(); - if (spacer != null) { - getElement().getParentElement().insertBefore(spacer, getElement()); - } - } - - public boolean isRelativeInDirection(boolean vertical) { - if (vertical) { - return hasRelativeHeight(); - } else { - return hasRelativeWidth(); - } - } -} diff --git a/client/src/com/vaadin/client/ui/orderedlayout/VAbstractOrderedLayout.java b/client/src/com/vaadin/client/ui/orderedlayout/VAbstractOrderedLayout.java deleted file mode 100644 index 1a36ba9c93..0000000000 --- a/client/src/com/vaadin/client/ui/orderedlayout/VAbstractOrderedLayout.java +++ /dev/null @@ -1,742 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.orderedlayout; - -import java.util.HashMap; -import java.util.Map; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.Style.Visibility; -import com.google.gwt.regexp.shared.MatchResult; -import com.google.gwt.regexp.shared.RegExp; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.RequiresResize; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.LayoutManager; -import com.vaadin.client.Profiler; -import com.vaadin.client.Util; -import com.vaadin.client.WidgetUtil; -import com.vaadin.shared.ui.MarginInfo; - -/** - * Base class for ordered layouts - */ -public class VAbstractOrderedLayout extends FlowPanel { - - protected boolean spacing = false; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean vertical = true; - - protected boolean definedHeight = false; - - private Map widgetToSlot = new HashMap(); - - private Element expandWrapper; - - private LayoutManager layoutManager; - - /** - * Keep track of the last allocated expand size to help detecting when it - * changes. - */ - private int lastExpandSize = -1; - - public VAbstractOrderedLayout(boolean vertical) { - this.vertical = vertical; - } - - /** - * See the method {@link #addOrMoveSlot(Slot, int, boolean)}. - * - *

- * This method always adjusts spacings for the whole layout. - * - * @param slot - * The slot to move or add - * @param index - * The index where the slot should be placed. - * @deprecated since 7.1.4, use {@link #addOrMoveSlot(Slot, int, boolean)} - */ - @Deprecated - public void addOrMoveSlot(Slot slot, int index) { - addOrMoveSlot(slot, index, true); - } - - /** - * Add or move a slot to another index. - *

- * For internal use only. May be removed or replaced in the future. - *

- * You should note that the index does not refer to the DOM index if - * spacings are used. If spacings are used then the index will be adjusted - * to include the spacings when inserted. - *

- * For instance when using spacing the index converts to DOM index in the - * following way: - * - *

-     * index : 0 -> DOM index: 0
-     * index : 1 -> DOM index: 1
-     * index : 2 -> DOM index: 3
-     * index : 3 -> DOM index: 5
-     * index : 4 -> DOM index: 7
-     * 
- * - * When using this method never account for spacings. - *

- * The caller should remove all spacings before calling this method and - * re-add them (if necessary) after this method. This can be done before and - * after all slots have been added/moved. - *

- * - * @since 7.1.4 - * - * @param slot - * The slot to move or add - * @param index - * The index where the slot should be placed. - * @param adjustSpacing - * true to recalculate spacings for the whole layout after the - * operation - */ - public void addOrMoveSlot(Slot slot, int index, boolean adjustSpacing) { - Profiler.enter("VAOL.onConnectorHierarchyChange addOrMoveSlot find index"); - if (slot.getParent() == this) { - int currentIndex = getWidgetIndex(slot); - if (index == currentIndex) { - Profiler.leave("VAOL.onConnectorHierarchyChange addOrMoveSlot find index"); - return; - } - } - Profiler.leave("VAOL.onConnectorHierarchyChange addOrMoveSlot find index"); - - Profiler.enter("VAOL.onConnectorHierarchyChange addOrMoveSlot insert"); - insert(slot, index); - Profiler.leave("VAOL.onConnectorHierarchyChange addOrMoveSlot insert"); - - if (adjustSpacing) { - Profiler.enter("VAOL.onConnectorHierarchyChange addOrMoveSlot setSpacing"); - setSpacing(spacing); - Profiler.leave("VAOL.onConnectorHierarchyChange addOrMoveSlot setSpacing"); - } - } - - /** - * {@inheritDoc} - * - * @deprecated As of 7.2, use or override - * {@link #insert(Widget, Element, int, boolean)} instead. - */ - @Override - @Deprecated - protected void insert(Widget child, - com.google.gwt.user.client.Element container, int beforeIndex, - boolean domInsert) { - // Validate index; adjust if the widget is already a child of this - // panel. - beforeIndex = adjustIndex(child, beforeIndex); - - // Detach new child. - child.removeFromParent(); - - // Logical attach. - getChildren().insert(child, beforeIndex); - - // Physical attach. - container = expandWrapper != null ? DOM.asOld(expandWrapper) - : getElement(); - if (domInsert) { - if (spacing) { - if (beforeIndex != 0) { - /* - * Since the spacing elements are located at the same DOM - * level as the slots we need to take them into account when - * calculating the slot position. - * - * The spacing elements are always located before the actual - * slot except for the first slot which do not have a - * spacing element like this - * - * |...| - */ - beforeIndex = beforeIndex * 2 - 1; - } - } - DOM.insertChild(container, child.getElement(), beforeIndex); - } else { - DOM.appendChild(container, child.getElement()); - } - - // Adopt. - adopt(child); - } - - /** - * {@inheritDoc} - * - * @since 7.2 - */ - @Override - protected void insert(Widget child, Element container, int beforeIndex, - boolean domInsert) { - insert(child, DOM.asOld(container), beforeIndex, domInsert); - } - - /** - * Remove a slot from the layout - * - * @param widget - * @return - */ - public void removeWidget(Widget widget) { - Slot slot = widgetToSlot.remove(widget); - if (slot != null) { - removeSlot(slot); - } - } - - /** - * Remove a slot from the layout. - * - * This method is called automatically by {@link #removeWidget(Widget)} and - * should not be called directly by the user. When overridden, the super - * method must be called. - * - * @since 7.6 - * @param Slot - * to remove - */ - protected void removeSlot(Slot slot) { - remove(slot); - } - - /** - * Get the containing slot for a widget. If no slot is found a new slot is - * created and returned. - * - * @param widget - * The widget whose slot you want to get - * - * @return - */ - public Slot getSlot(Widget widget) { - Slot slot = widgetToSlot.get(widget); - if (slot == null) { - slot = createSlot(widget); - widgetToSlot.put(widget, slot); - } - return slot; - } - - /** - * Create a slot to be added to the layout. - * - * This method is called automatically by {@link #getSlot(Widget)} when a - * new slot is needed. It should not be called directly by the user, but can - * be overridden to customize slot creation. - * - * @since 7.6 - * @param widget - * the widget for which a slot is being created - * @return created Slot - */ - protected Slot createSlot(Widget widget) { - Slot slot = GWT.create(Slot.class); - slot.setLayout(this); - slot.setWidget(widget); - return slot; - } - - /** - * Gets a slot based on the widget element. If no slot is found then null is - * returned. - * - * @param widgetElement - * The element of the widget ( Same as getWidget().getElement() ) - * @return - * @deprecated As of 7.2, call or override {@link #getSlot(Element)} instead - */ - @Deprecated - public Slot getSlot(com.google.gwt.user.client.Element widgetElement) { - for (Map.Entry entry : widgetToSlot.entrySet()) { - if (entry.getKey().getElement() == widgetElement) { - return entry.getValue(); - } - } - return null; - } - - /** - * Gets a slot based on the widget element. If no slot is found then null is - * returned. - * - * @param widgetElement - * The element of the widget ( Same as getWidget().getElement() ) - * @return - * - * @since 7.2 - */ - public Slot getSlot(Element widgetElement) { - return getSlot(DOM.asOld(widgetElement)); - } - - /** - * Set the layout manager for the layout - * - * @param manager - * The layout manager to use - */ - public void setLayoutManager(LayoutManager manager) { - layoutManager = manager; - } - - /** - * Get the layout manager used by this layout - * - */ - public LayoutManager getLayoutManager() { - return layoutManager; - } - - /** - * Deducts the caption position by examining the wrapping element. - *

- * For internal use only. May be removed or replaced in the future. - * - * @param captionWrap - * The wrapping element - * - * @return The caption position - * @deprecated As of 7.2, call or override - * {@link #getCaptionPositionFromElement(Element)} instead - */ - @Deprecated - public CaptionPosition getCaptionPositionFromElement( - com.google.gwt.user.client.Element captionWrap) { - RegExp captionPositionRegexp = RegExp.compile("v-caption-on-(\\S+)"); - - // Get caption position from the classname - MatchResult matcher = captionPositionRegexp.exec(captionWrap - .getClassName()); - if (matcher == null || matcher.getGroupCount() < 2) { - return CaptionPosition.TOP; - } - String captionClass = matcher.getGroup(1); - CaptionPosition captionPosition = CaptionPosition.valueOf( - CaptionPosition.class, captionClass.toUpperCase()); - return captionPosition; - } - - /** - * Deducts the caption position by examining the wrapping element. - *

- * For internal use only. May be removed or replaced in the future. - * - * @param captionWrap - * The wrapping element - * - * @return The caption position - * @since 7.2 - */ - public CaptionPosition getCaptionPositionFromElement(Element captionWrap) { - return getCaptionPositionFromElement(DOM.asOld(captionWrap)); - } - - /** - * Update the offset off the caption relative to the slot - *

- * For internal use only. May be removed or replaced in the future. - * - * @param caption - * The caption element - * @deprecated As of 7.2, call or override - * {@link #updateCaptionOffset(Element)} instead - */ - @Deprecated - public void updateCaptionOffset(com.google.gwt.user.client.Element caption) { - - Element captionWrap = caption.getParentElement(); - - Style captionWrapStyle = captionWrap.getStyle(); - captionWrapStyle.clearPaddingTop(); - captionWrapStyle.clearPaddingRight(); - captionWrapStyle.clearPaddingBottom(); - captionWrapStyle.clearPaddingLeft(); - - Style captionStyle = caption.getStyle(); - captionStyle.clearMarginTop(); - captionStyle.clearMarginRight(); - captionStyle.clearMarginBottom(); - captionStyle.clearMarginLeft(); - - // Get caption position from the classname - CaptionPosition captionPosition = getCaptionPositionFromElement(captionWrap); - - if (captionPosition == CaptionPosition.LEFT - || captionPosition == CaptionPosition.RIGHT) { - int captionWidth; - if (layoutManager != null) { - captionWidth = layoutManager.getOuterWidth(caption) - - layoutManager.getMarginWidth(caption); - } else { - captionWidth = caption.getOffsetWidth(); - } - if (captionWidth > 0) { - if (captionPosition == CaptionPosition.LEFT) { - captionWrapStyle.setPaddingLeft(captionWidth, Unit.PX); - captionStyle.setMarginLeft(-captionWidth, Unit.PX); - } else { - captionWrapStyle.setPaddingRight(captionWidth, Unit.PX); - captionStyle.setMarginRight(-captionWidth, Unit.PX); - } - } - } - if (captionPosition == CaptionPosition.TOP - || captionPosition == CaptionPosition.BOTTOM) { - int captionHeight; - if (layoutManager != null) { - captionHeight = layoutManager.getOuterHeight(caption) - - layoutManager.getMarginHeight(caption); - } else { - captionHeight = caption.getOffsetHeight(); - } - if (captionHeight > 0) { - if (captionPosition == CaptionPosition.TOP) { - captionWrapStyle.setPaddingTop(captionHeight, Unit.PX); - captionStyle.setMarginTop(-captionHeight, Unit.PX); - } else { - captionWrapStyle.setPaddingBottom(captionHeight, Unit.PX); - captionStyle.setMarginBottom(-captionHeight, Unit.PX); - } - } - } - } - - /** - * Update the offset off the caption relative to the slot - *

- * For internal use only. May be removed or replaced in the future. - * - * @param caption - * The caption element - * @since 7.2 - */ - public void updateCaptionOffset(Element caption) { - updateCaptionOffset(DOM.asOld(caption)); - } - - /** - * Set the margin of the layout - * - * @param marginInfo - * The margin information - */ - public void setMargin(MarginInfo marginInfo) { - if (marginInfo != null) { - setStyleName("v-margin-top", marginInfo.hasTop()); - setStyleName("v-margin-right", marginInfo.hasRight()); - setStyleName("v-margin-bottom", marginInfo.hasBottom()); - setStyleName("v-margin-left", marginInfo.hasLeft()); - } - } - - /** - * Turn on or off spacing in the layout - * - * @param spacing - * True if spacing should be used, false if not - */ - public void setSpacing(boolean spacing) { - Profiler.enter("VAOL.onConnectorHierarchyChange setSpacing"); - this.spacing = spacing; - // first widget does not have spacing on - // optimization to avoid looking up widget indices on every iteration - Widget firstSlot = null; - if (getWidgetCount() > 0) { - firstSlot = getWidget(0); - } - for (Slot slot : widgetToSlot.values()) { - slot.setSpacing(spacing && firstSlot != slot); - } - Profiler.leave("VAOL.onConnectorHierarchyChange setSpacing"); - } - - /** - * Assigns relative sizes to the children that should expand based on their - * expand ratios. - */ - public void updateExpandedSizes() { - // Ensure the expand wrapper is in place - if (expandWrapper == null) { - expandWrapper = DOM.createDiv(); - expandWrapper.setClassName("v-expand"); - - // Detach all widgets before modifying DOM - for (Widget widget : getChildren()) { - orphan(widget); - } - - while (getElement().getChildCount() > 0) { - Node el = getElement().getChild(0); - expandWrapper.appendChild(el); - } - getElement().appendChild(expandWrapper); - - // Attach all widgets again - for (Widget widget : getChildren()) { - adopt(widget); - } - } - - // Sum up expand ratios to get the denominator - double total = 0; - for (Slot slot : widgetToSlot.values()) { - // FIXME expandRatio might be <0 - total += slot.getExpandRatio(); - } - - // Give each expanded child its own share - for (Slot slot : widgetToSlot.values()) { - - Element slotElement = slot.getElement(); - slotElement.removeAttribute("aria-hidden"); - - Style slotStyle = slotElement.getStyle(); - slotStyle.clearVisibility(); - slotStyle.clearMarginLeft(); - slotStyle.clearMarginTop(); - - if (slot.getExpandRatio() != 0) { - // FIXME expandRatio might be <0 - double size = 100 * (slot.getExpandRatio() / total); - - if (vertical) { - slot.setHeight(size + "%"); - if (slot.hasRelativeHeight()) { - Util.notifyParentOfSizeChange(this, true); - } - } else { - slot.setWidth(size + "%"); - if (slot.hasRelativeWidth()) { - Util.notifyParentOfSizeChange(this, true); - } - } - - } else if (slot.isRelativeInDirection(vertical)) { - // Relative child without expansion gets no space at all - if (vertical) { - slot.setHeight("0"); - } else { - slot.setWidth("0"); - } - slotStyle.setVisibility(Visibility.HIDDEN); - slotElement.setAttribute("aria-hidden", "true"); - - } else { - // Non-relative child without expansion should be unconstrained - if (BrowserInfo.get().isIE8()) { - // unconstrained in IE8 is auto - if (vertical) { - slot.setHeight("auto"); - } else { - slot.setWidth("auto"); - } - } else { - if (vertical) { - slotStyle.clearHeight(); - } else { - slotStyle.clearWidth(); - } - } - } - } - } - - /** - * Removes elements used to expand a slot. - *

- * For internal use only. May be removed or replaced in the future. - */ - public void clearExpand() { - if (expandWrapper != null) { - // Detach all widgets before modifying DOM - for (Widget widget : getChildren()) { - orphan(widget); - } - - lastExpandSize = -1; - while (expandWrapper.getChildCount() > 0) { - Element el = expandWrapper.getChild(0).cast(); - getElement().appendChild(el); - if (vertical) { - el.getStyle().clearHeight(); - el.getStyle().clearMarginTop(); - } else { - el.getStyle().clearWidth(); - el.getStyle().clearMarginLeft(); - } - } - expandWrapper.removeFromParent(); - expandWrapper = null; - - // Attach children again - for (Widget widget : getChildren()) { - adopt(widget); - } - } - } - - /** - * Updates the expand compensation based on the measured sizes of children - * without expand. - */ - public void updateExpandCompensation() { - boolean isExpanding = false; - for (Widget slot : getChildren()) { - // FIXME expandRatio might be <0 - if (((Slot) slot).getExpandRatio() != 0) { - isExpanding = true; - break; - } - } - - if (isExpanding) { - /* - * Expanded slots have relative sizes that together add up to 100%. - * To make room for slots without expand, we will add padding that - * is not considered for relative sizes and a corresponding negative - * margin for the unexpanded slots. We calculate the size by summing - * the size of all non-expanded non-relative slots. - * - * Relatively sized slots without expansion are considered to get - * 0px, but we still keep them visible (causing overflows) to help - * the developer see what's happening. Forcing them to only get 0px - * would make them disappear which would avoid overflows but would - * instead cause confusion as they would then just disappear without - * any obvious reason. - */ - int totalSize = 0; - for (Widget w : getChildren()) { - Slot slot = (Slot) w; - if (slot.getExpandRatio() == 0 - && !slot.isRelativeInDirection(vertical)) { - - if (layoutManager != null) { - // TODO check caption position - if (vertical) { - int size = layoutManager.getOuterHeight(slot - .getWidget().getElement()); - if (slot.hasCaption()) { - size += layoutManager.getOuterHeight(slot - .getCaptionElement()); - } - if (size > 0) { - totalSize += size; - } - } else { - int max = -1; - max = layoutManager.getOuterWidth(slot.getWidget() - .getElement()); - if (slot.hasCaption()) { - int max2 = layoutManager.getOuterWidth(slot - .getCaptionElement()); - max = Math.max(max, max2); - } - if (max > 0) { - totalSize += max; - } - } - } else { - // FIXME expandRatio might be <0 - totalSize += vertical ? slot.getOffsetHeight() : slot - .getOffsetWidth(); - } - } - // TODO fails in Opera, always returns 0 - int spacingSize = vertical ? slot.getVerticalSpacing() : slot - .getHorizontalSpacing(); - if (spacingSize > 0) { - totalSize += spacingSize; - } - } - - // When we set the margin to the first child, we don't need - // overflow:hidden in the layout root element, since the wrapper - // would otherwise be placed outside of the layout root element - // and block events on elements below it. - if (vertical) { - expandWrapper.getStyle().setPaddingTop(totalSize, Unit.PX); - expandWrapper.getFirstChildElement().getStyle() - .setMarginTop(-totalSize, Unit.PX); - } else { - expandWrapper.getStyle().setPaddingLeft(totalSize, Unit.PX); - expandWrapper.getFirstChildElement().getStyle() - .setMarginLeft(-totalSize, Unit.PX); - } - - // Measure expanded children again if their size might have changed - if (totalSize != lastExpandSize) { - lastExpandSize = totalSize; - for (Widget w : getChildren()) { - Slot slot = (Slot) w; - // FIXME expandRatio might be <0 - if (slot.getExpandRatio() != 0) { - if (layoutManager != null) { - layoutManager.setNeedsMeasure(Util - .findConnectorFor(slot.getWidget())); - } else if (slot.getWidget() instanceof RequiresResize) { - ((RequiresResize) slot.getWidget()).onResize(); - } - } - } - } - } - WidgetUtil.forceIE8Redraw(getElement()); - } - - /** - * {@inheritDoc} - */ - @Override - public void setHeight(String height) { - super.setHeight(height); - definedHeight = (height != null && !"".equals(height)); - } - - /** - * Sets the slots style names. The style names will be prefixed with the - * v-slot prefix. - * - * @param stylenames - * The style names of the slot. - */ - public void setSlotStyleNames(Widget widget, String... stylenames) { - Slot slot = getSlot(widget); - if (slot == null) { - throw new IllegalArgumentException( - "A slot for the widget could not be found. Has the widget been added to the layout?"); - } - slot.setStyleNames(stylenames); - } - -} diff --git a/client/src/com/vaadin/client/ui/orderedlayout/VerticalLayoutConnector.java b/client/src/com/vaadin/client/ui/orderedlayout/VerticalLayoutConnector.java deleted file mode 100644 index 33ff020e89..0000000000 --- a/client/src/com/vaadin/client/ui/orderedlayout/VerticalLayoutConnector.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.orderedlayout; - -import com.vaadin.client.ui.VVerticalLayout; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.Connect.LoadStyle; -import com.vaadin.shared.ui.orderedlayout.VerticalLayoutState; -import com.vaadin.ui.VerticalLayout; - -/** - * Connects the client widget {@link VVerticalLayout} with the Vaadin server - * side counterpart {@link VerticalLayout} - */ -@Connect(value = VerticalLayout.class, loadStyle = LoadStyle.EAGER) -public class VerticalLayoutConnector extends AbstractOrderedLayoutConnector { - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.client.ui.orderedlayout.AbstractOrderedLayoutConnector#getWidget - * () - */ - @Override - public VVerticalLayout getWidget() { - return (VVerticalLayout) super.getWidget(); - } - - @Override - public VerticalLayoutState getState() { - return (VerticalLayoutState) super.getState(); - } -} diff --git a/client/src/com/vaadin/client/ui/panel/PanelConnector.java b/client/src/com/vaadin/client/ui/panel/PanelConnector.java deleted file mode 100644 index 11111df602..0000000000 --- a/client/src/com/vaadin/client/ui/panel/PanelConnector.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.panel; - -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Unit; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorHierarchyChangeEvent; -import com.vaadin.client.LayoutManager; -import com.vaadin.client.Paintable; -import com.vaadin.client.Profiler; -import com.vaadin.client.UIDL; -import com.vaadin.client.ui.AbstractSingleComponentContainerConnector; -import com.vaadin.client.ui.ClickEventHandler; -import com.vaadin.client.ui.PostLayoutListener; -import com.vaadin.client.ui.ShortcutActionHandler; -import com.vaadin.client.ui.SimpleManagedLayout; -import com.vaadin.client.ui.VPanel; -import com.vaadin.client.ui.layout.MayScrollChildren; -import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.ui.ComponentStateUtil; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.panel.PanelServerRpc; -import com.vaadin.shared.ui.panel.PanelState; -import com.vaadin.ui.Panel; - -@Connect(Panel.class) -public class PanelConnector extends AbstractSingleComponentContainerConnector - implements Paintable, SimpleManagedLayout, PostLayoutListener, - MayScrollChildren { - - private Integer uidlScrollTop; - - private ClickEventHandler clickEventHandler = new ClickEventHandler(this) { - - @Override - protected void fireClick(NativeEvent event, - MouseEventDetails mouseDetails) { - getRpcProxy(PanelServerRpc.class).click(mouseDetails); - } - }; - - private Integer uidlScrollLeft; - - @Override - public void init() { - super.init(); - VPanel panel = getWidget(); - LayoutManager layoutManager = getLayoutManager(); - - layoutManager.registerDependency(this, panel.captionNode); - layoutManager.registerDependency(this, panel.bottomDecoration); - layoutManager.registerDependency(this, panel.contentNode); - } - - @Override - public void onUnregister() { - VPanel panel = getWidget(); - LayoutManager layoutManager = getLayoutManager(); - - layoutManager.unregisterDependency(this, panel.captionNode); - layoutManager.unregisterDependency(this, panel.bottomDecoration); - layoutManager.unregisterDependency(this, panel.contentNode); - } - - @Override - public boolean delegateCaptionHandling() { - return false; - } - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (isRealUpdate(uidl)) { - - // Handle caption displaying and style names, prior generics. - // Affects size calculations - - // Restore default stylenames - getWidget().contentNode.setClassName(VPanel.CLASSNAME + "-content"); - getWidget().bottomDecoration.setClassName(VPanel.CLASSNAME - + "-deco"); - getWidget().captionNode.setClassName(VPanel.CLASSNAME + "-caption"); - boolean hasCaption = false; - if (getState().caption != null && !"".equals(getState().caption)) { - getWidget().setCaption(getState().caption); - hasCaption = true; - } else { - getWidget().setCaption(""); - getWidget().captionNode.setClassName(VPanel.CLASSNAME - + "-nocaption"); - } - - // Add proper stylenames for all elements. This way we can prevent - // unwanted CSS selector inheritance. - final String captionBaseClass = VPanel.CLASSNAME - + (hasCaption ? "-caption" : "-nocaption"); - final String contentBaseClass = VPanel.CLASSNAME + "-content"; - final String decoBaseClass = VPanel.CLASSNAME + "-deco"; - String captionClass = captionBaseClass; - String contentClass = contentBaseClass; - String decoClass = decoBaseClass; - if (ComponentStateUtil.hasStyles(getState())) { - for (String style : getState().styles) { - captionClass += " " + captionBaseClass + "-" + style; - contentClass += " " + contentBaseClass + "-" + style; - decoClass += " " + decoBaseClass + "-" + style; - } - } - getWidget().captionNode.setClassName(captionClass); - getWidget().contentNode.setClassName(contentClass); - getWidget().bottomDecoration.setClassName(decoClass); - - getWidget().makeScrollable(); - } - - if (!isRealUpdate(uidl)) { - return; - } - - clickEventHandler.handleEventHandlerRegistration(); - - getWidget().client = client; - getWidget().id = uidl.getId(); - - if (getIconUri() != null) { - getWidget().setIconUri(getIconUri(), client); - } else { - getWidget().setIconUri(null, client); - } - - getWidget().setErrorIndicatorVisible(null != getState().errorMessage); - - // We may have actions attached to this panel - if (uidl.getChildCount() > 0) { - final int cnt = uidl.getChildCount(); - for (int i = 0; i < cnt; i++) { - UIDL childUidl = uidl.getChildUIDL(i); - if (childUidl.getTag().equals("actions")) { - if (getWidget().shortcutHandler == null) { - getWidget().shortcutHandler = new ShortcutActionHandler( - getConnectorId(), client); - } - getWidget().shortcutHandler.updateActionMap(childUidl); - } - } - } - - if (getState().scrollTop != getWidget().scrollTop) { - // Sizes are not yet up to date, so changing the scroll position - // is deferred to after the layout phase - uidlScrollTop = getState().scrollTop; - } - - if (getState().scrollLeft != getWidget().scrollLeft) { - // Sizes are not yet up to date, so changing the scroll position - // is deferred to after the layout phase - uidlScrollLeft = getState().scrollLeft; - } - - // And apply tab index - getWidget().contentNode.setTabIndex(getState().tabIndex); - } - - @Override - public void updateCaption(ComponentConnector component) { - // NOP: layouts caption, errors etc not rendered in Panel - } - - @Override - public VPanel getWidget() { - return (VPanel) super.getWidget(); - } - - @Override - public void layout() { - updateSizes(); - } - - void updateSizes() { - VPanel panel = getWidget(); - - LayoutManager layoutManager = getLayoutManager(); - Profiler.enter("PanelConnector.layout getHeights"); - int top = layoutManager.getOuterHeight(panel.captionNode); - int bottom = layoutManager.getInnerHeight(panel.bottomDecoration); - Profiler.leave("PanelConnector.layout getHeights"); - - Profiler.enter("PanelConnector.layout modify style"); - Style style = panel.getElement().getStyle(); - panel.captionNode.getParentElement().getStyle() - .setMarginTop(-top, Unit.PX); - panel.bottomDecoration.getStyle().setMarginBottom(-bottom, Unit.PX); - style.setPaddingTop(top, Unit.PX); - style.setPaddingBottom(bottom, Unit.PX); - Profiler.leave("PanelConnector.layout modify style"); - - // Update scroll positions - Profiler.enter("PanelConnector.layout update scroll positions"); - panel.contentNode.setScrollTop(panel.scrollTop); - panel.contentNode.setScrollLeft(panel.scrollLeft); - Profiler.leave("PanelConnector.layout update scroll positions"); - - // Read actual value back to ensure update logic is correct - Profiler.enter("PanelConnector.layout read scroll positions"); - panel.scrollTop = panel.contentNode.getScrollTop(); - panel.scrollLeft = panel.contentNode.getScrollLeft(); - Profiler.leave("PanelConnector.layout read scroll positions"); - } - - @Override - public void postLayout() { - VPanel panel = getWidget(); - if (uidlScrollTop != null) { - panel.contentNode.setScrollTop(uidlScrollTop.intValue()); - // Read actual value back to ensure update logic is correct - // TODO Does this trigger reflows? - panel.scrollTop = panel.contentNode.getScrollTop(); - uidlScrollTop = null; - } - - if (uidlScrollLeft != null) { - panel.contentNode.setScrollLeft(uidlScrollLeft.intValue()); - // Read actual value back to ensure update logic is correct - // TODO Does this trigger reflows? - panel.scrollLeft = panel.contentNode.getScrollLeft(); - uidlScrollLeft = null; - } - } - - @Override - public PanelState getState() { - return (PanelState) super.getState(); - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - // We always have 1 child, unless the child is hidden - getWidget().setWidget(getContentWidget()); - } - -} diff --git a/client/src/com/vaadin/client/ui/passwordfield/PasswordFieldConnector.java b/client/src/com/vaadin/client/ui/passwordfield/PasswordFieldConnector.java deleted file mode 100644 index 61576fac04..0000000000 --- a/client/src/com/vaadin/client/ui/passwordfield/PasswordFieldConnector.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.passwordfield; - -import com.vaadin.client.ui.VPasswordField; -import com.vaadin.client.ui.textfield.TextFieldConnector; -import com.vaadin.shared.ui.Connect; -import com.vaadin.ui.PasswordField; - -@Connect(PasswordField.class) -public class PasswordFieldConnector extends TextFieldConnector { - - @Override - public VPasswordField getWidget() { - return (VPasswordField) super.getWidget(); - } -} diff --git a/client/src/com/vaadin/client/ui/popupview/PopupViewConnector.java b/client/src/com/vaadin/client/ui/popupview/PopupViewConnector.java deleted file mode 100644 index 6afceb75de..0000000000 --- a/client/src/com/vaadin/client/ui/popupview/PopupViewConnector.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.popupview; - -import java.util.ArrayList; -import java.util.List; - -import com.google.gwt.event.shared.HandlerRegistration; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorHierarchyChangeEvent; -import com.vaadin.client.VCaption; -import com.vaadin.client.VCaptionWrapper; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractHasComponentsConnector; -import com.vaadin.client.ui.PostLayoutListener; -import com.vaadin.client.ui.VOverlay; -import com.vaadin.client.ui.VPopupView; -import com.vaadin.shared.ui.ComponentStateUtil; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.popupview.PopupViewServerRpc; -import com.vaadin.shared.ui.popupview.PopupViewState; -import com.vaadin.ui.PopupView; - -@Connect(PopupView.class) -public class PopupViewConnector extends AbstractHasComponentsConnector - implements PostLayoutListener, VisibilityChangeHandler { - - private boolean centerAfterLayout = false; - - private final List handlerRegistration = new ArrayList(); - - @Override - protected void init() { - super.init(); - - handlerRegistration.add(getWidget().addVisibilityChangeHandler(this)); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - getWidget().setHTML(getState().html); - getWidget().popup.setHideOnMouseOut(getState().hideOnMouseOut); - } - - @Override - public PopupViewState getState() { - return (PopupViewState) super.getState(); - } - - @Override - public void updateCaption(ComponentConnector component) { - if (VCaption.isNeeded(component.getState())) { - if (getWidget().popup.captionWrapper != null) { - getWidget().popup.captionWrapper.updateCaption(); - } else { - getWidget().popup.captionWrapper = new VCaptionWrapper( - component, getConnection()); - getWidget().popup.setWidget(getWidget().popup.captionWrapper); - getWidget().popup.captionWrapper.updateCaption(); - } - } else { - if (getWidget().popup.captionWrapper != null) { - getWidget().popup - .setWidget(getWidget().popup.popupComponentWidget); - } - } - } - - @Override - public VPopupView getWidget() { - return (VPopupView) super.getWidget(); - } - - @Override - public void postLayout() { - if (centerAfterLayout) { - centerAfterLayout = false; - getWidget().center(); - } - } - - @Override - public void onConnectorHierarchyChange( - ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) { - // Render the popup if visible and show it. - if (!getChildComponents().isEmpty()) { - getWidget().preparePopup(getWidget().popup); - getWidget().popup.setPopupConnector(getChildComponents().get(0)); - - final StringBuffer styleBuf = new StringBuffer(); - final String primaryName = getWidget().popup.getStylePrimaryName(); - styleBuf.append(primaryName); - - // Add "animate-in" class back if already present - boolean isAnimatingIn = getWidget().popup.getStyleName().contains( - VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN); - - if (isAnimatingIn) { - styleBuf.append(" "); - styleBuf.append(primaryName); - styleBuf.append("-"); - styleBuf.append(VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN); - } - - if (ComponentStateUtil.hasStyles(getState())) { - for (String style : getState().styles) { - styleBuf.append(" "); - styleBuf.append(primaryName); - styleBuf.append("-"); - styleBuf.append(style); - } - } - - getWidget().popup.setStyleName(styleBuf.toString()); - getWidget().showPopup(getWidget().popup); - centerAfterLayout = true; - - } else { - // The popup shouldn't be visible, try to hide it. - getWidget().popup.hide(); - } - } - - @Override - public void onVisibilityChange(VisibilityChangeEvent event) { - getRpcProxy(PopupViewServerRpc.class).setPopupVisibility( - event.isVisible()); - } - -} diff --git a/client/src/com/vaadin/client/ui/popupview/VisibilityChangeEvent.java b/client/src/com/vaadin/client/ui/popupview/VisibilityChangeEvent.java deleted file mode 100644 index 8db2d1cb85..0000000000 --- a/client/src/com/vaadin/client/ui/popupview/VisibilityChangeEvent.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.popupview; - -import com.google.gwt.event.shared.GwtEvent; - -public class VisibilityChangeEvent extends GwtEvent { - - private static Type TYPE; - - private boolean visible; - - public VisibilityChangeEvent(final boolean visible) { - this.visible = visible; - } - - public boolean isVisible() { - return visible; - } - - @Override - public Type getAssociatedType() { - return getType(); - } - - public static Type getType() { - if (TYPE == null) { - TYPE = new Type(); - } - return TYPE; - } - - @Override - protected void dispatch(final VisibilityChangeHandler handler) { - handler.onVisibilityChange(this); - } -} diff --git a/client/src/com/vaadin/client/ui/popupview/VisibilityChangeHandler.java b/client/src/com/vaadin/client/ui/popupview/VisibilityChangeHandler.java deleted file mode 100644 index 3c5e09d1fc..0000000000 --- a/client/src/com/vaadin/client/ui/popupview/VisibilityChangeHandler.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.popupview; - -import com.google.gwt.event.shared.EventHandler; - -public interface VisibilityChangeHandler extends EventHandler { - - void onVisibilityChange(VisibilityChangeEvent event); -} diff --git a/client/src/com/vaadin/client/ui/progressindicator/ProgressBarConnector.java b/client/src/com/vaadin/client/ui/progressindicator/ProgressBarConnector.java deleted file mode 100644 index 3a83430f7a..0000000000 --- a/client/src/com/vaadin/client/ui/progressindicator/ProgressBarConnector.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.progressindicator; - -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractFieldConnector; -import com.vaadin.client.ui.VProgressBar; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.progressindicator.ProgressBarState; -import com.vaadin.ui.ProgressBar; - -/** - * Connector for {@link VProgressBar}. - * - * @since 7.1 - * @author Vaadin Ltd - */ -@Connect(ProgressBar.class) -public class ProgressBarConnector extends AbstractFieldConnector { - - public ProgressBarConnector() { - super(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - getWidget().setIndeterminate(getState().indeterminate); - getWidget().setState(getState().state); - } - - @Override - public ProgressBarState getState() { - return (ProgressBarState) super.getState(); - } - - @Override - public VProgressBar getWidget() { - return (VProgressBar) super.getWidget(); - } - -} diff --git a/client/src/com/vaadin/client/ui/progressindicator/ProgressIndicatorConnector.java b/client/src/com/vaadin/client/ui/progressindicator/ProgressIndicatorConnector.java deleted file mode 100644 index 36bb1dd6b2..0000000000 --- a/client/src/com/vaadin/client/ui/progressindicator/ProgressIndicatorConnector.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.progressindicator; - -import com.google.gwt.user.client.Timer; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.VProgressBar; -import com.vaadin.client.ui.VProgressIndicator; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.progressindicator.ProgressIndicatorServerRpc; -import com.vaadin.shared.ui.progressindicator.ProgressIndicatorState; -import com.vaadin.ui.ProgressIndicator; - -/** - * Connector for {@link VProgressBar} with polling support. - * - * @since 7.0 - * @author Vaadin Ltd - * @deprecated as of 7.1, use {@link ProgressBarConnector} combined with server - * push or UI polling. - */ -@Connect(ProgressIndicator.class) -@Deprecated -public class ProgressIndicatorConnector extends ProgressBarConnector { - - @Override - public ProgressIndicatorState getState() { - return (ProgressIndicatorState) super.getState(); - } - - private Timer poller = new Timer() { - - @Override - public void run() { - getRpcProxy(ProgressIndicatorServerRpc.class).poll(); - } - - }; - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - if (isEnabled()) { - poller.scheduleRepeating(getState().pollingInterval); - } else { - poller.cancel(); - } - } - - @Override - public VProgressIndicator getWidget() { - return (VProgressIndicator) super.getWidget(); - } - - @Override - public void onUnregister() { - super.onUnregister(); - poller.cancel(); - } -} diff --git a/client/src/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java b/client/src/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java deleted file mode 100644 index bcf61a9338..0000000000 --- a/client/src/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.richtextarea; - -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.user.client.Event; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.Paintable; -import com.vaadin.client.UIDL; -import com.vaadin.client.ui.AbstractFieldConnector; -import com.vaadin.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; -import com.vaadin.client.ui.SimpleManagedLayout; -import com.vaadin.client.ui.VRichTextArea; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.Connect.LoadStyle; -import com.vaadin.shared.ui.textarea.RichTextAreaState; -import com.vaadin.shared.util.SharedUtil; -import com.vaadin.ui.RichTextArea; - -@Connect(value = RichTextArea.class, loadStyle = LoadStyle.LAZY) -public class RichTextAreaConnector extends AbstractFieldConnector implements - Paintable, BeforeShortcutActionListener, SimpleManagedLayout { - - /* - * Last value received from the server - */ - private String cachedValue = ""; - - @Override - protected void init() { - getWidget().addBlurHandler(new BlurHandler() { - - @Override - public void onBlur(BlurEvent event) { - flush(); - } - }); - getLayoutManager().registerDependency(this, - getWidget().formatter.getElement()); - } - - @Override - public void onUnregister() { - super.onUnregister(); - getLayoutManager().unregisterDependency(this, - getWidget().formatter.getElement()); - } - - @Override - public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) { - getWidget().client = client; - getWidget().id = uidl.getId(); - - if (uidl.hasVariable("text")) { - String newValue = uidl.getStringVariable("text"); - if (!SharedUtil.equals(newValue, cachedValue)) { - getWidget().setValue(newValue); - cachedValue = newValue; - } - } - - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().setEnabled(isEnabled()); - getWidget().setReadOnly(isReadOnly()); - getWidget().immediate = getState().immediate; - int newMaxLength = uidl.hasAttribute("maxLength") ? uidl - .getIntAttribute("maxLength") : -1; - if (newMaxLength >= 0) { - if (getWidget().maxLength == -1) { - getWidget().keyPressHandler = getWidget().rta - .addKeyPressHandler(getWidget()); - } - getWidget().maxLength = newMaxLength; - } else if (getWidget().maxLength != -1) { - getWidget().getElement().setAttribute("maxlength", ""); - getWidget().maxLength = -1; - getWidget().keyPressHandler.removeHandler(); - } - - if (uidl.hasAttribute("selectAll")) { - getWidget().selectAll(); - } - - } - - @Override - public void onBeforeShortcutAction(Event e) { - flush(); - } - - @Override - public VRichTextArea getWidget() { - return (VRichTextArea) super.getWidget(); - } - - @Override - public void flush() { - if (getConnection() != null && getConnectorId() != null) { - final String html = getWidget().getSanitizedValue(); - if (!html.equals(cachedValue)) { - cachedValue = html; - getConnection().updateVariable(getConnectorId(), "text", html, - getState().immediate); - } - } - } - - @Override - public void layout() { - if (!isUndefinedHeight()) { - int rootElementInnerHeight = getLayoutManager().getInnerHeight( - getWidget().getElement()); - int formatterHeight = getLayoutManager().getOuterHeight( - getWidget().formatter.getElement()); - int editorHeight = rootElementInnerHeight - formatterHeight; - if (editorHeight < 0) { - editorHeight = 0; - } - getWidget().rta.setHeight(editorHeight + "px"); - } - } - - @Override - public RichTextAreaState getState() { - return (RichTextAreaState) super.getState(); - } -} diff --git a/client/src/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties b/client/src/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties deleted file mode 100644 index 363b704584..0000000000 --- a/client/src/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties +++ /dev/null @@ -1,35 +0,0 @@ -bold = Toggle Bold -createLink = Create Link -hr = Insert Horizontal Rule -indent = Indent Right -insertImage = Insert Image -italic = Toggle Italic -justifyCenter = Center -justifyLeft = Left Justify -justifyRight = Right Justify -ol = Insert Ordered List -outdent = Indent Left -removeFormat = Remove Formatting -removeLink = Remove Link -strikeThrough = Toggle Strikethrough -subscript = Toggle Subscript -superscript = Toggle Superscript -ul = Insert Unordered List -underline = Toggle Underline -color = Color -black = Black -white = White -red = Red -green = Green -yellow = Yellow -blue = Blue -font = Font -normal = Normal -size = Size -xxsmall = XX-Small -xsmall = X-Small -small = Small -medium = Medium -large = Large -xlarge = X-Large -xxlarge = XX-Large \ No newline at end of file diff --git a/client/src/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java b/client/src/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java deleted file mode 100644 index 2e0554c499..0000000000 --- a/client/src/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java +++ /dev/null @@ -1,476 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -/* - * Copyright 2007 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.richtextarea; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; -import com.google.gwt.i18n.client.Constants; -import com.google.gwt.resources.client.ClientBundle; -import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.Image; -import com.google.gwt.user.client.ui.ListBox; -import com.google.gwt.user.client.ui.PushButton; -import com.google.gwt.user.client.ui.RichTextArea; -import com.google.gwt.user.client.ui.ToggleButton; - -/** - * A modified version of sample toolbar for use with {@link RichTextArea}. It - * provides a simple UI for all rich text formatting, dynamically displayed only - * for the available functionality. - */ -public class VRichTextToolbar extends Composite { - - /** - * This {@link ClientBundle} is used for all the button icons. Using a - * bundle allows all of these images to be packed into a single image, which - * saves a lot of HTTP requests, drastically improving startup time. - */ - public interface Images extends ClientBundle { - - ImageResource bold(); - - ImageResource createLink(); - - ImageResource hr(); - - ImageResource indent(); - - ImageResource insertImage(); - - ImageResource italic(); - - ImageResource justifyCenter(); - - ImageResource justifyLeft(); - - ImageResource justifyRight(); - - ImageResource ol(); - - ImageResource outdent(); - - ImageResource removeFormat(); - - ImageResource removeLink(); - - ImageResource strikeThrough(); - - ImageResource subscript(); - - ImageResource superscript(); - - ImageResource ul(); - - ImageResource underline(); - } - - /** - * This {@link Constants} interface is used to make the toolbar's strings - * internationalizable. - */ - public interface Strings extends Constants { - - String black(); - - String blue(); - - String bold(); - - String color(); - - String createLink(); - - String font(); - - String green(); - - String hr(); - - String indent(); - - String insertImage(); - - String italic(); - - String justifyCenter(); - - String justifyLeft(); - - String justifyRight(); - - String large(); - - String medium(); - - String normal(); - - String ol(); - - String outdent(); - - String red(); - - String removeFormat(); - - String removeLink(); - - String size(); - - String small(); - - String strikeThrough(); - - String subscript(); - - String superscript(); - - String ul(); - - String underline(); - - String white(); - - String xlarge(); - - String xsmall(); - - String xxlarge(); - - String xxsmall(); - - String yellow(); - } - - /** - * We use an inner EventHandler class to avoid exposing event methods on the - * RichTextToolbar itself. - */ - private class EventHandler implements ClickHandler, ChangeHandler, - KeyUpHandler { - - @Override - @SuppressWarnings("deprecation") - public void onChange(ChangeEvent event) { - Object sender = event.getSource(); - if (sender == backColors) { - basic.setBackColor(backColors.getValue(backColors - .getSelectedIndex())); - backColors.setSelectedIndex(0); - } else if (sender == foreColors) { - basic.setForeColor(foreColors.getValue(foreColors - .getSelectedIndex())); - foreColors.setSelectedIndex(0); - } else if (sender == fonts) { - basic.setFontName(fonts.getValue(fonts.getSelectedIndex())); - fonts.setSelectedIndex(0); - } else if (sender == fontSizes) { - basic.setFontSize(fontSizesConstants[fontSizes - .getSelectedIndex() - 1]); - fontSizes.setSelectedIndex(0); - } - } - - @Override - @SuppressWarnings("deprecation") - public void onClick(ClickEvent event) { - Object sender = event.getSource(); - if (sender == bold) { - basic.toggleBold(); - } else if (sender == italic) { - basic.toggleItalic(); - } else if (sender == underline) { - basic.toggleUnderline(); - } else if (sender == subscript) { - basic.toggleSubscript(); - } else if (sender == superscript) { - basic.toggleSuperscript(); - } else if (sender == strikethrough) { - extended.toggleStrikethrough(); - } else if (sender == indent) { - extended.rightIndent(); - } else if (sender == outdent) { - extended.leftIndent(); - } else if (sender == justifyLeft) { - basic.setJustification(RichTextArea.Justification.LEFT); - } else if (sender == justifyCenter) { - basic.setJustification(RichTextArea.Justification.CENTER); - } else if (sender == justifyRight) { - basic.setJustification(RichTextArea.Justification.RIGHT); - } else if (sender == insertImage) { - final String url = Window.prompt("Enter an image URL:", - "http://"); - if (url != null) { - extended.insertImage(url); - } - } else if (sender == createLink) { - final String url = Window - .prompt("Enter a link URL:", "http://"); - if (url != null) { - extended.createLink(url); - } - } else if (sender == removeLink) { - extended.removeLink(); - } else if (sender == hr) { - extended.insertHorizontalRule(); - } else if (sender == ol) { - extended.insertOrderedList(); - } else if (sender == ul) { - extended.insertUnorderedList(); - } else if (sender == removeFormat) { - extended.removeFormat(); - } else if (sender == richText) { - // We use the RichTextArea's onKeyUp event to update the toolbar - // status. This will catch any cases where the user moves the - // cursur using the keyboard, or uses one of the browser's - // built-in keyboard shortcuts. - updateStatus(); - } - } - - @Override - public void onKeyUp(KeyUpEvent event) { - if (event.getSource() == richText) { - // We use the RichTextArea's onKeyUp event to update the toolbar - // status. This will catch any cases where the user moves the - // cursor using the keyboard, or uses one of the browser's - // built-in keyboard shortcuts. - updateStatus(); - } - } - } - - private static final RichTextArea.FontSize[] fontSizesConstants = new RichTextArea.FontSize[] { - RichTextArea.FontSize.XX_SMALL, RichTextArea.FontSize.X_SMALL, - RichTextArea.FontSize.SMALL, RichTextArea.FontSize.MEDIUM, - RichTextArea.FontSize.LARGE, RichTextArea.FontSize.X_LARGE, - RichTextArea.FontSize.XX_LARGE }; - - private final Images images = (Images) GWT.create(Images.class); - private final Strings strings = (Strings) GWT.create(Strings.class); - private final EventHandler handler = new EventHandler(); - - private final RichTextArea richText; - @SuppressWarnings("deprecation") - private final RichTextArea.BasicFormatter basic; - @SuppressWarnings("deprecation") - private final RichTextArea.ExtendedFormatter extended; - - private final FlowPanel outer = new FlowPanel(); - private final FlowPanel topPanel = new FlowPanel(); - private final FlowPanel bottomPanel = new FlowPanel(); - private ToggleButton bold; - private ToggleButton italic; - private ToggleButton underline; - private ToggleButton subscript; - private ToggleButton superscript; - private ToggleButton strikethrough; - private PushButton indent; - private PushButton outdent; - private PushButton justifyLeft; - private PushButton justifyCenter; - private PushButton justifyRight; - private PushButton hr; - private PushButton ol; - private PushButton ul; - private PushButton insertImage; - private PushButton createLink; - private PushButton removeLink; - private PushButton removeFormat; - - private ListBox backColors; - private ListBox foreColors; - private ListBox fonts; - private ListBox fontSizes; - - /** - * Creates a new toolbar that drives the given rich text area. - * - * @param richText - * the rich text area to be controlled - */ - @SuppressWarnings("deprecation") - public VRichTextToolbar(RichTextArea richText) { - this.richText = richText; - basic = richText.getBasicFormatter(); - extended = richText.getExtendedFormatter(); - - outer.add(topPanel); - outer.add(bottomPanel); - topPanel.setStyleName("gwt-RichTextToolbar-top"); - bottomPanel.setStyleName("gwt-RichTextToolbar-bottom"); - - initWidget(outer); - setStyleName("gwt-RichTextToolbar"); - - if (basic != null) { - topPanel.add(bold = createToggleButton(images.bold(), - strings.bold())); - topPanel.add(italic = createToggleButton(images.italic(), - strings.italic())); - topPanel.add(underline = createToggleButton(images.underline(), - strings.underline())); - topPanel.add(subscript = createToggleButton(images.subscript(), - strings.subscript())); - topPanel.add(superscript = createToggleButton(images.superscript(), - strings.superscript())); - topPanel.add(justifyLeft = createPushButton(images.justifyLeft(), - strings.justifyLeft())); - topPanel.add(justifyCenter = createPushButton( - images.justifyCenter(), strings.justifyCenter())); - topPanel.add(justifyRight = createPushButton(images.justifyRight(), - strings.justifyRight())); - } - - if (extended != null) { - topPanel.add(strikethrough = createToggleButton( - images.strikeThrough(), strings.strikeThrough())); - topPanel.add(indent = createPushButton(images.indent(), - strings.indent())); - topPanel.add(outdent = createPushButton(images.outdent(), - strings.outdent())); - topPanel.add(hr = createPushButton(images.hr(), strings.hr())); - topPanel.add(ol = createPushButton(images.ol(), strings.ol())); - topPanel.add(ul = createPushButton(images.ul(), strings.ul())); - topPanel.add(insertImage = createPushButton(images.insertImage(), - strings.insertImage())); - topPanel.add(createLink = createPushButton(images.createLink(), - strings.createLink())); - topPanel.add(removeLink = createPushButton(images.removeLink(), - strings.removeLink())); - topPanel.add(removeFormat = createPushButton(images.removeFormat(), - strings.removeFormat())); - } - - if (basic != null) { - bottomPanel.add(backColors = createColorList("Background")); - bottomPanel.add(foreColors = createColorList("Foreground")); - bottomPanel.add(fonts = createFontList()); - bottomPanel.add(fontSizes = createFontSizes()); - - // We only use these handlers for updating status, so don't hook - // them up unless at least basic editing is supported. - richText.addKeyUpHandler(handler); - richText.addClickHandler(handler); - } - } - - private ListBox createColorList(String caption) { - final ListBox lb = new ListBox(); - lb.addChangeHandler(handler); - lb.setVisibleItemCount(1); - - lb.addItem(caption); - lb.addItem(strings.white(), "white"); - lb.addItem(strings.black(), "black"); - lb.addItem(strings.red(), "red"); - lb.addItem(strings.green(), "green"); - lb.addItem(strings.yellow(), "yellow"); - lb.addItem(strings.blue(), "blue"); - lb.setTabIndex(-1); - return lb; - } - - private ListBox createFontList() { - final ListBox lb = new ListBox(); - lb.addChangeHandler(handler); - lb.setVisibleItemCount(1); - - lb.addItem(strings.font(), ""); - lb.addItem(strings.normal(), "inherit"); - lb.addItem("Times New Roman", "Times New Roman"); - lb.addItem("Arial", "Arial"); - lb.addItem("Courier New", "Courier New"); - lb.addItem("Georgia", "Georgia"); - lb.addItem("Trebuchet", "Trebuchet"); - lb.addItem("Verdana", "Verdana"); - lb.setTabIndex(-1); - return lb; - } - - private ListBox createFontSizes() { - final ListBox lb = new ListBox(); - lb.addChangeHandler(handler); - lb.setVisibleItemCount(1); - - lb.addItem(strings.size()); - lb.addItem(strings.xxsmall()); - lb.addItem(strings.xsmall()); - lb.addItem(strings.small()); - lb.addItem(strings.medium()); - lb.addItem(strings.large()); - lb.addItem(strings.xlarge()); - lb.addItem(strings.xxlarge()); - lb.setTabIndex(-1); - return lb; - } - - private PushButton createPushButton(ImageResource img, String tip) { - final PushButton pb = new PushButton(new Image(img)); - pb.addClickHandler(handler); - pb.setTitle(tip); - pb.setTabIndex(-1); - return pb; - } - - private ToggleButton createToggleButton(ImageResource img, String tip) { - final ToggleButton tb = new ToggleButton(new Image(img)); - tb.addClickHandler(handler); - tb.setTitle(tip); - tb.setTabIndex(-1); - return tb; - } - - /** - * Updates the status of all the stateful buttons. - */ - @SuppressWarnings("deprecation") - private void updateStatus() { - if (basic != null) { - bold.setDown(basic.isBold()); - italic.setDown(basic.isItalic()); - underline.setDown(basic.isUnderlined()); - subscript.setDown(basic.isSubscript()); - superscript.setDown(basic.isSuperscript()); - } - - if (extended != null) { - strikethrough.setDown(extended.isStrikethrough()); - } - } -} diff --git a/client/src/com/vaadin/client/ui/richtextarea/backColors.gif b/client/src/com/vaadin/client/ui/richtextarea/backColors.gif deleted file mode 100644 index ddfc1cea2c..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/backColors.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/bold.gif b/client/src/com/vaadin/client/ui/richtextarea/bold.gif deleted file mode 100644 index 7c22eaac68..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/bold.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/createLink.gif b/client/src/com/vaadin/client/ui/richtextarea/createLink.gif deleted file mode 100644 index 1a1412fe0e..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/createLink.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/fontSizes.gif b/client/src/com/vaadin/client/ui/richtextarea/fontSizes.gif deleted file mode 100644 index c2f4c8cb21..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/fontSizes.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/fonts.gif b/client/src/com/vaadin/client/ui/richtextarea/fonts.gif deleted file mode 100644 index 1629cabb78..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/fonts.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/foreColors.gif b/client/src/com/vaadin/client/ui/richtextarea/foreColors.gif deleted file mode 100644 index 2bb89ef189..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/foreColors.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/gwtLogo.png b/client/src/com/vaadin/client/ui/richtextarea/gwtLogo.png deleted file mode 100644 index 80728186d8..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/gwtLogo.png and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/hr.gif b/client/src/com/vaadin/client/ui/richtextarea/hr.gif deleted file mode 100644 index d507082cf1..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/hr.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/indent.gif b/client/src/com/vaadin/client/ui/richtextarea/indent.gif deleted file mode 100644 index 905421ed76..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/indent.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/insertImage.gif b/client/src/com/vaadin/client/ui/richtextarea/insertImage.gif deleted file mode 100644 index 394ec432a5..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/insertImage.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/italic.gif b/client/src/com/vaadin/client/ui/richtextarea/italic.gif deleted file mode 100644 index ffe0e97284..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/italic.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/justifyCenter.gif b/client/src/com/vaadin/client/ui/richtextarea/justifyCenter.gif deleted file mode 100644 index f7d4c4693d..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/justifyCenter.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/justifyLeft.gif b/client/src/com/vaadin/client/ui/richtextarea/justifyLeft.gif deleted file mode 100644 index bc37a3ed5a..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/justifyLeft.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/justifyRight.gif b/client/src/com/vaadin/client/ui/richtextarea/justifyRight.gif deleted file mode 100644 index 892d569384..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/justifyRight.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/ol.gif b/client/src/com/vaadin/client/ui/richtextarea/ol.gif deleted file mode 100644 index 54f8e4f551..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/ol.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/outdent.gif b/client/src/com/vaadin/client/ui/richtextarea/outdent.gif deleted file mode 100644 index 78fd1b5722..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/outdent.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/removeFormat.gif b/client/src/com/vaadin/client/ui/richtextarea/removeFormat.gif deleted file mode 100644 index cf92c9774f..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/removeFormat.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/removeLink.gif b/client/src/com/vaadin/client/ui/richtextarea/removeLink.gif deleted file mode 100644 index 40721a7bca..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/removeLink.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/strikeThrough.gif b/client/src/com/vaadin/client/ui/richtextarea/strikeThrough.gif deleted file mode 100644 index a7a233c023..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/strikeThrough.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/subscript.gif b/client/src/com/vaadin/client/ui/richtextarea/subscript.gif deleted file mode 100644 index 58b6fbb816..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/subscript.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/superscript.gif b/client/src/com/vaadin/client/ui/richtextarea/superscript.gif deleted file mode 100644 index a6270f6e21..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/superscript.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/ul.gif b/client/src/com/vaadin/client/ui/richtextarea/ul.gif deleted file mode 100644 index 83f1562bcb..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/ul.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/richtextarea/underline.gif b/client/src/com/vaadin/client/ui/richtextarea/underline.gif deleted file mode 100644 index 06f0200fdd..0000000000 Binary files a/client/src/com/vaadin/client/ui/richtextarea/underline.gif and /dev/null differ diff --git a/client/src/com/vaadin/client/ui/slider/SliderConnector.java b/client/src/com/vaadin/client/ui/slider/SliderConnector.java deleted file mode 100644 index 8c3c0254d3..0000000000 --- a/client/src/com/vaadin/client/ui/slider/SliderConnector.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.slider; - -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.vaadin.client.communication.RpcProxy; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractFieldConnector; -import com.vaadin.client.ui.VSlider; -import com.vaadin.client.ui.layout.ElementResizeEvent; -import com.vaadin.client.ui.layout.ElementResizeListener; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.slider.SliderServerRpc; -import com.vaadin.shared.ui.slider.SliderState; -import com.vaadin.ui.Slider; - -@Connect(Slider.class) -public class SliderConnector extends AbstractFieldConnector implements - ValueChangeHandler { - - protected SliderServerRpc rpc = RpcProxy - .create(SliderServerRpc.class, this); - - private final ElementResizeListener resizeListener = new ElementResizeListener() { - - @Override - public void onElementResize(ElementResizeEvent e) { - getWidget().iLayout(); - } - }; - - @Override - public void init() { - super.init(); - getWidget().setConnection(getConnection()); - getWidget().addValueChangeHandler(this); - - getLayoutManager().addElementResizeListener(getWidget().getElement(), - resizeListener); - } - - @Override - public void onUnregister() { - super.onUnregister(); - getLayoutManager().removeElementResizeListener( - getWidget().getElement(), resizeListener); - } - - @Override - public VSlider getWidget() { - return (VSlider) super.getWidget(); - } - - @Override - public SliderState getState() { - return (SliderState) super.getState(); - } - - @Override - public void onValueChange(ValueChangeEvent event) { - getState().value = event.getValue(); - rpc.valueChanged(event.getValue()); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - getWidget().setId(getConnectorId()); - getWidget().setImmediate(getState().immediate); - getWidget().setDisabled(!isEnabled()); - getWidget().setReadOnly(isReadOnly()); - getWidget().setOrientation(getState().orientation); - getWidget().setMinValue(getState().minValue); - getWidget().setMaxValue(getState().maxValue); - getWidget().setResolution(getState().resolution); - getWidget().setValue(getState().value, false); - - getWidget().buildBase(); - getWidget().setTabIndex(getState().tabIndex); - } - -} diff --git a/client/src/com/vaadin/client/ui/splitpanel/AbstractSplitPanelConnector.java b/client/src/com/vaadin/client/ui/splitpanel/AbstractSplitPanelConnector.java deleted file mode 100644 index 6bf03ad880..0000000000 --- a/client/src/com/vaadin/client/ui/splitpanel/AbstractSplitPanelConnector.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.splitpanel; - -import java.util.LinkedList; -import java.util.List; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.event.dom.client.DomEvent; -import com.google.gwt.event.dom.client.DomEvent.Type; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorHierarchyChangeEvent; -import com.vaadin.client.LayoutManager; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractComponentContainerConnector; -import com.vaadin.client.ui.ClickEventHandler; -import com.vaadin.client.ui.SimpleManagedLayout; -import com.vaadin.client.ui.VAbstractSplitPanel; -import com.vaadin.client.ui.VAbstractSplitPanel.SplitterMoveHandler; -import com.vaadin.client.ui.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent; -import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.ui.ComponentStateUtil; -import com.vaadin.shared.ui.splitpanel.AbstractSplitPanelRpc; -import com.vaadin.shared.ui.splitpanel.AbstractSplitPanelState; -import com.vaadin.shared.ui.splitpanel.AbstractSplitPanelState.SplitterState; - -public abstract class AbstractSplitPanelConnector extends - AbstractComponentContainerConnector implements SimpleManagedLayout { - - @Override - protected void init() { - super.init(); - // TODO Remove - getWidget().client = getConnection(); - - getWidget().addHandler(new SplitterMoveHandler() { - - @Override - public void splitterMoved(SplitterMoveEvent event) { - String position = getWidget().getSplitterPosition(); - float pos = 0; - if (position.indexOf("%") > 0) { - // Send % values as a fraction to avoid that the splitter - // "jumps" when server responds with the integer pct value - // (e.g. dragged 16.6% -> should not jump to 17%) - pos = Float.valueOf(position.substring(0, - position.length() - 1)); - } else { - pos = Integer.parseInt(position.substring(0, - position.length() - 2)); - } - - getRpcProxy(AbstractSplitPanelRpc.class).setSplitterPosition( - pos); - } - - }, SplitterMoveEvent.TYPE); - } - - @Override - public void updateCaption(ComponentConnector component) { - // TODO Implement caption handling - } - - ClickEventHandler clickEventHandler = new ClickEventHandler(this) { - - @Override - protected HandlerRegistration registerHandler( - H handler, Type type) { - if ((Event.getEventsSunk(getWidget().splitter) & Event - .getTypeInt(type.getName())) != 0) { - // If we are already sinking the event for the splitter we do - // not want to additionally sink it for the root element - return getWidget().addHandler(handler, type); - } else { - return getWidget().addDomHandler(handler, type); - } - } - - @Override - protected boolean shouldFireEvent(DomEvent event) { - Element target = event.getNativeEvent().getEventTarget().cast(); - if (!getWidget().splitter.isOrHasChild(target)) { - return false; - } - - return super.shouldFireEvent(event); - } - - @Override - protected com.google.gwt.user.client.Element getRelativeToElement() { - return DOM.asOld(getWidget().splitter); - } - - @Override - protected void fireClick(NativeEvent event, - MouseEventDetails mouseDetails) { - getRpcProxy(AbstractSplitPanelRpc.class) - .splitterClick(mouseDetails); - } - - }; - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - getWidget().immediate = getState().immediate; - - getWidget().setEnabled(isEnabled()); - - clickEventHandler.handleEventHandlerRegistration(); - - if (ComponentStateUtil.hasStyles(getState())) { - getWidget().componentStyleNames = getState().styles; - } else { - getWidget().componentStyleNames = new LinkedList(); - } - - // Splitter updates - SplitterState splitterState = getState().splitterState; - - getWidget().setStylenames(); - - getWidget().minimumPosition = splitterState.minPosition - + splitterState.minPositionUnit; - - getWidget().maximumPosition = splitterState.maxPosition - + splitterState.maxPositionUnit; - - getWidget().position = splitterState.position - + splitterState.positionUnit; - - getWidget().setPositionReversed(splitterState.positionReversed); - - getWidget().setLocked(splitterState.locked); - - // This is needed at least for cases like #3458 to take - // appearing/disappearing scrollbars into account. - getConnection().runDescendentsLayout(getWidget()); - - getLayoutManager().setNeedsLayout(this); - - getWidget().makeScrollable(); - - handleSingleComponentMove(); - } - - /** - * Handles the case when there is only one child component and that - * component is moved between first <-> second. This does not trigger a - * hierarchy change event as the list of children contains the same - * component in both cases. - */ - private void handleSingleComponentMove() { - if (getChildComponents().size() == 1) { - Widget stateFirstChild = null; - Widget stateSecondChild = null; - if (getState().firstChild != null) { - stateFirstChild = ((ComponentConnector) getState().firstChild) - .getWidget(); - } - if (getState().secondChild != null) { - stateSecondChild = ((ComponentConnector) getState().secondChild) - .getWidget(); - } - - if (stateFirstChild == getWidget().getSecondWidget() - || stateSecondChild == getWidget().getFirstWidget()) { - handleHierarchyChange(); - } - } - - } - - @Override - public void layout() { - VAbstractSplitPanel splitPanel = getWidget(); - splitPanel.setSplitPosition(splitPanel.position); - splitPanel.updateSizes(); - // Report relative sizes in other direction for quicker propagation - List children = getChildComponents(); - for (ComponentConnector child : children) { - reportOtherDimension(child); - } - } - - private void reportOtherDimension(ComponentConnector child) { - LayoutManager layoutManager = getLayoutManager(); - if (this instanceof HorizontalSplitPanelConnector) { - if (child.isRelativeHeight()) { - int height = layoutManager.getInnerHeight(getWidget() - .getElement()); - layoutManager.reportHeightAssignedToRelative(child, height); - } - } else { - if (child.isRelativeWidth()) { - int width = layoutManager.getInnerWidth(getWidget() - .getElement()); - layoutManager.reportWidthAssignedToRelative(child, width); - } - } - } - - @Override - public VAbstractSplitPanel getWidget() { - return (VAbstractSplitPanel) super.getWidget(); - } - - @Override - public AbstractSplitPanelState getState() { - return (AbstractSplitPanelState) super.getState(); - } - - private ComponentConnector getFirstChild() { - return (ComponentConnector) getState().firstChild; - } - - private ComponentConnector getSecondChild() { - return (ComponentConnector) getState().secondChild; - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - handleHierarchyChange(); - } - - private void handleHierarchyChange() { - /* - * When the connector gets detached, the state isn't updated but there's - * still a hierarchy change -> verify that the child from the state is - * still our child before attaching the widget. See #10150. - */ - - Widget newFirstChildWidget = null; - ComponentConnector firstChild = getFirstChild(); - if (firstChild != null && firstChild.getParent() == this) { - newFirstChildWidget = firstChild.getWidget(); - } - getWidget().setFirstWidget(newFirstChildWidget); - - Widget newSecondChildWidget = null; - ComponentConnector secondChild = getSecondChild(); - if (secondChild != null && secondChild.getParent() == this) { - newSecondChildWidget = secondChild.getWidget(); - } - getWidget().setSecondWidget(newSecondChildWidget); - } -} diff --git a/client/src/com/vaadin/client/ui/splitpanel/HorizontalSplitPanelConnector.java b/client/src/com/vaadin/client/ui/splitpanel/HorizontalSplitPanelConnector.java deleted file mode 100644 index 75389b6cc4..0000000000 --- a/client/src/com/vaadin/client/ui/splitpanel/HorizontalSplitPanelConnector.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.splitpanel; - -import com.vaadin.client.ui.VSplitPanelHorizontal; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.Connect.LoadStyle; -import com.vaadin.shared.ui.splitpanel.HorizontalSplitPanelState; -import com.vaadin.ui.HorizontalSplitPanel; - -@Connect(value = HorizontalSplitPanel.class, loadStyle = LoadStyle.EAGER) -public class HorizontalSplitPanelConnector extends AbstractSplitPanelConnector { - - @Override - public VSplitPanelHorizontal getWidget() { - return (VSplitPanelHorizontal) super.getWidget(); - } - - @Override - public HorizontalSplitPanelState getState() { - return (HorizontalSplitPanelState) super.getState(); - } - -} diff --git a/client/src/com/vaadin/client/ui/splitpanel/VerticalSplitPanelConnector.java b/client/src/com/vaadin/client/ui/splitpanel/VerticalSplitPanelConnector.java deleted file mode 100644 index e95f7143ba..0000000000 --- a/client/src/com/vaadin/client/ui/splitpanel/VerticalSplitPanelConnector.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.splitpanel; - -import com.vaadin.client.ui.VSplitPanelVertical; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.Connect.LoadStyle; -import com.vaadin.shared.ui.splitpanel.VerticalSplitPanelState; -import com.vaadin.ui.VerticalSplitPanel; - -@Connect(value = VerticalSplitPanel.class, loadStyle = LoadStyle.EAGER) -public class VerticalSplitPanelConnector extends AbstractSplitPanelConnector { - - @Override - public VSplitPanelVertical getWidget() { - return (VSplitPanelVertical) super.getWidget(); - } - - @Override - public VerticalSplitPanelState getState() { - return (VerticalSplitPanelState) super.getState(); - } - -} diff --git a/client/src/com/vaadin/client/ui/table/TableConnector.java b/client/src/com/vaadin/client/ui/table/TableConnector.java deleted file mode 100644 index a554b9335c..0000000000 --- a/client/src/com/vaadin/client/ui/table/TableConnector.java +++ /dev/null @@ -1,556 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.table; - -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.EventTarget; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorHierarchyChangeEvent; -import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler; -import com.vaadin.client.DirectionalManagedLayout; -import com.vaadin.client.HasChildMeasurementHintConnector; -import com.vaadin.client.HasComponentsConnector; -import com.vaadin.client.Paintable; -import com.vaadin.client.ServerConnector; -import com.vaadin.client.TooltipInfo; -import com.vaadin.client.UIDL; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.ui.AbstractFieldConnector; -import com.vaadin.client.ui.PostLayoutListener; -import com.vaadin.client.ui.VScrollTable; -import com.vaadin.client.ui.VScrollTable.ContextMenuDetails; -import com.vaadin.client.ui.VScrollTable.FooterCell; -import com.vaadin.client.ui.VScrollTable.HeaderCell; -import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; -import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.table.TableConstants; -import com.vaadin.shared.ui.table.TableConstants.Section; -import com.vaadin.shared.ui.table.TableServerRpc; -import com.vaadin.shared.ui.table.TableState; - -@Connect(com.vaadin.ui.Table.class) -public class TableConnector extends AbstractFieldConnector implements - HasComponentsConnector, ConnectorHierarchyChangeHandler, Paintable, - DirectionalManagedLayout, PostLayoutListener, - HasChildMeasurementHintConnector { - - private List childComponents; - - public TableConnector() { - addConnectorHierarchyChangeHandler(this); - } - - @Override - protected void init() { - super.init(); - getWidget().init(getConnection()); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.AbstractComponentConnector#onUnregister() - */ - @Override - public void onUnregister() { - super.onUnregister(); - getWidget().onUnregister(); - } - - @Override - protected void sendContextClickEvent(MouseEventDetails details, - EventTarget eventTarget) { - - if (!Element.is(eventTarget)) { - return; - } - Element e = Element.as(eventTarget); - - Section section; - String colKey = null; - String rowKey = null; - if (getWidget().tFoot.getElement().isOrHasChild(e)) { - section = Section.FOOTER; - FooterCell w = WidgetUtil.findWidget(e, FooterCell.class); - colKey = w.getColKey(); - } else if (getWidget().tHead.getElement().isOrHasChild(e)) { - section = Section.HEADER; - HeaderCell w = WidgetUtil.findWidget(e, HeaderCell.class); - colKey = w.getColKey(); - } else { - section = Section.BODY; - if (getWidget().scrollBody.getElement().isOrHasChild(e)) { - VScrollTableRow w = getScrollTableRow(e); - /* - * if w is null because we've clicked on an empty area, we will - * let rowKey and colKey be null too, which will then lead to - * the server side returning a null object. - */ - if (w != null) { - rowKey = w.getKey(); - colKey = getWidget().tHead.getHeaderCell( - getElementIndex(e, w.getElement())).getColKey(); - } - } - } - - getRpcProxy(TableServerRpc.class).contextClick(rowKey, colKey, section, - details); - - WidgetUtil.clearTextSelection(); - } - - protected VScrollTableRow getScrollTableRow(Element e) { - return WidgetUtil.findWidget(e, VScrollTableRow.class); - } - - private int getElementIndex(Element e, - com.google.gwt.user.client.Element element) { - int i = 0; - Element current = element.getFirstChildElement(); - while (!current.isOrHasChild(e)) { - current = current.getNextSiblingElement(); - ++i; - } - return i; - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.Paintable#updateFromUIDL(com.vaadin.client.UIDL, - * com.vaadin.client.ApplicationConnection) - */ - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - getWidget().rendering = true; - - // If a row has an open context menu, it will be closed as the row is - // detached. Retain a reference here so we can restore the menu if - // required. - ContextMenuDetails contextMenuBeforeUpdate = getWidget().contextMenu; - - if (uidl.hasAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_FIRST)) { - getWidget().serverCacheFirst = uidl - .getIntAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_FIRST); - getWidget().serverCacheLast = uidl - .getIntAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_LAST); - } else { - getWidget().serverCacheFirst = -1; - getWidget().serverCacheLast = -1; - } - /* - * We need to do this before updateComponent since updateComponent calls - * this.setHeight() which will calculate a new body height depending on - * the space available. - */ - if (uidl.hasAttribute("colfooters")) { - getWidget().showColFooters = uidl.getBooleanAttribute("colfooters"); - } - - getWidget().tFoot.setVisible(getWidget().showColFooters); - - if (!isRealUpdate(uidl)) { - getWidget().rendering = false; - return; - } - - getWidget().enabled = isEnabled(); - - if (BrowserInfo.get().isIE8() && !getWidget().enabled) { - /* - * The disabled shim will not cover the table body if it is relative - * in IE8. See #7324 - */ - getWidget().scrollBodyPanel.getElement().getStyle() - .setPosition(Position.STATIC); - } else if (BrowserInfo.get().isIE8()) { - getWidget().scrollBodyPanel.getElement().getStyle() - .setPosition(Position.RELATIVE); - } - - getWidget().paintableId = uidl.getStringAttribute("id"); - getWidget().immediate = getState().immediate; - - int previousTotalRows = getWidget().totalRows; - getWidget().updateTotalRows(uidl); - boolean totalRowsHaveChanged = (getWidget().totalRows != previousTotalRows); - - getWidget().updateDragMode(uidl); - - // Update child measure hint - int childMeasureHint = uidl.hasAttribute("measurehint") ? uidl - .getIntAttribute("measurehint") : 0; - getWidget().setChildMeasurementHint( - ChildMeasurementHint.values()[childMeasureHint]); - - getWidget().updateSelectionProperties(uidl, getState(), isReadOnly()); - - if (uidl.hasAttribute("alb")) { - getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb"); - } else { - // Need to clear the actions if the action handlers have been - // removed - getWidget().bodyActionKeys = null; - } - - getWidget().setCacheRateFromUIDL(uidl); - - getWidget().recalcWidths = uidl.hasAttribute("recalcWidths"); - if (getWidget().recalcWidths) { - getWidget().tHead.clear(); - getWidget().tFoot.clear(); - } - - getWidget().updatePageLength(uidl); - - getWidget().updateFirstVisibleAndScrollIfNeeded(uidl); - - getWidget().showRowHeaders = uidl.getBooleanAttribute("rowheaders"); - getWidget().showColHeaders = uidl.getBooleanAttribute("colheaders"); - - getWidget().updateSortingProperties(uidl); - - getWidget().updateActionMap(uidl); - - getWidget().updateColumnProperties(uidl); - - UIDL ac = uidl.getChildByTagName("-ac"); - if (ac == null) { - if (getWidget().dropHandler != null) { - // remove dropHandler if not present anymore - getWidget().dropHandler = null; - } - } else { - if (getWidget().dropHandler == null) { - getWidget().dropHandler = getWidget().new VScrollTableDropHandler(); - } - getWidget().dropHandler.updateAcceptRules(ac); - } - - UIDL partialRowAdditions = uidl.getChildByTagName("prows"); - UIDL partialRowUpdates = uidl.getChildByTagName("urows"); - if (partialRowUpdates != null || partialRowAdditions != null) { - getWidget().postponeSanityCheckForLastRendered = true; - // we may have pending cache row fetch, cancel it. See #2136 - getWidget().rowRequestHandler.cancel(); - - getWidget().updateRowsInBody(partialRowUpdates); - getWidget().addAndRemoveRows(partialRowAdditions); - - // sanity check (in case the value has slipped beyond the total - // amount of rows) - getWidget().scrollBody.setLastRendered(getWidget().scrollBody - .getLastRendered()); - getWidget().updateMaxIndent(); - } else { - getWidget().postponeSanityCheckForLastRendered = false; - UIDL rowData = uidl.getChildByTagName("rows"); - if (rowData != null) { - // we may have pending cache row fetch, cancel it. See #2136 - getWidget().rowRequestHandler.cancel(); - - if (!getWidget().recalcWidths - && getWidget().initializedAndAttached) { - getWidget().updateBody(rowData, - uidl.getIntAttribute("firstrow"), - uidl.getIntAttribute("rows")); - if (getWidget().headerChangedDuringUpdate) { - getWidget().triggerLazyColumnAdjustment(true); - } - } else { - getWidget().initializeRows(uidl, rowData); - } - } - } - - boolean keyboardSelectionOverRowFetchInProgress = getWidget() - .selectSelectedRows(uidl); - - // If a row had an open context menu before the update, and after the - // update there's a row with the same key as that row, restore the - // context menu. See #8526. - showSavedContextMenu(contextMenuBeforeUpdate); - - if (!getWidget().isSelectable()) { - getWidget().scrollBody.addStyleName(getWidget() - .getStylePrimaryName() + "-body-noselection"); - } else { - getWidget().scrollBody.removeStyleName(getWidget() - .getStylePrimaryName() + "-body-noselection"); - } - - getWidget().hideScrollPositionAnnotation(); - - // selection is no in sync with server, avoid excessive server visits by - // clearing to flag used during the normal operation - if (!keyboardSelectionOverRowFetchInProgress) { - getWidget().selectionChanged = false; - } - - /* - * This is called when the Home or page up button has been pressed in - * selectable mode and the next selected row was not yet rendered in the - * client - */ - if (getWidget().selectFirstItemInNextRender - || getWidget().focusFirstItemInNextRender) { - getWidget().selectFirstRenderedRowInViewPort( - getWidget().focusFirstItemInNextRender); - getWidget().selectFirstItemInNextRender = getWidget().focusFirstItemInNextRender = false; - } - - /* - * This is called when the page down or end button has been pressed in - * selectable mode and the next selected row was not yet rendered in the - * client - */ - if (getWidget().selectLastItemInNextRender - || getWidget().focusLastItemInNextRender) { - getWidget().selectLastRenderedRowInViewPort( - getWidget().focusLastItemInNextRender); - getWidget().selectLastItemInNextRender = getWidget().focusLastItemInNextRender = false; - } - getWidget().multiselectPending = false; - - if (getWidget().focusedRow != null) { - if (!getWidget().focusedRow.isAttached() - && !getWidget().rowRequestHandler.isRequestHandlerRunning()) { - // focused row has been orphaned, can't focus - if (getWidget().selectedRowKeys.contains(getWidget().focusedRow - .getKey())) { - // if row cache was refreshed, focused row should be - // in selection and exists with same index - getWidget().setRowFocus( - getWidget().getRenderedRowByKey( - getWidget().focusedRow.getKey())); - } else if (getWidget().selectedRowKeys.size() > 0) { - // try to focus any row in selection - getWidget().setRowFocus( - getWidget().getRenderedRowByKey( - getWidget().selectedRowKeys.iterator() - .next())); - } else { - // try to focus any row - getWidget().focusRowFromBody(); - } - } - } - - /* - * If the server has (re)initialized the rows, our selectionRangeStart - * row will point to an index that the server knows nothing about, - * causing problems if doing multi selection with shift. The field will - * be cleared a little later when the row focus has been restored. - * (#8584) - */ - if (uidl.hasAttribute(TableConstants.ATTRIBUTE_KEY_MAPPER_RESET) - && uidl.getBooleanAttribute(TableConstants.ATTRIBUTE_KEY_MAPPER_RESET) - && getWidget().selectionRangeStart != null) { - assert !getWidget().selectionRangeStart.isAttached(); - getWidget().selectionRangeStart = getWidget().focusedRow; - } - - getWidget().tabIndex = getState().tabIndex; - getWidget().setProperTabIndex(); - - Scheduler.get().scheduleFinally(new ScheduledCommand() { - - @Override - public void execute() { - getWidget().resizeSortedColumnForSortIndicator(); - } - }); - - // Remember this to detect situations where overflow hack might be - // needed during scrolling - getWidget().lastRenderedHeight = getWidget().scrollBody - .getOffsetHeight(); - - getWidget().rendering = false; - getWidget().headerChangedDuringUpdate = false; - - getWidget().collapsibleMenuContent = getState().collapseMenuContent; - } - - @Override - public VScrollTable getWidget() { - return (VScrollTable) super.getWidget(); - } - - @Override - public void updateCaption(ComponentConnector component) { - // NOP, not rendered - } - - @Override - public void layoutVertically() { - getWidget().updateHeight(); - } - - @Override - public void layoutHorizontally() { - getWidget().updateWidth(); - } - - @Override - public void postLayout() { - VScrollTable table = getWidget(); - if (table.sizeNeedsInit) { - table.sizeInit(); - Scheduler.get().scheduleFinally(new ScheduledCommand() { - @Override - public void execute() { - // IE8 needs some hacks to measure sizes correctly - WidgetUtil.forceIE8Redraw(getWidget().getElement()); - - getLayoutManager().setNeedsMeasure(TableConnector.this); - ServerConnector parent = getParent(); - if (parent instanceof ComponentConnector) { - getLayoutManager().setNeedsMeasure( - (ComponentConnector) parent); - } - getLayoutManager().setNeedsVerticalLayout( - TableConnector.this); - getLayoutManager().layoutNow(); - } - }); - } - } - - @Override - public boolean isReadOnly() { - return super.isReadOnly() || getState().propertyReadOnly; - } - - @Override - public TableState getState() { - return (TableState) super.getState(); - } - - /** - * Shows a saved row context menu if the row for the context menu is still - * visible. Does nothing if a context menu has not been saved. - * - * @param savedContextMenu - */ - public void showSavedContextMenu(ContextMenuDetails savedContextMenu) { - if (isEnabled() && savedContextMenu != null) { - Iterator iterator = getWidget().scrollBody.iterator(); - while (iterator.hasNext()) { - Widget w = iterator.next(); - VScrollTableRow row = (VScrollTableRow) w; - if (row.getKey().equals(savedContextMenu.rowKey)) { - row.showContextMenu(savedContextMenu.left, - savedContextMenu.top); - } - } - } - } - - @Override - public TooltipInfo getTooltipInfo(Element element) { - - TooltipInfo info = null; - - if (element != getWidget().getElement()) { - Object node = WidgetUtil.findWidget(element, VScrollTableRow.class); - - if (node != null) { - VScrollTableRow row = (VScrollTableRow) node; - info = row.getTooltip(element); - } - } - - if (info == null) { - info = super.getTooltipInfo(element); - } - - return info; - } - - @Override - public boolean hasTooltip() { - /* - * Tooltips for individual rows and cells are not processed until - * updateFromUIDL, so we can't be sure that there are no tooltips during - * onStateChange when this method is used. - */ - return true; - } - - @Override - public void onConnectorHierarchyChange( - ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) { - // TODO Move code from updateFromUIDL to this method - } - - @Override - protected void updateComponentSize(String newWidth, String newHeight) { - super.updateComponentSize(newWidth, newHeight); - - if ("".equals(newWidth)) { - getWidget().updateWidth(); - } - if ("".equals(newHeight)) { - getWidget().updateHeight(); - } - } - - @Override - public List getChildComponents() { - if (childComponents == null) { - return Collections.emptyList(); - } - - return childComponents; - } - - @Override - public void setChildComponents(List childComponents) { - this.childComponents = childComponents; - } - - @Override - public HandlerRegistration addConnectorHierarchyChangeHandler( - ConnectorHierarchyChangeHandler handler) { - return ensureHandlerManager().addHandler( - ConnectorHierarchyChangeEvent.TYPE, handler); - } - - @Override - public void setChildMeasurementHint(ChildMeasurementHint hint) { - getWidget().setChildMeasurementHint(hint); - } - - @Override - public ChildMeasurementHint getChildMeasurementHint() { - return getWidget().getChildMeasurementHint(); - } - -} diff --git a/client/src/com/vaadin/client/ui/tabsheet/TabsheetBaseConnector.java b/client/src/com/vaadin/client/ui/tabsheet/TabsheetBaseConnector.java deleted file mode 100644 index 9de415e74e..0000000000 --- a/client/src/com/vaadin/client/ui/tabsheet/TabsheetBaseConnector.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.tabsheet; - -import java.util.ArrayList; -import java.util.Iterator; - -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractComponentContainerConnector; -import com.vaadin.client.ui.VTabsheetBase; -import com.vaadin.shared.ui.tabsheet.TabState; -import com.vaadin.shared.ui.tabsheet.TabsheetState; - -public abstract class TabsheetBaseConnector extends - AbstractComponentContainerConnector { - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.AbstractConnector#init() - */ - @Override - protected void init() { - super.init(); - - getWidget().setClient(getConnection()); - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.client.ui.AbstractComponentConnector#onStateChanged(com.vaadin - * .client.communication.StateChangeEvent) - */ - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - // Update member references - getWidget().setEnabled(isEnabled()); - - // Widgets in the TabSheet before update - ArrayList oldWidgets = new ArrayList(); - for (Iterator iterator = getWidget().getWidgetIterator(); iterator - .hasNext();) { - oldWidgets.add(iterator.next()); - } - - // Clear previous values - getWidget().clearTabKeys(); - - int index = 0; - for (TabState tab : getState().tabs) { - final String key = tab.key; - final boolean selected = key.equals(getState().selected); - - getWidget().addTabKey(key, !tab.enabled && tab.visible); - - if (selected) { - getWidget().setActiveTabIndex(index); - } - getWidget().renderTab(tab, index); - if (selected) { - getWidget().selectTab(index); - } - index++; - } - - int tabCount = getWidget().getTabCount(); - while (tabCount-- > index) { - getWidget().removeTab(index); - } - - for (int i = 0; i < getWidget().getTabCount(); i++) { - ComponentConnector p = getWidget().getTab(i); - // null for PlaceHolder widgets - if (p != null) { - oldWidgets.remove(p.getWidget()); - } - } - - // Detach any old tab widget, should be max 1 - for (Iterator iterator = oldWidgets.iterator(); iterator - .hasNext();) { - Widget oldWidget = iterator.next(); - if (oldWidget.isAttached()) { - oldWidget.removeFromParent(); - } - } - } - - @Override - public VTabsheetBase getWidget() { - return (VTabsheetBase) super.getWidget(); - } - - @Override - public TabsheetState getState() { - return (TabsheetState) super.getState(); - } - -} diff --git a/client/src/com/vaadin/client/ui/tabsheet/TabsheetConnector.java b/client/src/com/vaadin/client/ui/tabsheet/TabsheetConnector.java deleted file mode 100644 index 1b043c8a51..0000000000 --- a/client/src/com/vaadin/client/ui/tabsheet/TabsheetConnector.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.tabsheet; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Overflow; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorHierarchyChangeEvent; -import com.vaadin.client.TooltipInfo; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.SimpleManagedLayout; -import com.vaadin.client.ui.VTabsheet; -import com.vaadin.client.ui.layout.MayScrollChildren; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.tabsheet.TabsheetClientRpc; -import com.vaadin.ui.TabSheet; - -@Connect(TabSheet.class) -public class TabsheetConnector extends TabsheetBaseConnector implements - SimpleManagedLayout, MayScrollChildren { - - public TabsheetConnector() { - registerRpc(TabsheetClientRpc.class, new TabsheetClientRpc() { - @Override - public void revertToSharedStateSelection() { - for (int i = 0; i < getState().tabs.size(); ++i) { - final String key = getState().tabs.get(i).key; - final boolean selected = key.equals(getState().selected); - if (selected) { - getWidget().waitingForResponse = false; - getWidget().setActiveTabIndex(i); - getWidget().selectTab(i); - break; - } - } - renderContent(); - } - }); - } - - @Override - protected void init() { - super.init(); - getWidget().setConnector(this); - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.client.ui.AbstractComponentConnector#onStateChanged(com.vaadin - * .client.communication.StateChangeEvent) - */ - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - getWidget().handleStyleNames(getState()); - - if (getState().tabsVisible) { - getWidget().showTabs(); - } else { - getWidget().hideTabs(); - } - - // tabs; push or not - if (!isUndefinedWidth()) { - getWidget().tabs.getStyle().setOverflow(Overflow.HIDDEN); - } else { - getWidget().showAllTabs(); - getWidget().tabs.getStyle().clearWidth(); - getWidget().tabs.getStyle().setOverflow(Overflow.VISIBLE); - getWidget().updateDynamicWidth(); - } - - if (!isUndefinedHeight()) { - // Must update height after the styles have been set - getWidget().updateContentNodeHeight(); - getWidget().updateOpenTabSize(); - } - - getWidget().iLayout(); - } - - @Override - public VTabsheet getWidget() { - return (VTabsheet) super.getWidget(); - } - - @Override - public void updateCaption(ComponentConnector component) { - /* Tabsheet does not render its children's captions */ - } - - @Override - public void layout() { - VTabsheet tabsheet = getWidget(); - - tabsheet.updateContentNodeHeight(); - - if (isUndefinedWidth()) { - tabsheet.contentNode.getStyle().setProperty("width", ""); - } else { - int contentWidth = tabsheet.getOffsetWidth() - - tabsheet.getContentAreaBorderWidth(); - if (contentWidth < 0) { - contentWidth = 0; - } - tabsheet.contentNode.getStyle().setProperty("width", - contentWidth + "px"); - } - - tabsheet.updateOpenTabSize(); - if (isUndefinedWidth()) { - tabsheet.updateDynamicWidth(); - } - - tabsheet.iLayout(); - - } - - @Override - public TooltipInfo getTooltipInfo(Element element) { - - TooltipInfo info = null; - - // Find a tooltip for the tab, if the element is a tab - if (element != getWidget().getElement()) { - Object node = WidgetUtil.findWidget(element, - VTabsheet.TabCaption.class); - - if (node != null) { - VTabsheet.TabCaption caption = (VTabsheet.TabCaption) node; - info = caption.getTooltipInfo(); - } - } - - // If not tab tooltip was found, use the default - if (info == null) { - info = super.getTooltipInfo(element); - } - - return info; - } - - @Override - public boolean hasTooltip() { - /* - * Tab tooltips are not processed until updateFromUIDL, so we can't be - * sure that there are no tooltips during onStateChange when this method - * is used. - */ - return true; - } - - @Override - public void onConnectorHierarchyChange( - ConnectorHierarchyChangeEvent connector) { - renderContent(); - } - - /** - * (Re-)render the content of the active tab. - */ - protected void renderContent() { - ComponentConnector contentConnector = null; - if (!getChildComponents().isEmpty()) { - contentConnector = getChildComponents().get(0); - } - - if (null != contentConnector) { - getWidget().renderContent(contentConnector.getWidget()); - } else { - getWidget().renderContent(null); - } - } - -} diff --git a/client/src/com/vaadin/client/ui/textarea/TextAreaConnector.java b/client/src/com/vaadin/client/ui/textarea/TextAreaConnector.java deleted file mode 100644 index 3bc0a86df4..0000000000 --- a/client/src/com/vaadin/client/ui/textarea/TextAreaConnector.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.textarea; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.dom.client.MouseUpEvent; -import com.google.gwt.event.dom.client.MouseUpHandler; -import com.vaadin.client.WidgetUtil.CssSize; -import com.vaadin.client.ui.VTextArea; -import com.vaadin.client.ui.textfield.TextFieldConnector; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.textarea.TextAreaState; -import com.vaadin.ui.TextArea; - -@Connect(TextArea.class) -public class TextAreaConnector extends TextFieldConnector { - - @Override - public TextAreaState getState() { - return (TextAreaState) super.getState(); - } - - @Override - public VTextArea getWidget() { - return (VTextArea) super.getWidget(); - } - - @Override - protected void init() { - super.init(); - - getWidget().addMouseUpHandler(new ResizeMouseUpHandler()); - } - - /* - * Workaround to handle the resize on the mouse up. - */ - private class ResizeMouseUpHandler implements MouseUpHandler { - - @Override - public void onMouseUp(MouseUpEvent event) { - Element element = getWidget().getElement(); - - updateSize(element.getStyle().getHeight(), getState().height, - "height"); - updateSize(element.getStyle().getWidth(), getState().width, "width"); - } - - /* - * Update the specified size on the server. - */ - private void updateSize(String sizeText, String stateSizeText, - String sizeType) { - - CssSize stateSize = CssSize.fromString(stateSizeText); - CssSize newSize = CssSize.fromString(sizeText); - - if (stateSize == null && newSize == null) { - return; - - } else if (newSize == null) { - sizeText = ""; - - // Else, if the current stateSize is null, just go ahead and set - // the newSize, so no check on stateSize is needed. - - } else if (stateSize != null && stateSize.equals(newSize)) { - return; - } - - getConnection().updateVariable(getConnectorId(), sizeType, - sizeText, false); - } - - } - -} diff --git a/client/src/com/vaadin/client/ui/textfield/TextFieldConnector.java b/client/src/com/vaadin/client/ui/textfield/TextFieldConnector.java deleted file mode 100644 index 0d85e98ee3..0000000000 --- a/client/src/com/vaadin/client/ui/textfield/TextFieldConnector.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.textfield; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.Event; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.Paintable; -import com.vaadin.client.UIDL; -import com.vaadin.client.Util; -import com.vaadin.client.ui.AbstractFieldConnector; -import com.vaadin.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; -import com.vaadin.client.ui.VTextField; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.Connect.LoadStyle; -import com.vaadin.shared.ui.textfield.AbstractTextFieldState; -import com.vaadin.shared.ui.textfield.TextFieldConstants; -import com.vaadin.ui.TextField; - -@Connect(value = TextField.class, loadStyle = LoadStyle.EAGER) -public class TextFieldConnector extends AbstractFieldConnector implements - Paintable, BeforeShortcutActionListener { - - @Override - public AbstractTextFieldState getState() { - return (AbstractTextFieldState) super.getState(); - } - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - // Save details - getWidget().client = client; - getWidget().paintableId = uidl.getId(); - - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().setReadOnly(isReadOnly()); - - getWidget().setInputPrompt(getState().inputPrompt); - getWidget().setMaxLength(getState().maxLength); - getWidget().setImmediate(getState().immediate); - - getWidget().listenTextChangeEvents = hasEventListener("ie"); - if (getWidget().listenTextChangeEvents) { - getWidget().textChangeEventMode = uidl - .getStringAttribute(TextFieldConstants.ATTR_TEXTCHANGE_EVENTMODE); - if (getWidget().textChangeEventMode - .equals(TextFieldConstants.TEXTCHANGE_MODE_EAGER)) { - getWidget().textChangeEventTimeout = 1; - } else { - getWidget().textChangeEventTimeout = uidl - .getIntAttribute(TextFieldConstants.ATTR_TEXTCHANGE_TIMEOUT); - if (getWidget().textChangeEventTimeout < 1) { - // Sanitize and allow lazy/timeout with timeout set to 0 to - // work as eager - getWidget().textChangeEventTimeout = 1; - } - } - getWidget().sinkEvents(VTextField.TEXTCHANGE_EVENTS); - getWidget().attachCutEventListener(getWidget().getElement()); - } - getWidget().setColumns(getState().columns); - - String text = getState().text; - if (text == null) { - text = ""; - } - /* - * We skip the text content update if field has been repainted, but text - * has not been changed (#6588). Additional sanity check verifies there - * is no change in the queue (in which case we count more on the server - * side value). is updated only when it looses focus, so we - * force updating if not focused. Lost focus issue appeared in (#15144) - */ - if (!(Util.getFocusedElement() == getWidget().getElement()) - || !uidl.getBooleanAttribute(TextFieldConstants.ATTR_NO_VALUE_CHANGE_BETWEEN_PAINTS) - || getWidget().valueBeforeEdit == null - || !text.equals(getWidget().valueBeforeEdit)) { - getWidget().updateFieldContent(text); - } - - if (uidl.hasAttribute("selpos")) { - final int pos = uidl.getIntAttribute("selpos"); - final int length = uidl.getIntAttribute("sellen"); - /* - * Gecko defers setting the text so we need to defer the selection. - */ - Scheduler.get().scheduleDeferred(new Command() { - @Override - public void execute() { - getWidget().setSelectionRange(pos, length); - } - }); - } - } - - @Override - public VTextField getWidget() { - return (VTextField) super.getWidget(); - } - - @Override - public void onBeforeShortcutAction(Event e) { - flush(); - } - - @Override - public void flush() { - getWidget().valueChange(false); - } - -} diff --git a/client/src/com/vaadin/client/ui/tree/TreeConnector.java b/client/src/com/vaadin/client/ui/tree/TreeConnector.java deleted file mode 100644 index f49f44e802..0000000000 --- a/client/src/com/vaadin/client/ui/tree/TreeConnector.java +++ /dev/null @@ -1,400 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.tree; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -import com.google.gwt.aria.client.Roles; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.EventTarget; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.Paintable; -import com.vaadin.client.TooltipInfo; -import com.vaadin.client.UIDL; -import com.vaadin.client.VConsole; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractComponentConnector; -import com.vaadin.client.ui.VTree; -import com.vaadin.client.ui.VTree.TreeNode; -import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.MultiSelectMode; -import com.vaadin.shared.ui.tree.TreeConstants; -import com.vaadin.shared.ui.tree.TreeServerRpc; -import com.vaadin.shared.ui.tree.TreeState; -import com.vaadin.ui.Tree; - -@Connect(Tree.class) -public class TreeConnector extends AbstractComponentConnector implements - Paintable { - - protected final Map tooltipMap = new HashMap(); - - @Override - protected void init() { - getWidget().connector = this; - } - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().rendering = true; - - getWidget().client = client; - - if (uidl.hasAttribute("partialUpdate")) { - handleUpdate(uidl); - - // IE8 needs a hack to measure the tree again after update - WidgetUtil.forceIE8Redraw(getWidget().getElement()); - - getWidget().rendering = false; - return; - } - - getWidget().paintableId = uidl.getId(); - - getWidget().immediate = getState().immediate; - - getWidget().disabled = !isEnabled(); - getWidget().readonly = isReadOnly(); - - getWidget().dragMode = uidl.hasAttribute("dragMode") ? uidl - .getIntAttribute("dragMode") : 0; - - getWidget().isNullSelectionAllowed = uidl - .getBooleanAttribute("nullselect"); - getWidget().isHtmlContentAllowed = uidl - .getBooleanAttribute(TreeConstants.ATTRIBUTE_HTML_ALLOWED); - - if (uidl.hasAttribute("alb")) { - getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb"); - } - - getWidget().body.clear(); - // clear out any references to nodes that no longer are attached - getWidget().clearNodeToKeyMap(); - tooltipMap.clear(); - - TreeNode childTree = null; - UIDL childUidl = null; - for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { - childUidl = (UIDL) i.next(); - if ("actions".equals(childUidl.getTag())) { - updateActionMap(childUidl); - continue; - } else if ("-ac".equals(childUidl.getTag())) { - getWidget().updateDropHandler(childUidl); - continue; - } - childTree = getWidget().new TreeNode(); - getConnection().getVTooltip().connectHandlersToWidget(childTree); - updateNodeFromUIDL(childTree, childUidl, 1); - getWidget().body.add(childTree); - childTree.addStyleDependentName("root"); - childTree.childNodeContainer.addStyleDependentName("root"); - } - if (childTree != null && childUidl != null) { - boolean leaf = !childUidl.getTag().equals("node"); - childTree.addStyleDependentName(leaf ? "leaf-last" : "last"); - childTree.childNodeContainer.addStyleDependentName("last"); - } - final String selectMode = uidl.getStringAttribute("selectmode"); - getWidget().selectable = !"none".equals(selectMode); - getWidget().isMultiselect = "multi".equals(selectMode); - - if (getWidget().isMultiselect) { - Roles.getTreeRole().setAriaMultiselectableProperty( - getWidget().getElement(), true); - - if (BrowserInfo.get().isTouchDevice()) { - // Always use the simple mode for touch devices that do not have - // shift/ctrl keys (#8595) - getWidget().multiSelectMode = MultiSelectMode.SIMPLE; - } else { - getWidget().multiSelectMode = MultiSelectMode.valueOf(uidl - .getStringAttribute("multiselectmode")); - } - } else { - Roles.getTreeRole().setAriaMultiselectableProperty( - getWidget().getElement(), false); - } - - getWidget().selectedIds = uidl.getStringArrayVariableAsSet("selected"); - - // Update lastSelection and focusedNode to point to *actual* nodes again - // after the old ones have been cleared from the body. This fixes focus - // and keyboard navigation issues as described in #7057 and other - // tickets. - if (getWidget().lastSelection != null) { - getWidget().lastSelection = getWidget().getNodeByKey( - getWidget().lastSelection.key); - } - - if (getWidget().focusedNode != null) { - - Set selectedIds = getWidget().selectedIds; - - // If the focused node is not between the selected nodes, we need to - // refresh the focused node to prevent an undesired scroll. #12618. - if (!selectedIds.isEmpty() - && !selectedIds.contains(getWidget().focusedNode.key)) { - String keySelectedId = selectedIds.iterator().next(); - - TreeNode nodeToSelect = getWidget().getNodeByKey(keySelectedId); - - getWidget().setFocusedNode(nodeToSelect); - } else { - getWidget().setFocusedNode( - getWidget().getNodeByKey(getWidget().focusedNode.key)); - } - } - - if (getWidget().lastSelection == null - && getWidget().focusedNode == null - && !getWidget().selectedIds.isEmpty()) { - getWidget().setFocusedNode( - getWidget().getNodeByKey( - getWidget().selectedIds.iterator().next())); - getWidget().focusedNode.setFocused(false); - } - - // IE8 needs a hack to measure the tree again after update - WidgetUtil.forceIE8Redraw(getWidget().getElement()); - - getWidget().rendering = false; - - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - // VTree does not implement Focusable - getWidget().setTabIndex(getState().tabIndex); - } - - @Override - public VTree getWidget() { - return (VTree) super.getWidget(); - } - - private void handleUpdate(UIDL uidl) { - final TreeNode rootNode = getWidget().getNodeByKey( - uidl.getStringAttribute("rootKey")); - if (rootNode != null) { - if (!rootNode.getState()) { - // expanding node happened server side - rootNode.setState(true, false); - } - String levelPropertyString = Roles.getTreeitemRole() - .getAriaLevelProperty(rootNode.getElement()); - int levelProperty; - try { - levelProperty = Integer.valueOf(levelPropertyString); - } catch (NumberFormatException e) { - levelProperty = 1; - VConsole.error(e); - } - - renderChildNodes(rootNode, (Iterator) uidl.getChildIterator(), - levelProperty + 1); - } - } - - /** - * Registers action for the root and also for individual nodes - * - * @param uidl - */ - private void updateActionMap(UIDL uidl) { - final Iterator it = uidl.getChildIterator(); - while (it.hasNext()) { - final UIDL action = (UIDL) it.next(); - final String key = action.getStringAttribute("key"); - final String caption = action - .getStringAttribute(TreeConstants.ATTRIBUTE_ACTION_CAPTION); - String iconUrl = null; - if (action.hasAttribute(TreeConstants.ATTRIBUTE_ACTION_ICON)) { - iconUrl = getConnection() - .translateVaadinUri( - action.getStringAttribute(TreeConstants.ATTRIBUTE_ACTION_ICON)); - } - getWidget().registerAction(key, caption, iconUrl); - } - - } - - public void updateNodeFromUIDL(TreeNode treeNode, UIDL uidl, int level) { - Roles.getTreeitemRole().setAriaLevelProperty(treeNode.getElement(), - level); - - String nodeKey = uidl.getStringAttribute("key"); - String caption = uidl - .getStringAttribute(TreeConstants.ATTRIBUTE_NODE_CAPTION); - if (getWidget().isHtmlContentAllowed) { - treeNode.setHtml(caption); - } else { - treeNode.setText(caption); - } - treeNode.key = nodeKey; - - getWidget().registerNode(treeNode); - - if (uidl.hasAttribute("al")) { - treeNode.actionKeys = uidl.getStringArrayAttribute("al"); - } - - if (uidl.getTag().equals("node")) { - if (uidl.getChildCount() == 0) { - treeNode.childNodeContainer.setVisible(false); - } else { - renderChildNodes(treeNode, (Iterator) uidl.getChildIterator(), - level + 1); - treeNode.childrenLoaded = true; - } - } else { - treeNode.addStyleName(TreeNode.CLASSNAME + "-leaf"); - } - if (uidl.hasAttribute(TreeConstants.ATTRIBUTE_NODE_STYLE)) { - treeNode.setNodeStyleName(uidl - .getStringAttribute(TreeConstants.ATTRIBUTE_NODE_STYLE)); - } - - String description = uidl.getStringAttribute("descr"); - if (description != null) { - tooltipMap.put(treeNode, new TooltipInfo(description, null, - treeNode)); - } - - if (uidl.getBooleanAttribute("expanded") && !treeNode.getState()) { - treeNode.setState(true, false); - } - - if (uidl.getBooleanAttribute("selected")) { - treeNode.setSelected(true); - // ensure that identifier is in selectedIds array (this may be a - // partial update) - getWidget().selectedIds.add(nodeKey); - } - - String iconUrl = uidl - .getStringAttribute(TreeConstants.ATTRIBUTE_NODE_ICON); - String iconAltText = uidl - .getStringAttribute(TreeConstants.ATTRIBUTE_NODE_ICON_ALT); - treeNode.setIcon(iconUrl, iconAltText); - } - - void renderChildNodes(TreeNode containerNode, Iterator i, int level) { - containerNode.childNodeContainer.clear(); - containerNode.childNodeContainer.setVisible(true); - while (i.hasNext()) { - final UIDL childUidl = i.next(); - // actions are in bit weird place, don't mix them with children, - // but current node's actions - if ("actions".equals(childUidl.getTag())) { - updateActionMap(childUidl); - continue; - } - final TreeNode childTree = getWidget().new TreeNode(); - getConnection().getVTooltip().connectHandlersToWidget(childTree); - updateNodeFromUIDL(childTree, childUidl, level); - containerNode.childNodeContainer.add(childTree); - if (!i.hasNext()) { - childTree - .addStyleDependentName(childTree.isLeaf() ? "leaf-last" - : "last"); - childTree.childNodeContainer.addStyleDependentName("last"); - } - } - containerNode.childrenLoaded = true; - } - - @Override - public boolean isReadOnly() { - return super.isReadOnly() || getState().propertyReadOnly; - } - - @Override - public TreeState getState() { - return (TreeState) super.getState(); - } - - @Override - public TooltipInfo getTooltipInfo(Element element) { - - TooltipInfo info = null; - - // Try to find a tooltip for a node - if (element != getWidget().getElement()) { - Object node = WidgetUtil.findWidget(element, TreeNode.class); - - if (node != null) { - TreeNode tnode = (TreeNode) node; - if (tnode.isCaptionElement(element)) { - info = tooltipMap.get(tnode); - } - } - } - - // If no tooltip found for the node or if the target was not a node, use - // the default tooltip - if (info == null) { - info = super.getTooltipInfo(element); - } - - return info; - } - - @Override - public boolean hasTooltip() { - /* - * Item tooltips are not processed until updateFromUIDL, so we can't be - * sure that there are no tooltips during onStateChange when this method - * is used. - */ - return true; - } - - @Override - protected void sendContextClickEvent(MouseEventDetails details, - EventTarget eventTarget) { - if (!Element.is(eventTarget)) { - return; - } - - Element e = Element.as(eventTarget); - String key = null; - - if (getWidget().body.getElement().isOrHasChild(e)) { - TreeNode t = WidgetUtil.findWidget(e, TreeNode.class); - if (t != null) { - key = t.key; - } - } - - getRpcProxy(TreeServerRpc.class).contextClick(key, details); - - WidgetUtil.clearTextSelection(); - } -} diff --git a/client/src/com/vaadin/client/ui/treetable/TreeTableConnector.java b/client/src/com/vaadin/client/ui/treetable/TreeTableConnector.java deleted file mode 100644 index 0e0c190c11..0000000000 --- a/client/src/com/vaadin/client/ui/treetable/TreeTableConnector.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.treetable; - -import com.google.gwt.dom.client.Element; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.TooltipInfo; -import com.vaadin.client.UIDL; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.ui.FocusableScrollPanel; -import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; -import com.vaadin.client.ui.VTreeTable; -import com.vaadin.client.ui.VTreeTable.PendingNavigationEvent; -import com.vaadin.client.ui.VTreeTable.VTreeTableScrollBody.VTreeTableRow; -import com.vaadin.client.ui.table.TableConnector; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.treetable.TreeTableConstants; -import com.vaadin.shared.ui.treetable.TreeTableState; -import com.vaadin.ui.TreeTable; - -@Connect(TreeTable.class) -public class TreeTableConnector extends TableConnector { - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - FocusableScrollPanel widget = null; - int scrollPosition = 0; - if (getWidget().collapseRequest) { - widget = (FocusableScrollPanel) getWidget().getWidget(1); - scrollPosition = widget.getScrollPosition(); - } - getWidget().animationsEnabled = uidl.getBooleanAttribute("animate"); - getWidget().colIndexOfHierarchy = uidl - .hasAttribute(TreeTableConstants.ATTRIBUTE_HIERARCHY_COLUMN_INDEX) ? uidl - .getIntAttribute(TreeTableConstants.ATTRIBUTE_HIERARCHY_COLUMN_INDEX) - : 0; - int oldTotalRows = getWidget().getTotalRows(); - - super.updateFromUIDL(uidl, client); - // super.updateFromUIDL set rendering to false, even though we continue - // rendering here. Set it back to true. - getWidget().rendering = true; - - if (getWidget().collapseRequest) { - if (getWidget().collapsedRowKey != null - && getWidget().scrollBody != null) { - VScrollTableRow row = getWidget().getRenderedRowByKey( - getWidget().collapsedRowKey); - if (row != null) { - getWidget().setRowFocus(row); - getWidget().focus(); - } - } - - int scrollPosition2 = widget.getScrollPosition(); - if (scrollPosition != scrollPosition2) { - widget.setScrollPosition(scrollPosition); - } - - // check which rows are needed from the server and initiate a - // deferred fetch - getWidget().onScroll(null); - } - // Recalculate table size if collapse request, or if page length is zero - // (not sent by server) and row count changes (#7908). - if (getWidget().collapseRequest - || (!uidl.hasAttribute("pagelength") && getWidget() - .getTotalRows() != oldTotalRows)) { - /* - * Ensure that possibly removed/added scrollbars are considered. - * Triggers row calculations, removes cached rows etc. Basically - * cleans up state. Be careful if touching this, you will break - * pageLength=0 if you remove this. - */ - getWidget().triggerLazyColumnAdjustment(true); - - getWidget().collapseRequest = false; - } - if (uidl.hasAttribute("focusedRow")) { - String key = uidl.getStringAttribute("focusedRow"); - getWidget().setRowFocus(getWidget().getRenderedRowByKey(key)); - getWidget().focusParentResponsePending = false; - } else if (uidl.hasAttribute("clearFocusPending")) { - // Special case to detect a response to a focusParent request that - // does not return any focusedRow because the selected node has no - // parent - getWidget().focusParentResponsePending = false; - } - - while (!getWidget().collapseRequest - && !getWidget().focusParentResponsePending - && !getWidget().pendingNavigationEvents.isEmpty()) { - // Keep replaying any queued events as long as we don't have any - // potential content changes pending - PendingNavigationEvent event = getWidget().pendingNavigationEvents - .removeFirst(); - getWidget() - .handleNavigation(event.keycode, event.ctrl, event.shift); - } - getWidget().rendering = false; - } - - @Override - public VTreeTable getWidget() { - return (VTreeTable) super.getWidget(); - } - - @Override - public TreeTableState getState() { - return (TreeTableState) super.getState(); - } - - @Override - public TooltipInfo getTooltipInfo(Element element) { - - TooltipInfo info = null; - - if (element != getWidget().getElement()) { - Object node = WidgetUtil.findWidget(element, VTreeTableRow.class); - - if (node != null) { - VTreeTableRow row = (VTreeTableRow) node; - info = row.getTooltip(element); - } - } - - if (info == null) { - info = super.getTooltipInfo(element); - } - - return info; - } - - @Override - protected VScrollTableRow getScrollTableRow(Element e) { - return WidgetUtil.findWidget(e, VTreeTableRow.class); - } -} diff --git a/client/src/com/vaadin/client/ui/twincolselect/TwinColSelectConnector.java b/client/src/com/vaadin/client/ui/twincolselect/TwinColSelectConnector.java deleted file mode 100644 index 0994ac15c3..0000000000 --- a/client/src/com/vaadin/client/ui/twincolselect/TwinColSelectConnector.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.twincolselect; - -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.DirectionalManagedLayout; -import com.vaadin.client.UIDL; -import com.vaadin.client.ui.VTwinColSelect; -import com.vaadin.client.ui.optiongroup.OptionGroupBaseConnector; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.twincolselect.TwinColSelectState; -import com.vaadin.ui.TwinColSelect; - -@Connect(TwinColSelect.class) -public class TwinColSelectConnector extends OptionGroupBaseConnector implements - DirectionalManagedLayout { - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - // Captions are updated before super call to ensure the widths are set - // correctly - if (isRealUpdate(uidl)) { - getWidget().updateCaptions(uidl); - getLayoutManager().setNeedsHorizontalLayout(this); - } - - super.updateFromUIDL(uidl, client); - } - - @Override - protected void init() { - super.init(); - getLayoutManager().registerDependency(this, - getWidget().captionWrapper.getElement()); - } - - @Override - public void onUnregister() { - getLayoutManager().unregisterDependency(this, - getWidget().captionWrapper.getElement()); - } - - @Override - public VTwinColSelect getWidget() { - return (VTwinColSelect) super.getWidget(); - } - - @Override - public TwinColSelectState getState() { - return (TwinColSelectState) super.getState(); - } - - @Override - public void layoutVertically() { - if (isUndefinedHeight()) { - getWidget().clearInternalHeights(); - } else { - getWidget().setInternalHeights(); - } - } - - @Override - public void layoutHorizontally() { - if (isUndefinedWidth()) { - getWidget().clearInternalWidths(); - } else { - getWidget().setInternalWidths(); - } - } -} diff --git a/client/src/com/vaadin/client/ui/ui/UIConnector.java b/client/src/com/vaadin/client/ui/ui/UIConnector.java deleted file mode 100644 index 9ffb9cfba9..0000000000 --- a/client/src/com/vaadin/client/ui/ui/UIConnector.java +++ /dev/null @@ -1,1126 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.ui; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.logging.Logger; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.HeadElement; -import com.google.gwt.dom.client.LinkElement; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.dom.client.StyleElement; -import com.google.gwt.dom.client.StyleInjector; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.ScrollEvent; -import com.google.gwt.event.dom.client.ScrollHandler; -import com.google.gwt.event.logical.shared.ResizeEvent; -import com.google.gwt.event.logical.shared.ResizeHandler; -import com.google.gwt.http.client.URL; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.History; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.Window.Location; -import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.Widget; -import com.google.web.bindery.event.shared.HandlerRegistration; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorHierarchyChangeEvent; -import com.vaadin.client.Focusable; -import com.vaadin.client.Paintable; -import com.vaadin.client.ResourceLoader; -import com.vaadin.client.ResourceLoader.ResourceLoadEvent; -import com.vaadin.client.ResourceLoader.ResourceLoadListener; -import com.vaadin.client.ServerConnector; -import com.vaadin.client.UIDL; -import com.vaadin.client.Util; -import com.vaadin.client.VConsole; -import com.vaadin.client.ValueMap; -import com.vaadin.client.annotations.OnStateChange; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; -import com.vaadin.client.ui.AbstractConnector; -import com.vaadin.client.ui.AbstractSingleComponentContainerConnector; -import com.vaadin.client.ui.ClickEventHandler; -import com.vaadin.client.ui.ShortcutActionHandler; -import com.vaadin.client.ui.VNotification; -import com.vaadin.client.ui.VOverlay; -import com.vaadin.client.ui.VUI; -import com.vaadin.client.ui.VWindow; -import com.vaadin.client.ui.layout.MayScrollChildren; -import com.vaadin.client.ui.window.WindowConnector; -import com.vaadin.server.Page.Styles; -import com.vaadin.shared.ApplicationConstants; -import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.Version; -import com.vaadin.shared.communication.MethodInvocation; -import com.vaadin.shared.ui.ComponentStateUtil; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.Connect.LoadStyle; -import com.vaadin.shared.ui.ui.DebugWindowClientRpc; -import com.vaadin.shared.ui.ui.DebugWindowServerRpc; -import com.vaadin.shared.ui.ui.PageClientRpc; -import com.vaadin.shared.ui.ui.PageState; -import com.vaadin.shared.ui.ui.ScrollClientRpc; -import com.vaadin.shared.ui.ui.UIClientRpc; -import com.vaadin.shared.ui.ui.UIConstants; -import com.vaadin.shared.ui.ui.UIServerRpc; -import com.vaadin.shared.ui.ui.UIState; -import com.vaadin.shared.util.SharedUtil; -import com.vaadin.ui.UI; - -@Connect(value = UI.class, loadStyle = LoadStyle.EAGER) -public class UIConnector extends AbstractSingleComponentContainerConnector - implements Paintable, MayScrollChildren { - - private HandlerRegistration childStateChangeHandlerRegistration; - - private String activeTheme = null; - - private final StateChangeHandler childStateChangeHandler = new StateChangeHandler() { - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - // TODO Should use a more specific handler that only reacts to - // size changes - onChildSizeChange(); - } - }; - - @Override - protected void init() { - super.init(); - registerRpc(PageClientRpc.class, new PageClientRpc() { - - @Override - public void reload() { - Window.Location.reload(); - - } - }); - registerRpc(ScrollClientRpc.class, new ScrollClientRpc() { - @Override - public void setScrollTop(int scrollTop) { - getWidget().getElement().setScrollTop(scrollTop); - } - - @Override - public void setScrollLeft(int scrollLeft) { - getWidget().getElement().setScrollLeft(scrollLeft); - } - }); - registerRpc(UIClientRpc.class, new UIClientRpc() { - @Override - public void uiClosed(final boolean sessionExpired) { - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - @Override - public void execute() { - // Only notify user if we're still running and not eg. - // navigating away (#12298) - if (getConnection().isApplicationRunning()) { - if (sessionExpired) { - getConnection().showSessionExpiredError(null); - } else { - getState().enabled = false; - updateEnabledState(getState().enabled); - } - getConnection().setApplicationRunning(false); - } - } - }); - } - }); - registerRpc(DebugWindowClientRpc.class, new DebugWindowClientRpc() { - - @Override - public void reportLayoutProblems(String json) { - VConsole.printLayoutProblems(getValueMap(json), getConnection()); - } - - private native ValueMap getValueMap(String json) - /*-{ - return JSON.parse(json); - }-*/; - }); - - getWidget().addResizeHandler(new ResizeHandler() { - @Override - public void onResize(ResizeEvent event) { - getRpcProxy(UIServerRpc.class).resize(event.getHeight(), - event.getWidth(), Window.getClientWidth(), - Window.getClientHeight()); - if (getState().immediate || getPageState().hasResizeListeners) { - getConnection().getServerRpcQueue().flush(); - } - } - }); - getWidget().addScrollHandler(new ScrollHandler() { - private int lastSentScrollTop = Integer.MAX_VALUE; - private int lastSentScrollLeft = Integer.MAX_VALUE; - - @Override - public void onScroll(ScrollEvent event) { - Element element = getWidget().getElement(); - int newScrollTop = element.getScrollTop(); - int newScrollLeft = element.getScrollLeft(); - if (newScrollTop != lastSentScrollTop - || newScrollLeft != lastSentScrollLeft) { - lastSentScrollTop = newScrollTop; - lastSentScrollLeft = newScrollLeft; - getRpcProxy(UIServerRpc.class).scroll(newScrollTop, - newScrollLeft); - } - } - }); - } - - private native void open(String url, String name) - /*-{ - $wnd.open(url, name); - }-*/; - - @Override - public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) { - getWidget().id = getConnectorId(); - boolean firstPaint = getWidget().connection == null; - getWidget().connection = client; - - getWidget().immediate = getState().immediate; - getWidget().resizeLazy = uidl.hasAttribute(UIConstants.RESIZE_LAZY); - // this also implicitly removes old styles - String styles = ""; - styles += getWidget().getStylePrimaryName() + " "; - if (ComponentStateUtil.hasStyles(getState())) { - for (String style : getState().styles) { - styles += style + " "; - } - } - if (!client.getConfiguration().isStandalone()) { - styles += getWidget().getStylePrimaryName() + "-embedded"; - } - getWidget().setStyleName(styles.trim()); - - getWidget().makeScrollable(); - - clickEventHandler.handleEventHandlerRegistration(); - - // Process children - int childIndex = 0; - - // Open URL:s - boolean isClosed = false; // was this window closed? - while (childIndex < uidl.getChildCount() - && "open".equals(uidl.getChildUIDL(childIndex).getTag())) { - final UIDL open = uidl.getChildUIDL(childIndex); - final String url = client.translateVaadinUri(open - .getStringAttribute("src")); - final String target = open.getStringAttribute("name"); - if (target == null) { - // source will be opened to this browser window, but we may have - // to finish rendering this window in case this is a download - // (and window stays open). - Scheduler.get().scheduleDeferred(new Command() { - @Override - public void execute() { - VUI.goTo(url); - } - }); - } else if ("_self".equals(target)) { - // This window is closing (for sure). Only other opens are - // relevant in this change. See #3558, #2144 - isClosed = true; - VUI.goTo(url); - } else { - String options; - boolean alwaysAsPopup = true; - if (open.hasAttribute("popup")) { - alwaysAsPopup = open.getBooleanAttribute("popup"); - } - if (alwaysAsPopup) { - if (open.hasAttribute("border")) { - if (open.getStringAttribute("border").equals("minimal")) { - options = "menubar=yes,location=no,status=no"; - } else { - options = "menubar=no,location=no,status=no"; - } - - } else { - options = "resizable=yes,menubar=yes,toolbar=yes,directories=yes,location=yes,scrollbars=yes,status=yes"; - } - - if (open.hasAttribute("width")) { - int w = open.getIntAttribute("width"); - options += ",width=" + w; - } - if (open.hasAttribute("height")) { - int h = open.getIntAttribute("height"); - options += ",height=" + h; - } - - Window.open(url, target, options); - } else { - open(url, target); - } - } - childIndex++; - } - if (isClosed) { - // We're navigating away, so stop the application. - client.setApplicationRunning(false); - return; - } - - // Handle other UIDL children - UIDL childUidl; - while ((childUidl = uidl.getChildUIDL(childIndex++)) != null) { - String tag = childUidl.getTag().intern(); - if (tag == "actions") { - if (getWidget().actionHandler == null) { - getWidget().actionHandler = new ShortcutActionHandler( - getWidget().id, client); - } - getWidget().actionHandler.updateActionMap(childUidl); - } else if (tag == "notifications") { - for (final Iterator it = childUidl.getChildIterator(); it - .hasNext();) { - final UIDL notification = (UIDL) it.next(); - VNotification.showNotification(client, notification); - } - } else if (tag == "css-injections") { - injectCSS(childUidl); - } - } - - if (uidl.hasAttribute("focused")) { - // set focused component when render phase is finished - Scheduler.get().scheduleDeferred(new Command() { - @Override - public void execute() { - ComponentConnector connector = (ComponentConnector) uidl - .getPaintableAttribute("focused", getConnection()); - - if (connector == null) { - // Do not try to focus invisible components which not - // present in UIDL - return; - } - - final Widget toBeFocused = connector.getWidget(); - /* - * Two types of Widgets can be focused, either implementing - * GWT Focusable of a thinner Vaadin specific Focusable - * interface. - */ - if (toBeFocused instanceof com.google.gwt.user.client.ui.Focusable) { - final com.google.gwt.user.client.ui.Focusable toBeFocusedWidget = (com.google.gwt.user.client.ui.Focusable) toBeFocused; - toBeFocusedWidget.setFocus(true); - } else if (toBeFocused instanceof Focusable) { - ((Focusable) toBeFocused).focus(); - } else { - getLogger() - .severe("Server is trying to set focus to the widget of connector " - + Util.getConnectorString(connector) - + " but it is not focusable. The widget should implement either " - + com.google.gwt.user.client.ui.Focusable.class - .getName() - + " or " - + Focusable.class.getName()); - } - } - }); - } - - // Add window listeners on first paint, to prevent premature - // variablechanges - if (firstPaint) { - Window.addWindowClosingHandler(getWidget()); - Window.addResizeHandler(getWidget()); - } - - if (uidl.hasAttribute("scrollTo")) { - final ComponentConnector connector = (ComponentConnector) uidl - .getPaintableAttribute("scrollTo", getConnection()); - scrollIntoView(connector); - } - - if (uidl.hasAttribute(UIConstants.LOCATION_VARIABLE)) { - String location = uidl - .getStringAttribute(UIConstants.LOCATION_VARIABLE); - String newFragment; - - int fragmentIndex = location.indexOf('#'); - if (fragmentIndex >= 0) { - // Decode fragment to avoid double encoding (#10769) - newFragment = URL.decodePathSegment(location - .substring(fragmentIndex + 1)); - - if (newFragment.isEmpty() - && Location.getHref().indexOf('#') == -1) { - // Ensure there is a trailing # even though History and - // Location.getHash() treat null and "" the same way. - Location.assign(Location.getHref() + "#"); - } - } else { - // No fragment in server-side location, but can't completely - // remove the browser fragment since that would reload the page - newFragment = ""; - } - - getWidget().currentFragment = newFragment; - - if (!newFragment.equals(History.getToken())) { - History.newItem(newFragment, true); - } - } - - if (firstPaint) { - // Queue the initial window size to be sent with the following - // request. - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - @Override - public void execute() { - getWidget().sendClientResized(); - } - }); - } - } - - /** - * Reads CSS strings and resources injected by {@link Styles#inject} from - * the UIDL stream. - * - * @param uidl - * The uidl which contains "css-resource" and "css-string" tags - */ - private void injectCSS(UIDL uidl) { - - /* - * Search the UIDL stream for CSS resources and strings to be injected. - */ - for (Iterator it = uidl.getChildIterator(); it.hasNext();) { - UIDL cssInjectionsUidl = (UIDL) it.next(); - - // Check if we have resources to inject - if (cssInjectionsUidl.getTag().equals("css-resource")) { - String url = getWidget().connection - .translateVaadinUri(cssInjectionsUidl - .getStringAttribute("url")); - LinkElement link = LinkElement.as(DOM - .createElement(LinkElement.TAG)); - link.setRel("stylesheet"); - link.setHref(url); - link.setType("text/css"); - getHead().appendChild(link); - // Check if we have CSS string to inject - } else if (cssInjectionsUidl.getTag().equals("css-string")) { - for (Iterator it2 = cssInjectionsUidl.getChildIterator(); it2 - .hasNext();) { - StyleInjector.injectAtEnd((String) it2.next()); - StyleInjector.flush(); - } - } - } - } - - /** - * Internal helper to get the tag of the page - * - * @since 7.3 - * @return the head element - */ - private HeadElement getHead() { - return HeadElement.as(Document.get() - .getElementsByTagName(HeadElement.TAG).getItem(0)); - } - - /** - * Internal helper for removing any stylesheet with the given URL - * - * @since 7.3 - * @param url - * the url to match with existing stylesheets - */ - private void removeStylesheet(String url) { - NodeList linkTags = getHead().getElementsByTagName( - LinkElement.TAG); - for (int i = 0; i < linkTags.getLength(); i++) { - LinkElement link = LinkElement.as(linkTags.getItem(i)); - if (!"stylesheet".equals(link.getRel())) { - continue; - } - if (!"text/css".equals(link.getType())) { - continue; - } - if (url.equals(link.getHref())) { - getHead().removeChild(link); - } - } - } - - public void init(String rootPanelId, - ApplicationConnection applicationConnection) { - // Create a style tag for style injections so they don't end up in - // the theme tag in IE8-IE10 (we don't want to wipe them out if we - // change theme). - // StyleInjectorImplIE always injects to the last style tag on the page. - if (BrowserInfo.get().isIE() - && BrowserInfo.get().getBrowserMajorVersion() < 11) { - StyleElement style = Document.get().createStyleElement(); - style.setType("text/css"); - getHead().appendChild(style); - } - - Widget shortcutContextWidget = getWidget(); - if (applicationConnection.getConfiguration().isStandalone()) { - // Listen to body for standalone apps (#19392) - shortcutContextWidget = RootPanel.get(); // document body - } - - shortcutContextWidget.addDomHandler(new KeyDownHandler() { - @Override - public void onKeyDown(KeyDownEvent event) { - if (getWidget().actionHandler != null) { - getWidget().actionHandler.handleKeyboardEvent((Event) event - .getNativeEvent().cast()); - } - } - }, KeyDownEvent.getType()); - - DOM.sinkEvents(getWidget().getElement(), Event.ONSCROLL); - - RootPanel root = RootPanel.get(rootPanelId); - - // Remove the v-app-loading or any splash screen added inside the div by - // the user - root.getElement().setInnerHTML(""); - - // Activate the initial theme by only adding the class name. Not calling - // activateTheme here as it will also cause a full layout and updates to - // the overlay container which has not yet been created at this point - activeTheme = applicationConnection.getConfiguration().getThemeName(); - root.addStyleName(activeTheme); - - root.add(getWidget()); - - // Set default tab index before focus call. State change handler - // will update this later if needed. - getWidget().setTabIndex(1); - - if (applicationConnection.getConfiguration().isStandalone()) { - // set focus to iview element by default to listen possible keyboard - // shortcuts. For embedded applications this is unacceptable as we - // don't want to steal focus from the main page nor we don't want - // side-effects from focusing (scrollIntoView). - getWidget().getElement().focus(); - } - - applicationConnection.addHandler( - ApplicationConnection.ApplicationStoppedEvent.TYPE, - new ApplicationConnection.ApplicationStoppedHandler() { - - @Override - public void onApplicationStopped( - ApplicationStoppedEvent event) { - // Stop any polling - if (pollTimer != null) { - pollTimer.cancel(); - pollTimer = null; - } - } - }); - } - - private ClickEventHandler clickEventHandler = new ClickEventHandler(this) { - - @Override - protected void fireClick(NativeEvent event, - MouseEventDetails mouseDetails) { - getRpcProxy(UIServerRpc.class).click(mouseDetails); - } - - }; - - private Timer pollTimer = null; - - @Override - public void updateCaption(ComponentConnector component) { - // NOP The main view never draws caption for its layout - } - - @Override - public VUI getWidget() { - return (VUI) super.getWidget(); - } - - @Override - protected ComponentConnector getContent() { - ComponentConnector connector = super.getContent(); - // VWindow (WindowConnector is its connector)is also a child component - // but it's never a content widget - if (connector instanceof WindowConnector) { - return null; - } else { - return connector; - } - } - - protected void onChildSizeChange() { - ComponentConnector child = getContent(); - if (child == null) { - return; - } - Style childStyle = child.getWidget().getElement().getStyle(); - /* - * Must set absolute position if the child has relative height and - * there's a chance of horizontal scrolling as some browsers will - * otherwise not take the scrollbar into account when calculating the - * height. Assuming v-ui does not have an undefined width for now, see - * #8460. - */ - if (child.isRelativeHeight() && !BrowserInfo.get().isIE9()) { - childStyle.setPosition(Position.ABSOLUTE); - } else { - childStyle.clearPosition(); - } - } - - /** - * Checks if the given sub window is a child of this UI Connector - * - * @deprecated Should be replaced by a more generic mechanism for getting - * non-ComponentConnector children - * @param wc - * @return - */ - @Deprecated - public boolean hasSubWindow(WindowConnector wc) { - return getChildComponents().contains(wc); - } - - /** - * Return an iterator for current subwindows. This method is meant for - * testing purposes only. - * - * @return - */ - public List getSubWindows() { - ArrayList windows = new ArrayList(); - for (ComponentConnector child : getChildComponents()) { - if (child instanceof WindowConnector) { - windows.add((WindowConnector) child); - } - } - return windows; - } - - @Override - public UIState getState() { - return (UIState) super.getState(); - } - - /** - * Returns the state of the Page associated with the UI. - *

- * Note that state is considered an internal part of the connector. You - * should not rely on the state object outside of the connector who owns it. - * If you depend on the state of other connectors you should use their - * public API instead of their state object directly. The page state might - * not be an independent state object but can be embedded in UI state. - *

- * - * @since 7.1 - * @return state object of the page - */ - public PageState getPageState() { - return getState().pageState; - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - ComponentConnector oldChild = null; - ComponentConnector newChild = getContent(); - - for (ComponentConnector c : event.getOldChildren()) { - if (!(c instanceof WindowConnector)) { - oldChild = c; - break; - } - } - - if (oldChild != newChild) { - if (childStateChangeHandlerRegistration != null) { - childStateChangeHandlerRegistration.removeHandler(); - childStateChangeHandlerRegistration = null; - } - if (newChild != null) { - getWidget().setWidget(newChild.getWidget()); - childStateChangeHandlerRegistration = newChild - .addStateChangeHandler(childStateChangeHandler); - // Must handle new child here as state change events are already - // fired - onChildSizeChange(); - } else { - getWidget().setWidget(null); - } - } - - for (ComponentConnector c : getChildComponents()) { - if (c instanceof WindowConnector) { - WindowConnector wc = (WindowConnector) c; - wc.setWindowOrderAndPosition(); - VWindow window = wc.getWidget(); - if (!window.isAttached()) { - - // Attach so that all widgets inside the Window are attached - // when their onStateChange is run - - // Made invisible here for legacy reasons and made visible - // at the end of stateChange. This dance could probably be - // removed - window.setVisible(false); - window.show(); - } - - } - } - - // Close removed sub windows - for (ComponentConnector c : event.getOldChildren()) { - if (c.getParent() != this && c instanceof WindowConnector) { - ((WindowConnector) c).getWidget().hide(); - } - } - } - - @Override - public boolean hasTooltip() { - /* - * Always return true so there's always top level tooltip handler that - * takes care of hiding tooltips whenever the mouse is moved somewhere - * else. - */ - return true; - } - - /** - * Tries to scroll the viewport so that the given connector is in view. - * - * @param componentConnector - * The connector which should be visible - * - */ - public void scrollIntoView(final ComponentConnector componentConnector) { - if (componentConnector == null) { - return; - } - - Scheduler.get().scheduleDeferred(new Command() { - @Override - public void execute() { - componentConnector.getWidget().getElement().scrollIntoView(); - } - }); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - if (stateChangeEvent.hasPropertyChanged("tooltipConfiguration")) { - getConnection().getVTooltip().setCloseTimeout( - getState().tooltipConfiguration.closeTimeout); - getConnection().getVTooltip().setOpenDelay( - getState().tooltipConfiguration.openDelay); - getConnection().getVTooltip().setQuickOpenDelay( - getState().tooltipConfiguration.quickOpenDelay); - getConnection().getVTooltip().setQuickOpenTimeout( - getState().tooltipConfiguration.quickOpenTimeout); - getConnection().getVTooltip().setMaxWidth( - getState().tooltipConfiguration.maxWidth); - } - - if (stateChangeEvent - .hasPropertyChanged("loadingIndicatorConfiguration")) { - getConnection().getLoadingIndicator().setFirstDelay( - getState().loadingIndicatorConfiguration.firstDelay); - getConnection().getLoadingIndicator().setSecondDelay( - getState().loadingIndicatorConfiguration.secondDelay); - getConnection().getLoadingIndicator().setThirdDelay( - getState().loadingIndicatorConfiguration.thirdDelay); - } - - if (stateChangeEvent.hasPropertyChanged("pollInterval")) { - configurePolling(); - } - - if (stateChangeEvent.hasPropertyChanged("pageState.title")) { - String title = getState().pageState.title; - if (title != null) { - com.google.gwt.user.client.Window.setTitle(title); - } - } - - if (stateChangeEvent.hasPropertyChanged("pushConfiguration")) { - getConnection().getMessageSender().setPushEnabled( - getState().pushConfiguration.mode.isEnabled()); - } - if (stateChangeEvent.hasPropertyChanged("reconnectDialogConfiguration")) { - getConnection().getConnectionStateHandler().configurationUpdated(); - } - - if (stateChangeEvent.hasPropertyChanged("overlayContainerLabel")) { - VOverlay.setOverlayContainerLabel(getConnection(), - getState().overlayContainerLabel); - } - } - - private void configurePolling() { - if (pollTimer != null) { - pollTimer.cancel(); - pollTimer = null; - } - if (getState().pollInterval >= 0) { - pollTimer = new Timer() { - @Override - public void run() { - if (getState().pollInterval < 0) { - // Polling has been cancelled server side - pollTimer.cancel(); - pollTimer = null; - return; - } - getRpcProxy(UIServerRpc.class).poll(); - // Send changes even though poll is @Delayed - getConnection().getServerRpcQueue().flush(); - } - }; - pollTimer.scheduleRepeating(getState().pollInterval); - } else { - // Ensure no more polls are sent as polling has been disabled - getConnection().getServerRpcQueue().removeMatching( - new MethodInvocation(getConnectorId(), UIServerRpc.class - .getName(), "poll")); - } - } - - /** - * Invokes the layout analyzer on the server - * - * @since 7.1 - */ - public void analyzeLayouts() { - getRpcProxy(DebugWindowServerRpc.class).analyzeLayouts(); - } - - /** - * Sends a request to the server to print details to console that will help - * the developer to locate the corresponding server-side connector in the - * source code. - * - * @since 7.1 - * @param serverConnector - * the connector to locate - */ - public void showServerDebugInfo(ServerConnector serverConnector) { - getRpcProxy(DebugWindowServerRpc.class).showServerDebugInfo( - serverConnector); - } - - /** - * Sends a request to the server to print a design to the console for the - * given component. - * - * @since 7.5 - * @param connector - * the component connector to output a declarative design for - */ - public void showServerDesign(ServerConnector connector) { - getRpcProxy(DebugWindowServerRpc.class).showServerDesign(connector); - } - - @OnStateChange("theme") - void onThemeChange() { - final String oldTheme = activeTheme; - final String newTheme = getState().theme; - final String oldThemeUrl = getThemeUrl(oldTheme); - final String newThemeUrl = getThemeUrl(newTheme); - - if (SharedUtil.equals(oldTheme, newTheme)) { - // This should only happen on the initial load when activeTheme has - // been updated in init. - - if (newTheme == null) { - return; - } - - // For the embedded case we cannot be 100% sure that the theme has - // been loaded and that the style names have been set. - - if (findStylesheetTag(oldThemeUrl) == null) { - // If there is no style tag, load it the normal way (the class - // name will be added when theme has been loaded) - replaceTheme(null, newTheme, null, newThemeUrl); - } else if (!getWidget().getParent().getElement() - .hasClassName(newTheme)) { - // If only the class name is missing, add that - activateTheme(newTheme); - } - return; - } - - getLogger().info("Changing theme from " + oldTheme + " to " + newTheme); - replaceTheme(oldTheme, newTheme, oldThemeUrl, newThemeUrl); - } - - /** - * Loads the new theme and removes references to the old theme - * - * @since 7.4.3 - * @param oldTheme - * The name of the old theme - * @param newTheme - * The name of the new theme - * @param oldThemeUrl - * The url of the old theme - * @param newThemeUrl - * The url of the new theme - */ - protected void replaceTheme(final String oldTheme, final String newTheme, - String oldThemeUrl, final String newThemeUrl) { - - LinkElement tagToReplace = null; - - if (oldTheme != null) { - tagToReplace = findStylesheetTag(oldThemeUrl); - - if (tagToReplace == null) { - getLogger() - .warning( - "Did not find the link tag for the old theme (" - + oldThemeUrl - + "), adding a new stylesheet for the new theme (" - + newThemeUrl + ")"); - } - } - - if (newTheme != null) { - loadTheme(newTheme, newThemeUrl, tagToReplace); - } else { - if (tagToReplace != null) { - tagToReplace.getParentElement().removeChild(tagToReplace); - } - - activateTheme(null); - } - - } - - private void updateVaadinFavicon(String newTheme) { - NodeList iconElements = querySelectorAll("link[rel~=\"icon\"]"); - for (int i = 0; i < iconElements.getLength(); i++) { - Element iconElement = iconElements.getItem(i); - - String href = iconElement.getAttribute("href"); - if (href != null && href.contains("VAADIN/themes") - && href.endsWith("/favicon.ico")) { - href = href.replaceFirst("VAADIN/themes/.+?/favicon.ico", - "VAADIN/themes/" + newTheme + "/favicon.ico"); - iconElement.setAttribute("href", href); - } - } - } - - private static native NodeList querySelectorAll(String selector) - /*-{ - return $doc.querySelectorAll(selector); - }-*/; - - /** - * Finds a link tag for a style sheet with the given URL - * - * @since 7.3 - * @param url - * the URL of the style sheet - * @return the link tag or null if no matching link tag was found - */ - private LinkElement findStylesheetTag(String url) { - NodeList linkTags = getHead().getElementsByTagName( - LinkElement.TAG); - for (int i = 0; i < linkTags.getLength(); i++) { - final LinkElement link = LinkElement.as(linkTags.getItem(i)); - if ("stylesheet".equals(link.getRel()) - && "text/css".equals(link.getType()) - && url.equals(link.getHref())) { - return link; - } - } - return null; - } - - /** - * Loads the given theme and replaces the given link element with the new - * theme link element. - * - * @param newTheme - * The name of the new theme - * @param newThemeUrl - * The url of the new theme - * @param tagToReplace - * The link element to replace. If null, then the new link - * element is added at the end. - */ - private void loadTheme(final String newTheme, final String newThemeUrl, - final LinkElement tagToReplace) { - LinkElement newThemeLinkElement = Document.get().createLinkElement(); - newThemeLinkElement.setRel("stylesheet"); - newThemeLinkElement.setType("text/css"); - newThemeLinkElement.setHref(newThemeUrl); - ResourceLoader.addOnloadHandler(newThemeLinkElement, - new ResourceLoadListener() { - - @Override - public void onLoad(ResourceLoadEvent event) { - getLogger().info( - "Loading of " + newTheme + " from " - + newThemeUrl + " completed"); - - if (tagToReplace != null) { - tagToReplace.getParentElement().removeChild( - tagToReplace); - } - activateTheme(newTheme); - } - - @Override - public void onError(ResourceLoadEvent event) { - getLogger().warning( - "Could not load theme from " - + getThemeUrl(newTheme)); - } - }, null); - - if (tagToReplace != null) { - getHead().insertBefore(newThemeLinkElement, tagToReplace); - } else { - getHead().appendChild(newThemeLinkElement); - } - } - - /** - * Activates the new theme. Assumes the theme has been loaded and taken into - * use in the browser. - * - * @since 7.4.3 - * @param newTheme - * The name of the new theme - */ - protected void activateTheme(String newTheme) { - if (activeTheme != null) { - getWidget().getParent().removeStyleName(activeTheme); - VOverlay.getOverlayContainer(getConnection()).removeClassName( - activeTheme); - } - - String oldThemeBase = getConnection().translateVaadinUri("theme://"); - - activeTheme = newTheme; - - if (newTheme != null) { - getWidget().getParent().addStyleName(newTheme); - VOverlay.getOverlayContainer(getConnection()).addClassName( - activeTheme); - - updateVaadinFavicon(newTheme); - - } - - // Request a full resynchronization from the server to deal with legacy - // components - getConnection().getMessageSender().resynchronize(); - - // Immediately update state and do layout while waiting for the resync - forceStateChangeRecursively(UIConnector.this); - getLayoutManager().forceLayout(); - } - - /** - * Force a full recursive recheck of every connector's state variables. - * - * @see #forceStateChange() - * - * @since 7.3 - */ - protected static void forceStateChangeRecursively( - AbstractConnector connector) { - connector.forceStateChange(); - - for (ServerConnector child : connector.getChildren()) { - if (child instanceof AbstractConnector) { - forceStateChangeRecursively((AbstractConnector) child); - } else { - getLogger().warning( - "Could not force state change for unknown connector type: " - + child.getClass().getName()); - } - } - - } - - /** - * Internal helper to get the theme URL for a given theme - * - * @since 7.3 - * @param theme - * the name of the theme - * @return The URL the theme can be loaded from - */ - private String getThemeUrl(String theme) { - String themeUrl = getConnection().translateVaadinUri( - ApplicationConstants.VAADIN_PROTOCOL_PREFIX + "themes/" + theme - + "/styles" + ".css"); - // Parameter appended to bypass caches after version upgrade. - themeUrl += "?v=" + Version.getFullVersion(); - return themeUrl; - - } - - /** - * Returns the name of the theme currently in used by the UI - * - * @since 7.3 - * @return the theme name used by this UI - */ - public String getActiveTheme() { - return activeTheme; - } - - private static Logger getLogger() { - return Logger.getLogger(UIConnector.class.getName()); - } -} diff --git a/client/src/com/vaadin/client/ui/upload/UploadConnector.java b/client/src/com/vaadin/client/ui/upload/UploadConnector.java deleted file mode 100644 index 9e25770a17..0000000000 --- a/client/src/com/vaadin/client/ui/upload/UploadConnector.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.upload; - -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.Paintable; -import com.vaadin.client.UIDL; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractComponentConnector; -import com.vaadin.client.ui.VUpload; -import com.vaadin.shared.EventId; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.upload.UploadClientRpc; -import com.vaadin.shared.ui.upload.UploadServerRpc; -import com.vaadin.shared.ui.upload.UploadState; -import com.vaadin.ui.Upload; - -@Connect(Upload.class) -public class UploadConnector extends AbstractComponentConnector implements - Paintable { - - public UploadConnector() { - registerRpc(UploadClientRpc.class, new UploadClientRpc() { - @Override - public void submitUpload() { - getWidget().submit(); - } - }); - } - - @Override - protected void init() { - super.init(); - - getWidget().fu.addChangeHandler(new ChangeHandler() { - @Override - public void onChange(ChangeEvent event) { - if (hasEventListener(EventId.CHANGE)) { - getRpcProxy(UploadServerRpc.class).change( - getWidget().fu.getFilename()); - } - } - }); - } - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (!isRealUpdate(uidl)) { - return; - } - if (uidl.hasAttribute("notStarted")) { - getWidget().t.schedule(400); - return; - } - getWidget().setImmediate(getState().immediate); - getWidget().client = client; - getWidget().paintableId = uidl.getId(); - getWidget().nextUploadId = uidl.getIntAttribute("nextid"); - final String action = client.translateVaadinUri(uidl - .getStringVariable("action")); - getWidget().element.setAction(action); - if (uidl.hasAttribute("buttoncaption")) { - getWidget().submitButton.setText(uidl - .getStringAttribute("buttoncaption")); - getWidget().submitButton.setVisible(true); - } else { - getWidget().submitButton.setVisible(false); - } - getWidget().fu.setName(getWidget().paintableId + "_file"); - - if (!isEnabled() || isReadOnly()) { - getWidget().disableUpload(); - } else if (!uidl.getBooleanAttribute("state")) { - // Enable the button only if an upload is not in progress - getWidget().enableUpload(); - getWidget().ensureTargetFrame(); - } - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - getWidget().disableTitle(hasTooltip()); - } - - @Override - public VUpload getWidget() { - return (VUpload) super.getWidget(); - } - - @Override - public UploadState getState() { - return (UploadState) super.getState(); - } -} diff --git a/client/src/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategy.java b/client/src/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategy.java deleted file mode 100644 index 2c2e10594d..0000000000 --- a/client/src/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategy.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.upload; - -import com.vaadin.client.ui.VUpload; - -public class UploadIFrameOnloadStrategy { - - public native void hookEvents(com.google.gwt.dom.client.Element iframe, - VUpload upload) - /*-{ - iframe.onload = $entry(function() { - upload.@com.vaadin.client.ui.VUpload::onSubmitComplete()(); - }); - }-*/; - - /** - * @param iframe - * the iframe whose onLoad event is to be cleaned - */ - public native void unHookEvents(com.google.gwt.dom.client.Element iframe) - /*-{ - iframe.onload = null; - }-*/; - -} diff --git a/client/src/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategyIE.java b/client/src/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategyIE.java deleted file mode 100644 index 0c114e2ee7..0000000000 --- a/client/src/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategyIE.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.upload; - -import com.google.gwt.dom.client.Element; -import com.vaadin.client.ui.VUpload; - -/** - * IE does not have onload, detect onload via readystatechange - * - */ -public class UploadIFrameOnloadStrategyIE extends UploadIFrameOnloadStrategy { - @Override - public native void hookEvents(Element iframe, VUpload upload) - /*-{ - iframe.onreadystatechange = $entry(function() { - if (iframe.readyState == 'complete') { - upload.@com.vaadin.client.ui.VUpload::onSubmitComplete()(); - } - }); - }-*/; - - @Override - public native void unHookEvents(Element iframe) - /*-{ - iframe.onreadystatechange = null; - }-*/; - -} diff --git a/client/src/com/vaadin/client/ui/video/VideoConnector.java b/client/src/com/vaadin/client/ui/video/VideoConnector.java deleted file mode 100644 index de53368d6a..0000000000 --- a/client/src/com/vaadin/client/ui/video/VideoConnector.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.video; - -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.MediaBaseConnector; -import com.vaadin.client.ui.VVideo; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.video.VideoConstants; -import com.vaadin.shared.ui.video.VideoState; -import com.vaadin.ui.Video; - -@Connect(Video.class) -public class VideoConnector extends MediaBaseConnector { - - @Override - public VideoState getState() { - return (VideoState) super.getState(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - getWidget().setPoster(getResourceUrl(VideoConstants.POSTER_RESOURCE)); - } - - @Override - public VVideo getWidget() { - return (VVideo) super.getWidget(); - } - - @Override - protected String getDefaultAltHtml() { - return "Your browser does not support the video element."; - } - -} diff --git a/client/src/com/vaadin/client/ui/window/WindowConnector.java b/client/src/com/vaadin/client/ui/window/WindowConnector.java deleted file mode 100644 index 9ea3c8bb68..0000000000 --- a/client/src/com/vaadin/client/ui/window/WindowConnector.java +++ /dev/null @@ -1,512 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.window; - -import java.util.logging.Logger; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.DoubleClickEvent; -import com.google.gwt.event.dom.client.DoubleClickHandler; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Window; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorHierarchyChangeEvent; -import com.vaadin.client.LayoutManager; -import com.vaadin.client.Paintable; -import com.vaadin.client.UIDL; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.communication.RpcProxy; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.AbstractSingleComponentContainerConnector; -import com.vaadin.client.ui.ClickEventHandler; -import com.vaadin.client.ui.PostLayoutListener; -import com.vaadin.client.ui.ShortcutActionHandler; -import com.vaadin.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; -import com.vaadin.client.ui.SimpleManagedLayout; -import com.vaadin.client.ui.VWindow; -import com.vaadin.client.ui.layout.MayScrollChildren; -import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.window.WindowMode; -import com.vaadin.shared.ui.window.WindowServerRpc; -import com.vaadin.shared.ui.window.WindowState; - -@Connect(value = com.vaadin.ui.Window.class) -public class WindowConnector extends AbstractSingleComponentContainerConnector - implements Paintable, BeforeShortcutActionListener, - SimpleManagedLayout, PostLayoutListener, MayScrollChildren, - WindowMoveHandler { - - private Node windowClone; - - private ClickEventHandler clickEventHandler = new ClickEventHandler(this) { - @Override - protected void fireClick(NativeEvent event, - MouseEventDetails mouseDetails) { - getRpcProxy(WindowServerRpc.class).click(mouseDetails); - } - }; - - abstract class WindowEventHandler implements ClickHandler, - DoubleClickHandler { - } - - private WindowEventHandler maximizeRestoreClickHandler = new WindowEventHandler() { - - @Override - public void onClick(ClickEvent event) { - final Element target = event.getNativeEvent().getEventTarget() - .cast(); - if (target == getWidget().maximizeRestoreBox) { - // Click on maximize/restore box - onMaximizeRestore(); - } - } - - @Override - public void onDoubleClick(DoubleClickEvent event) { - final Element target = event.getNativeEvent().getEventTarget() - .cast(); - if (getWidget().header.isOrHasChild(target)) { - // Double click on header - onMaximizeRestore(); - } - } - }; - - @Override - public boolean delegateCaptionHandling() { - return false; - } - - @Override - protected void init() { - super.init(); - - VWindow window = getWidget(); - window.id = getConnectorId(); - window.client = getConnection(); - - getLayoutManager().registerDependency(this, - window.contentPanel.getElement()); - getLayoutManager().registerDependency(this, window.header); - getLayoutManager().registerDependency(this, window.footer); - - window.addHandler(maximizeRestoreClickHandler, ClickEvent.getType()); - window.addHandler(maximizeRestoreClickHandler, - DoubleClickEvent.getType()); - - window.setOwner(getConnection().getUIConnector().getWidget()); - - window.addMoveHandler(this); - } - - @Override - public void onUnregister() { - LayoutManager lm = getLayoutManager(); - VWindow window = getWidget(); - lm.unregisterDependency(this, window.contentPanel.getElement()); - lm.unregisterDependency(this, window.header); - lm.unregisterDependency(this, window.footer); - } - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - VWindow window = getWidget(); - String connectorId = getConnectorId(); - - // Workaround needed for Testing Tools (GWT generates window DOM - // slightly different in different browsers). - window.closeBox.setId(connectorId + "_window_close"); - window.maximizeRestoreBox - .setId(connectorId + "_window_maximizerestore"); - - window.visibilityChangesDisabled = true; - if (!isRealUpdate(uidl)) { - return; - } - window.visibilityChangesDisabled = false; - - // we may have actions - for (int i = 0; i < uidl.getChildCount(); i++) { - UIDL childUidl = uidl.getChildUIDL(i); - if (childUidl.getTag().equals("actions")) { - if (window.shortcutHandler == null) { - window.shortcutHandler = new ShortcutActionHandler( - connectorId, client); - } - window.shortcutHandler.updateActionMap(childUidl); - } - - } - - if (uidl.hasAttribute("bringToFront")) { - /* - * Focus as a side-effect. Will be overridden by - * ApplicationConnection if another component was focused by the - * server side. - */ - window.contentPanel.focus(); - window.bringToFrontSequence = uidl.getIntAttribute("bringToFront"); - VWindow.deferOrdering(); - } - } - - @Override - public void updateCaption(ComponentConnector component) { - // NOP, window has own caption, layout caption not rendered - } - - @Override - public void onBeforeShortcutAction(Event e) { - // NOP, nothing to update just avoid workaround ( causes excess - // blur/focus ) - } - - @Override - public VWindow getWidget() { - return (VWindow) super.getWidget(); - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - // We always have 1 child, unless the child is hidden - getWidget().contentPanel.setWidget(getContentWidget()); - - if (getParent() == null && windowClone != null) { - // If the window is removed from the UI, add the copy of the - // contents to the window (in case of an 'out-animation') - getWidget().getElement().removeAllChildren(); - getWidget().getElement().appendChild(windowClone); - - // Clean reference - windowClone = null; - } - - } - - @Override - public void layout() { - LayoutManager lm = getLayoutManager(); - VWindow window = getWidget(); - ComponentConnector content = getContent(); - boolean hasContent = (content != null); - Element contentElement = window.contentPanel.getElement(); - - Style contentStyle = window.contents.getStyle(); - - int headerHeight = lm.getOuterHeight(window.header); - contentStyle.setPaddingTop(headerHeight, Unit.PX); - contentStyle.setMarginTop(-headerHeight, Unit.PX); - - int footerHeight = lm.getOuterHeight(window.footer); - contentStyle.setPaddingBottom(footerHeight, Unit.PX); - contentStyle.setMarginBottom(-footerHeight, Unit.PX); - - int minWidth = lm.getOuterWidth(window.header) - - lm.getInnerWidth(window.header); - int minHeight = footerHeight + headerHeight; - - getWidget().getElement().getStyle().setPropertyPx("minWidth", minWidth); - getWidget().getElement().getStyle() - .setPropertyPx("minHeight", minHeight); - - /* - * Must set absolute position if the child has relative height and - * there's a chance of horizontal scrolling as some browsers will - * otherwise not take the scrollbar into account when calculating the - * height. - */ - if (hasContent) { - Element layoutElement = content.getWidget().getElement(); - Style childStyle = layoutElement.getStyle(); - - // IE8 needs some hackery to measure its content correctly - WidgetUtil.forceIE8Redraw(layoutElement); - - if (content.isRelativeHeight() && !BrowserInfo.get().isIE9()) { - childStyle.setPosition(Position.ABSOLUTE); - - Style wrapperStyle = contentElement.getStyle(); - if (window.getElement().getStyle().getWidth().length() == 0 - && !content.isRelativeWidth()) { - /* - * Need to lock width to make undefined width work even with - * absolute positioning - */ - int contentWidth = lm.getOuterWidth(layoutElement); - wrapperStyle.setWidth(contentWidth, Unit.PX); - } else { - wrapperStyle.clearWidth(); - } - } else { - childStyle.clearPosition(); - } - } - - } - - @Override - public void postLayout() { - VWindow window = getWidget(); - - if (!window.isAttached()) { - Logger.getLogger(WindowConnector.class.getName()).warning( - "Called postLayout to detached Window."); - return; - } - if (window.centered && getState().windowMode != WindowMode.MAXIMIZED) { - window.center(); - } - window.positionOrSizeUpdated(); - - if (getParent() != null) { - // Take a copy of the contents, since the server will detach all - // children of this window when it's closed, and the window will be - // emptied during the following hierarchy update (we need to keep - // the contents visible for the duration of a possible - // 'out-animation') - - // Fix for #14645 and #14785 - as soon as we clone audio and video - // tags, they start fetching data, and playing immediately in - // background, in case autoplay attribute is present. Therefore we - // have to replace them with stubs in the clone. And we can't just - // erase them, because there are corresponding player widgets to - // animate - windowClone = cloneNodeFilteringMedia(getWidget().getElement() - .getFirstChild()); - } - } - - private Node cloneNodeFilteringMedia(Node node) { - if (node instanceof Element) { - Element old = (Element) node; - if ("audio".equalsIgnoreCase(old.getTagName()) - || "video".equalsIgnoreCase(old.getTagName())) { - if (!old.hasAttribute("controls") - && "audio".equalsIgnoreCase(old.getTagName())) { - return null; // nothing to animate, so we won't add this to - // the clone - } - Element newEl = DOM.createElement(old.getTagName()); - if (old.hasAttribute("controls")) { - newEl.setAttribute("controls", old.getAttribute("controls")); - } - if (old.hasAttribute("style")) { - newEl.setAttribute("style", old.getAttribute("style")); - } - if (old.hasAttribute("class")) { - newEl.setAttribute("class", old.getAttribute("class")); - } - return newEl; - } - } - Node res = node.cloneNode(false); - if (node.hasChildNodes()) { - NodeList nl = node.getChildNodes(); - for (int i = 0; i < nl.getLength(); i++) { - Node clone = cloneNodeFilteringMedia(nl.getItem(i)); - if (clone != null) { - res.appendChild(clone); - } - } - } - return res; - } - - @Override - public WindowState getState() { - return (WindowState) super.getState(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - VWindow window = getWidget(); - WindowState state = getState(); - - if (state.modal != window.vaadinModality) { - window.setVaadinModality(!window.vaadinModality); - } - boolean resizeable = state.resizable - && state.windowMode == WindowMode.NORMAL; - window.setResizable(resizeable); - - window.resizeLazy = state.resizeLazy; - - window.setDraggable(state.draggable - && state.windowMode == WindowMode.NORMAL); - - window.updateMaximizeRestoreClassName(state.resizable, state.windowMode); - - // Caption must be set before required header size is measured. If - // the caption attribute is missing the caption should be cleared. - String iconURL = null; - if (getIconUri() != null) { - iconURL = getIconUri(); - } - - window.setAssistivePrefix(state.assistivePrefix); - window.setAssistivePostfix(state.assistivePostfix); - window.setCaption(state.caption, iconURL, getState().captionAsHtml); - - window.setWaiAriaRole(getState().role); - window.setAssistiveDescription(state.contentDescription); - - window.setTabStopEnabled(getState().assistiveTabStop); - window.setTabStopTopAssistiveText(getState().assistiveTabStopTopText); - window.setTabStopBottomAssistiveText(getState().assistiveTabStopBottomText); - - clickEventHandler.handleEventHandlerRegistration(); - - window.immediate = state.immediate; - - window.setClosable(!isReadOnly()); - // initialize position from state - updateWindowPosition(); - - // setting scrollposition must happen after children is rendered - window.contentPanel.setScrollPosition(state.scrollTop); - window.contentPanel.setHorizontalScrollPosition(state.scrollLeft); - - // Center this window on screen if requested - // This had to be here because we might not know the content size before - // everything is painted into the window - - // centered is this is unset on move/resize - window.centered = state.centered; - // Ensure centering before setting visible (#16486) - if (window.centered && getState().windowMode != WindowMode.MAXIMIZED) { - Scheduler.get().scheduleFinally(new ScheduledCommand() { - - @Override - public void execute() { - getWidget().center(); - } - }); - } - window.setVisible(true); - - // ensure window is not larger than browser window - if (window.getOffsetWidth() > Window.getClientWidth()) { - window.setWidth(Window.getClientWidth() + "px"); - } - if (window.getOffsetHeight() > Window.getClientHeight()) { - window.setHeight(Window.getClientHeight() + "px"); - } - } - - // Need to override default because of window mode - @Override - protected void updateComponentSize() { - if (getState().windowMode == WindowMode.NORMAL) { - super.updateComponentSize(); - } else if (getState().windowMode == WindowMode.MAXIMIZED) { - super.updateComponentSize("100%", "100%"); - } - } - - protected void updateWindowPosition() { - VWindow window = getWidget(); - WindowState state = getState(); - if (state.windowMode == WindowMode.NORMAL) { - // if centered, position handled in postLayout() - if (!state.centered - && (state.positionX >= 0 || state.positionY >= 0)) { - // If both positions are negative, then - // setWindowOrderAndPosition has already taken care of - // positioning the window so it stacks with other windows - window.setPopupPosition(state.positionX, state.positionY); - } - } else if (state.windowMode == WindowMode.MAXIMIZED) { - window.setPopupPositionNoUpdate(0, 0); - } - } - - protected void updateWindowMode() { - VWindow window = getWidget(); - WindowState state = getState(); - - // update draggable on widget - window.setDraggable(state.draggable - && state.windowMode == WindowMode.NORMAL); - // update resizable on widget - window.setResizable(state.resizable - && state.windowMode == WindowMode.NORMAL); - updateComponentSize(); - updateWindowPosition(); - window.updateMaximizeRestoreClassName(state.resizable, state.windowMode); - window.updateContentsSize(); - } - - protected void onMaximizeRestore() { - WindowState state = getState(); - if (state.resizable) { - if (state.windowMode == WindowMode.MAXIMIZED) { - state.windowMode = WindowMode.NORMAL; - } else { - state.windowMode = WindowMode.MAXIMIZED; - } - updateWindowMode(); - - VWindow window = getWidget(); - window.bringToFront(); - - getRpcProxy(WindowServerRpc.class).windowModeChanged( - state.windowMode); - } - } - - /** - * Gives the WindowConnector an order number. As a side effect, moves the - * window according to its order number so the windows are stacked. This - * method should be called for each window in the order they should appear. - */ - public void setWindowOrderAndPosition() { - getWidget().setWindowOrderAndPosition(); - } - - @Override - public boolean hasTooltip() { - /* - * Tooltip event handler always needed on the window widget to make sure - * tooltips are properly hidden. (#11448) - */ - return true; - } - - @Override - public void onWindowMove(WindowMoveEvent event) { - RpcProxy.create(WindowServerRpc.class, this).windowMoved( - event.getNewX(), event.getNewY()); - - } -} diff --git a/client/src/com/vaadin/client/ui/window/WindowMoveEvent.java b/client/src/com/vaadin/client/ui/window/WindowMoveEvent.java deleted file mode 100644 index add7ee740f..0000000000 --- a/client/src/com/vaadin/client/ui/window/WindowMoveEvent.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.window; - -import com.google.gwt.event.shared.GwtEvent; - -/** - * Event for window position updates - * - * @since 7.1.9 - * @author Vaadin Ltd - */ -public class WindowMoveEvent extends GwtEvent { - - private static final Type TYPE = new Type(); - - private final int newX; - private final int newY; - - /** - * Creates a new event with the given parameters - * - * @param x - * The new x-position for the VWindow - * @param y - * The new y-position for the VWindow - */ - public WindowMoveEvent(int x, int y) { - newX = x; - newY = y; - } - - /** - * Gets the new x position of the window - * - * @return the new X position of the VWindow - */ - public int getNewX() { - return newX; - } - - /** - * Gets the new y position of the window - * - * @return the new Y position of the VWindow - */ - public int getNewY() { - return newY; - } - - /** - * Gets the type of the event - * - * @return the type of the event - */ - public static Type getType() { - return TYPE; - } - - @Override - public Type getAssociatedType() { - return TYPE; - } - - @Override - protected void dispatch(WindowMoveHandler handler) { - handler.onWindowMove(this); - } -} diff --git a/client/src/com/vaadin/client/ui/window/WindowMoveHandler.java b/client/src/com/vaadin/client/ui/window/WindowMoveHandler.java deleted file mode 100644 index 0ac348af68..0000000000 --- a/client/src/com/vaadin/client/ui/window/WindowMoveHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.window; - -import com.google.gwt.event.shared.EventHandler; - -/** - * Handler for {@link WindowMoveEvent}s - * - * @since 7.1.9 - * @author Vaadin Ltd - */ -public interface WindowMoveHandler extends EventHandler { - - /** - * Called when the VWindow was moved by the user. - * - * @param event - * Contains new coordinates for the VWindow - */ - public void onWindowMove(WindowMoveEvent event); -} diff --git a/client/src/com/vaadin/client/widget/escalator/Cell.java b/client/src/com/vaadin/client/widget/escalator/Cell.java deleted file mode 100644 index 08dbcf6955..0000000000 --- a/client/src/com/vaadin/client/widget/escalator/Cell.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.escalator; - -import com.google.gwt.dom.client.TableCellElement; - -/** - * Describes a cell - *

- * It's a representation of the element in a grid cell, and its row and column - * indices. - *

- * Unlike the {@link FlyweightRow}, an instance of {@link Cell} can be stored in - * a field. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class Cell { - - private final int row; - - private final int column; - - private final TableCellElement element; - - /** - * Constructs a new {@link Cell}. - * - * @param row - * The index of the row - * @param column - * The index of the column - * @param element - * The cell element - */ - public Cell(int row, int column, TableCellElement element) { - super(); - this.row = row; - this.column = column; - this.element = element; - } - - /** - * Returns the index of the row the cell resides in. - * - * @return the row index - * - */ - public int getRow() { - return row; - } - - /** - * Returns the index of the column the cell resides in. - * - * @return the column index - */ - public int getColumn() { - return column; - } - - /** - * Returns the element of the cell. - * - * @return the cell element - */ - public TableCellElement getElement() { - return element; - } - -} diff --git a/client/src/com/vaadin/client/widget/escalator/ColumnConfiguration.java b/client/src/com/vaadin/client/widget/escalator/ColumnConfiguration.java deleted file mode 100644 index 76f6a55b8a..0000000000 --- a/client/src/com/vaadin/client/widget/escalator/ColumnConfiguration.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.widget.escalator; - -import java.util.Map; - -/** - * A representation of the columns in an instance of {@link Escalator}. - * - * @since 7.4 - * @author Vaadin Ltd - * @see Escalator#getColumnConfiguration() - */ -public interface ColumnConfiguration { - - /** - * Removes columns at certain indices. - *

- * If any of the removed columns were frozen, the number of frozen columns - * will be reduced by the number of the removed columns that were frozen. - *

- * Note: This method simply removes the given columns, and does not - * do much of anything else. Especially if you have column spans, you - * probably need to run {@link #refreshColumns(int, int)} or - * {@link RowContainer#refreshRows(int, int)} - * - * @param index - * the index of the first column to be removed - * @param numberOfColumns - * the number of rows to remove, starting from {@code index} - * @throws IndexOutOfBoundsException - * if the entire range of removed columns is not currently - * present in the escalator - * @throws IllegalArgumentException - * if numberOfColumns is less than 1. - */ - public void removeColumns(int index, int numberOfColumns) - throws IndexOutOfBoundsException, IllegalArgumentException; - - /** - * Adds columns at a certain index. - *

- * The new columns will be inserted between the column at the index, and the - * column before (an index of 0 means that the columns are inserted at the - * beginning). Therefore, the columns at the index and afterwards will be - * moved to the right. - *

- * The contents of the inserted columns will be queried from the respective - * cell renderers in the header, body and footer. - *

- * If there are frozen columns and the first added column is to the left of - * the last frozen column, the number of frozen columns will be increased by - * the number of inserted columns. - *

- * Note: Only the contents of the inserted columns will be - * rendered. If inserting new columns affects the contents of existing - * columns (e.g. you have column spans), - * {@link RowContainer#refreshRows(int, int)} or - * {@link #refreshColumns(int, int)} needs to be called as appropriate. - * - * @param index - * the index of the column before which new columns are inserted, - * or {@link #getColumnCount()} to add new columns at the end - * @param numberOfColumns - * the number of columns to insert after the index - * @throws IndexOutOfBoundsException - * if index is not an integer in the range - * [0..{@link #getColumnCount()}] - * @throws IllegalArgumentException - * if {@code numberOfColumns} is less than 1. - */ - public void insertColumns(int index, int numberOfColumns) - throws IndexOutOfBoundsException, IllegalArgumentException; - - /** - * Returns the number of columns in the escalator. - * - * @return the number of columns in the escalator - */ - public int getColumnCount(); - - /** - * Sets the number of leftmost columns that are not affected by horizontal - * scrolling. - * - * @param count - * the number of columns to freeze - * - * @throws IllegalArgumentException - * if the column count is < 0 or > the number of columns - * - */ - public void setFrozenColumnCount(int count) throws IllegalArgumentException; - - /** - * Get the number of leftmost columns that are not affected by horizontal - * scrolling. - * - * @return the number of frozen columns - */ - public int getFrozenColumnCount(); - - /** - * Sets (or unsets) an explicit width for a column. - * - * @param index - * the index of the column for which to set a width - * @param px - * the number of pixels the indicated column should be, or a - * negative number to let the escalator decide - * @throws IllegalArgumentException - * if index is not a valid column index - */ - public void setColumnWidth(int index, double px) - throws IllegalArgumentException; - - /** - * Returns the user-defined width of a column. - * - * @param index - * the index of the column for which to retrieve the width - * @return the column's width in pixels, or a negative number if the width - * is implicitly decided by the escalator - * @throws IllegalArgumentException - * if index is not a valid column index - */ - public double getColumnWidth(int index) throws IllegalArgumentException; - - /** - * Sets widths for a set of columns. - * - * @param indexWidthMap - * a map from column index to its respective width to be set. If - * the given width for a column index is negative, the column is - * resized-to-fit. - * @throws IllegalArgumentException - * if {@code indexWidthMap} is {@code null} - * @throws IllegalArgumentException - * if any column index in {@code indexWidthMap} is invalid - * @throws NullPointerException - * If any value in the map is null - */ - public void setColumnWidths(Map indexWidthMap) - throws IllegalArgumentException; - - /** - * Returns the actual width of a column. - * - * @param index - * the index of the column for which to retrieve the width - * @return the column's actual width in pixels - * @throws IllegalArgumentException - * if index is not a valid column index - */ - public double getColumnWidthActual(int index) - throws IllegalArgumentException; - - /** - * Refreshes a range of rows in the current row containers in each Escalator - * section. - *

- * The data for the refreshed columns is queried from the current cell - * renderer. - * - * @param index - * the index of the first row that will be updated - * @param numberOfRows - * the number of rows to update, starting from the index - * @throws IndexOutOfBoundsException - * if any integer number in the range - * [index..(index+numberOfColumns)] is not an - * existing column index. - * @throws IllegalArgumentException - * if {@code numberOfColumns} is less than 1. - * @see RowContainer#setEscalatorUpdater(EscalatorUpdater) - * @see Escalator#getHeader() - * @see Escalator#getBody() - * @see Escalator#getFooter() - */ - public void refreshColumns(int index, int numberOfColumns) - throws IndexOutOfBoundsException, IllegalArgumentException; -} diff --git a/client/src/com/vaadin/client/widget/escalator/EscalatorUpdater.java b/client/src/com/vaadin/client/widget/escalator/EscalatorUpdater.java deleted file mode 100644 index 54507a7650..0000000000 --- a/client/src/com/vaadin/client/widget/escalator/EscalatorUpdater.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.widget.escalator; - -/** - * An interface that allows client code to define how a certain row in Escalator - * will be displayed. The contents of an escalator's header, body and footer are - * rendered by their respective updaters. - *

- * The updater is responsible for internally handling all remote communication, - * should the displayed data need to be fetched remotely. - *

- * This has a similar function to {@link Grid Grid's} {@link Renderer Renderers} - * , although they operate on different abstraction levels. - * - * @since 7.4 - * @author Vaadin Ltd - * @see RowContainer#setEscalatorUpdater(EscalatorUpdater) - * @see Escalator#getHeader() - * @see Escalator#getBody() - * @see Escalator#getFooter() - * @see Renderer - */ -public interface EscalatorUpdater { - - /** - * An {@link EscalatorUpdater} that doesn't render anything. - */ - public static final EscalatorUpdater NULL = new EscalatorUpdater() { - @Override - public void update(final Row row, - final Iterable cellsToUpdate) { - // NOOP - } - - @Override - public void preAttach(final Row row, - final Iterable cellsToAttach) { - // NOOP - - } - - @Override - public void postAttach(final Row row, - final Iterable attachedCells) { - // NOOP - } - - @Override - public void preDetach(final Row row, - final Iterable cellsToDetach) { - // NOOP - } - - @Override - public void postDetach(final Row row, - final Iterable detachedCells) { - // NOOP - } - }; - - /** - * Renders a row contained in a row container. - *

- * Note: If rendering of cells is deferred (e.g. because - * asynchronous data retrieval), this method is responsible for explicitly - * displaying some placeholder data (empty content is valid). Because the - * cells (and rows) in an escalator are recycled, failing to reset a cell's - * presentation will lead to wrong data being displayed in the escalator. - *

- * For performance reasons, the escalator will never autonomously clear any - * data in a cell. - * - * @param row - * Information about the row that is being updated. - * Note: You should not store nor reuse this reference. - * @param cellsToUpdate - * A collection of cells that need to be updated. Note: - * You should neither store nor reuse the reference to the - * iterable, nor to the individual cells. - */ - public void update(Row row, Iterable cellsToUpdate); - - /** - * Called before attaching new cells to the escalator. - * - * @param row - * Information about the row to which the cells will be added. - * Note: You should not store nor reuse this reference. - * @param cellsToAttach - * A collection of cells that are about to be attached. - * Note: You should neither store nor reuse the - * reference to the iterable, nor to the individual cells. - * - */ - public void preAttach(Row row, Iterable cellsToAttach); - - /** - * Called after attaching new cells to the escalator. - * - * @param row - * Information about the row to which the cells were added. - * Note: You should not store nor reuse this reference. - * @param attachedCells - * A collection of cells that were attached. Note: You - * should neither store nor reuse the reference to the iterable, - * nor to the individual cells. - * - */ - public void postAttach(Row row, Iterable attachedCells); - - /** - * Called before detaching cells from the escalator. - * - * @param row - * Information about the row from which the cells will be - * removed. Note: You should not store nor reuse this - * reference. - * @param cellsToAttach - * A collection of cells that are about to be detached. - * Note: You should neither store nor reuse the - * reference to the iterable, nor to the individual cells. - * - */ - public void preDetach(Row row, Iterable cellsToDetach); - - /** - * Called after detaching cells from the escalator. - * - * @param row - * Information about the row from which the cells were removed. - * Note: You should not store nor reuse this reference. - * @param attachedCells - * A collection of cells that were detached. Note: You - * should neither store nor reuse the reference to the iterable, - * nor to the individual cells. - * - */ - public void postDetach(Row row, Iterable detachedCells); - -} diff --git a/client/src/com/vaadin/client/widget/escalator/FlyweightCell.java b/client/src/com/vaadin/client/widget/escalator/FlyweightCell.java deleted file mode 100644 index b77b752327..0000000000 --- a/client/src/com/vaadin/client/widget/escalator/FlyweightCell.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.escalator; - -import java.util.List; - -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.TableCellElement; -import com.vaadin.client.widget.escalator.FlyweightRow.CellIterator; -import com.vaadin.client.widgets.Escalator; - -/** - * A {@link FlyweightCell} represents a cell in the {@link Grid} or - * {@link Escalator} at a certain point in time. - * - *

- * Since the {@link FlyweightCell} follows the Flyweight-pattern - * any instance of this object is subject to change without the user knowing it - * and so should not be stored anywhere outside of the method providing these - * instances. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class FlyweightCell { - public static final String COLSPAN_ATTR = "colSpan"; - - private final int column; - private final FlyweightRow row; - - private TableCellElement element = null; - private CellIterator currentIterator = null; - - public FlyweightCell(final FlyweightRow row, final int column) { - this.row = row; - this.column = column; - } - - /** - * Returns the row index of the cell - */ - public int getRow() { - assertSetup(); - return row.getRow(); - } - - /** - * Returns the column index of the cell - */ - public int getColumn() { - assertSetup(); - return column; - } - - /** - * Returns the element of the cell. Can be either a TD element - * or a TH element. - */ - public TableCellElement getElement() { - assertSetup(); - return element; - } - - /** - * Return the colspan attribute of the element of the cell. - */ - public int getColSpan() { - assertSetup(); - return element.getPropertyInt(COLSPAN_ATTR); - } - - /** - * Sets the DOM element for this FlyweightCell, either a TD or - * a TH. It is the caller's responsibility to actually insert - * the given element to the document when needed. - * - * @param element - * the element corresponding to this cell, cannot be null - */ - public void setElement(TableCellElement element) { - assert element != null; - assertSetup(); - this.element = element; - } - - void setup(final CellIterator iterator) { - currentIterator = iterator; - - if (iterator.areCellsAttached()) { - final TableCellElement e = row.getElement().getCells() - .getItem(column); - - assert e != null : "Cell " + column + " for logical row " - + row.getRow() + " doesn't exist in the DOM!"; - - e.setPropertyInt(COLSPAN_ATTR, 1); - if (row.getColumnWidth(column) >= 0) { - e.getStyle().setWidth(row.getColumnWidth(column), Unit.PX); - } - e.getStyle().clearDisplay(); - setElement(e); - } - } - - /** - * Tear down the state of the Cell. - *

- * This is an internal check method, to prevent retrieving uninitialized - * data by calling {@link #getRow()}, {@link #getColumn()} or - * {@link #getElement()} at an improper time. - *

- * This should only be used with asserts (" - * assert flyweightCell.teardown() ") so that the code is never - * run when asserts aren't enabled. - * - * @return always true - * @see FlyweightRow#teardown() - */ - boolean teardown() { - currentIterator = null; - element = null; - return true; - } - - /** - * Asserts that the flyweight cell has properly been set up before trying to - * access any of its data. - */ - private void assertSetup() { - assert currentIterator != null : "FlyweightCell was not properly " - + "initialized. This is either a bug in Grid/Escalator " - + "or a Cell reference has been stored and reused " - + "inappropriately."; - } - - public void setColSpan(final int numberOfCells) { - if (numberOfCells < 1) { - throw new IllegalArgumentException( - "Number of cells should be more than 0"); - } - - /*- - * This will default to 1 if unset, as per DOM specifications: - * http://www.w3.org/TR/html5/tabular-data.html#attributes-common-to-td-and-th-elements - */ - final int prevColSpan = getElement().getPropertyInt(COLSPAN_ATTR); - if (numberOfCells == 1 && prevColSpan == 1) { - return; - } - - getElement().setPropertyInt(COLSPAN_ATTR, numberOfCells); - adjustCellWidthForSpan(numberOfCells); - hideOrRevealAdjacentCellElements(numberOfCells, prevColSpan); - currentIterator.setSkipNext(numberOfCells - 1); - } - - private void adjustCellWidthForSpan(final int numberOfCells) { - final int cellsToTheRight = currentIterator.rawPeekNext( - numberOfCells - 1).size(); - - final double selfWidth = row.getColumnWidth(column); - double widthsOfColumnsToTheRight = 0; - for (int i = 0; i < cellsToTheRight; i++) { - widthsOfColumnsToTheRight += row.getColumnWidth(column + i + 1); - } - getElement().getStyle().setWidth(selfWidth + widthsOfColumnsToTheRight, - Unit.PX); - } - - private void hideOrRevealAdjacentCellElements(final int numberOfCells, - final int prevColSpan) { - final int affectedCellsNumber = Math.max(prevColSpan, numberOfCells); - final List affectedCells = currentIterator - .rawPeekNext(affectedCellsNumber - 1); - if (prevColSpan < numberOfCells) { - for (int i = 0; i < affectedCells.size(); i++) { - affectedCells.get(prevColSpan + i - 1).getElement().getStyle() - .setDisplay(Display.NONE); - } - } else if (prevColSpan > numberOfCells) { - for (int i = 0; i < affectedCells.size(); i++) { - affectedCells.get(numberOfCells + i - 1).getElement() - .getStyle().clearDisplay(); - } - } - } -} diff --git a/client/src/com/vaadin/client/widget/escalator/FlyweightRow.java b/client/src/com/vaadin/client/widget/escalator/FlyweightRow.java deleted file mode 100644 index 8628adb05f..0000000000 --- a/client/src/com/vaadin/client/widget/escalator/FlyweightRow.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.escalator; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -import com.google.gwt.dom.client.TableRowElement; - -/** - * An internal implementation of the {@link Row} interface. - *

- * There is only one instance per Escalator. This is designed to be re-used when - * rendering rows. - * - * @since 7.4 - * @author Vaadin Ltd - * @see Escalator.AbstractRowContainer#refreshRow(Node, int) - */ -public class FlyweightRow implements Row { - - static class CellIterator implements Iterator { - /** A defensive copy of the cells in the current row. */ - private final ArrayList cells; - private final boolean cellsAttached; - private int cursor = 0; - private int skipNext = 0; - - /** - * Creates a new iterator of attached flyweight cells. A cell is - * attached if it has a corresponding {@link FlyweightCell#getElement() - * DOM element} attached to the row element. - * - * @param cells - * the collection of cells to iterate - */ - public static CellIterator attached( - final Collection cells) { - return new CellIterator(cells, true); - } - - /** - * Creates a new iterator of unattached flyweight cells. A cell is - * unattached if it does not have a corresponding - * {@link FlyweightCell#getElement() DOM element} attached to the row - * element. - * - * @param cells - * the collection of cells to iterate - */ - public static CellIterator unattached( - final Collection cells) { - return new CellIterator(cells, false); - } - - private CellIterator(final Collection cells, - final boolean attached) { - this.cells = new ArrayList(cells); - cellsAttached = attached; - } - - @Override - public boolean hasNext() { - return cursor + skipNext < cells.size(); - } - - @Override - public FlyweightCell next() { - // if we needed to skip some cells since the last invocation. - for (int i = 0; i < skipNext; i++) { - cells.remove(cursor); - } - skipNext = 0; - - final FlyweightCell cell = cells.get(cursor++); - cell.setup(this); - return cell; - } - - @Override - public void remove() { - throw new UnsupportedOperationException( - "Cannot remove cells via iterator"); - } - - /** - * Sets the number of cells to skip when {@link #next()} is called the - * next time. Cell hiding is also handled eagerly in this method. - * - * @param colspan - * the number of cells to skip on next invocation of - * {@link #next()} - */ - public void setSkipNext(final int colspan) { - assert colspan > 0 : "Number of cells didn't make sense: " - + colspan; - skipNext = colspan; - } - - /** - * Gets the next n cells in the iterator, ignoring any - * possibly spanned cells. - * - * @param n - * the number of next cells to retrieve - * @return A list of next n cells, or less if there aren't - * enough cells to retrieve - */ - public List rawPeekNext(final int n) { - final int from = Math.min(cursor, cells.size()); - final int to = Math.min(cursor + n, cells.size()); - List nextCells = cells.subList(from, to); - for (FlyweightCell cell : nextCells) { - cell.setup(this); - } - return nextCells; - } - - public boolean areCellsAttached() { - return cellsAttached; - } - } - - private static final int BLANK = Integer.MIN_VALUE; - - private int row; - private TableRowElement element; - private double[] columnWidths = null; - private final List cells = new ArrayList(); - - public void setup(final TableRowElement e, final int row, - double[] columnWidths) { - element = e; - this.row = row; - this.columnWidths = columnWidths; - } - - /** - * Tear down the state of the Row. - *

- * This is an internal check method, to prevent retrieving uninitialized - * data by calling {@link #getRow()}, {@link #getElement()} or - * {@link #getCells()} at an improper time. - *

- * This should only be used with asserts (" - * assert flyweightRow.teardown() ") so that the code is never - * run when asserts aren't enabled. - * - * @return always true - */ - public boolean teardown() { - element = null; - row = BLANK; - columnWidths = null; - for (final FlyweightCell cell : cells) { - assert cell.teardown(); - } - return true; - } - - @Override - public int getRow() { - assertSetup(); - return row; - } - - @Override - public TableRowElement getElement() { - assertSetup(); - return element; - } - - public void addCells(final int index, final int numberOfColumns) { - for (int i = 0; i < numberOfColumns; i++) { - final int col = index + i; - cells.add(col, new FlyweightCell(this, col)); - } - updateRestOfCells(index + numberOfColumns); - } - - public void removeCells(final int index, final int numberOfColumns) { - cells.subList(index, index + numberOfColumns).clear(); - updateRestOfCells(index); - } - - private void updateRestOfCells(final int startPos) { - // update the column number for the cells to the right - for (int col = startPos; col < cells.size(); col++) { - cells.set(col, new FlyweightCell(this, col)); - } - } - - /** - * Returns flyweight cells for the client code to render. The cells get - * their associated {@link FlyweightCell#getElement() elements} from the row - * element. - *

- * Precondition: each cell has a corresponding element in the row - * - * @return an iterable of flyweight cells - * - * @see #setup(Element, int, int[]) - * @see #teardown() - */ - public Iterable getCells() { - return getCells(0, cells.size()); - } - - /** - * Returns a subrange of flyweight cells for the client code to render. The - * cells get their associated {@link FlyweightCell#getElement() elements} - * from the row element. - *

- * Precondition: each cell has a corresponding element in the row - * - * @param offset - * the index of the first cell to return - * @param numberOfCells - * the number of cells to return - * @return an iterable of flyweight cells - */ - public Iterable getCells(final int offset, - final int numberOfCells) { - assertSetup(); - assert offset >= 0 && offset + numberOfCells <= cells.size() : "Invalid range of cells"; - return new Iterable() { - @Override - public Iterator iterator() { - return CellIterator.attached(cells.subList(offset, offset - + numberOfCells)); - } - }; - } - - /** - * Returns a subrange of unattached flyweight cells. Unattached cells do not - * have {@link FlyweightCell#getElement() elements} associated. Note that - * FlyweightRow does not keep track of whether cells in actuality have - * corresponding DOM elements or not; it is the caller's responsibility to - * invoke this method with correct parameters. - *

- * Precondition: the range [offset, offset + numberOfCells) must be valid - * - * @param offset - * the index of the first cell to return - * @param numberOfCells - * the number of cells to return - * @return an iterable of flyweight cells - */ - public Iterable getUnattachedCells(final int offset, - final int numberOfCells) { - assertSetup(); - assert offset >= 0 && offset + numberOfCells <= cells.size() : "Invalid range of cells"; - return new Iterable() { - @Override - public Iterator iterator() { - return CellIterator.unattached(cells.subList(offset, offset - + numberOfCells)); - } - }; - } - - /** - * Asserts that the flyweight row has properly been set up before trying to - * access any of its data. - */ - private void assertSetup() { - assert element != null && row != BLANK && columnWidths != null : "Flyweight row was not " - + "properly initialized. Make sure the setup-method is " - + "called before retrieving data. This is either a bug " - + "in Escalator, or the instance of the flyweight row " - + "has been stored and accessed."; - } - - double getColumnWidth(int column) { - assertSetup(); - return columnWidths[column]; - } -} diff --git a/client/src/com/vaadin/client/widget/escalator/PositionFunction.java b/client/src/com/vaadin/client/widget/escalator/PositionFunction.java deleted file mode 100644 index 929f27df37..0000000000 --- a/client/src/com/vaadin/client/widget/escalator/PositionFunction.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.widget.escalator; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Unit; - -/** - * A functional interface that can be used for positioning elements in the DOM. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface PositionFunction { - /** - * A position function using "transform: translate3d(x,y,z)" to position - * elements in the DOM. - */ - public static class Translate3DPosition implements PositionFunction { - @Override - public void set(Element e, double x, double y) { - e.getStyle().setProperty("transform", - "translate3d(" + x + "px, " + y + "px, 0)"); - } - - @Override - public void reset(Element e) { - e.getStyle().clearProperty("transform"); - } - } - - /** - * A position function using "transform: translate(x,y)" to position - * elements in the DOM. - */ - public static class TranslatePosition implements PositionFunction { - @Override - public void set(Element e, double x, double y) { - e.getStyle().setProperty("transform", - "translate(" + x + "px," + y + "px)"); - } - - @Override - public void reset(Element e) { - e.getStyle().clearProperty("transform"); - } - } - - /** - * A position function using "-webkit-transform: translate3d(x,y,z)" to - * position elements in the DOM. - */ - public static class WebkitTranslate3DPosition implements PositionFunction { - @Override - public void set(Element e, double x, double y) { - e.getStyle().setProperty("webkitTransform", - "translate3d(" + x + "px," + y + "px,0)"); - } - - @Override - public void reset(Element e) { - e.getStyle().clearProperty("webkitTransform"); - } - } - - /** - * A position function using "left: x" and "top: y" to position elements in - * the DOM. - */ - public static class AbsolutePosition implements PositionFunction { - @Override - public void set(Element e, double x, double y) { - e.getStyle().setLeft(x, Unit.PX); - e.getStyle().setTop(y, Unit.PX); - } - - @Override - public void reset(Element e) { - e.getStyle().clearLeft(); - e.getStyle().clearTop(); - } - } - - /** - * Position an element in an (x,y) coordinate system in the DOM. - * - * @param e - * the element to position. Never null. - * @param x - * the x coordinate, in pixels - * @param y - * the y coordinate, in pixels - */ - void set(Element e, double x, double y); - - /** - * Resets any previously applied positioning, clearing the used style - * attributes. - * - * @param e - * the element for which to reset the positioning - */ - void reset(Element e); -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/escalator/Row.java b/client/src/com/vaadin/client/widget/escalator/Row.java deleted file mode 100644 index fa89853120..0000000000 --- a/client/src/com/vaadin/client/widget/escalator/Row.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.widget.escalator; - -import com.google.gwt.dom.client.TableRowElement; - -/** - * A representation of a row in an {@link Escalator}. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface Row { - /** - * Gets the row index. - * - * @return the row index - */ - public int getRow(); - - /** - * Gets the root element for this row. - *

- * The {@link EscalatorUpdater} may update the class names of the element - * and add inline styles, but may not modify the contained DOM structure. - *

- * If you wish to modify the cells within this row element, access them via - * the List<{@link Cell}> objects passed in to - * {@code EscalatorUpdater.updateCells(Row, List)} - * - * @return the root element of the row - */ - public TableRowElement getElement(); -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/escalator/RowContainer.java b/client/src/com/vaadin/client/widget/escalator/RowContainer.java deleted file mode 100644 index abab25046c..0000000000 --- a/client/src/com/vaadin/client/widget/escalator/RowContainer.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.widget.escalator; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.TableRowElement; -import com.google.gwt.dom.client.TableSectionElement; - -/** - * A representation of the rows in each of the sections (header, body and - * footer) in an {@link com.vaadin.client.widgets.Escalator}. - * - * @since 7.4 - * @author Vaadin Ltd - * @see com.vaadin.client.widgets.Escalator#getHeader() - * @see com.vaadin.client.widgets.Escalator#getBody() - * @see com.vaadin.client.widgets.Escalator#getFooter() - * @see SpacerContainer - */ -public interface RowContainer { - - /** - * The row container for the body section in an - * {@link com.vaadin.client.widgets.Escalator}. - *

- * The body section can contain both rows and spacers. - * - * @since 7.5.0 - * @author Vaadin Ltd - * @see com.vaadin.client.widgets.Escalator#getBody() - */ - public interface BodyRowContainer extends RowContainer { - - /** - * Marks a spacer and its height. - *

- * If a spacer is already registered with the given row index, that - * spacer will be updated with the given height. - *

- * Note: The row index for a spacer will change if rows are - * inserted or removed above the current position. Spacers will also be - * removed alongside their associated rows - * - * @param rowIndex - * the row index for the spacer to modify. The affected - * spacer is underneath the given index. Use -1 to insert a - * spacer before the first row - * @param height - * the pixel height of the spacer. If {@code height} is - * negative, the affected spacer (if exists) will be removed - * @throws IllegalArgumentException - * if {@code rowIndex} is not a valid row index - * @see #insertRows(int, int) - * @see #removeRows(int, int) - */ - void setSpacer(int rowIndex, double height) - throws IllegalArgumentException; - - /** - * Sets a new spacer updater. - *

- * Spacers that are currently visible will be updated, i.e. - * {@link SpacerUpdater#destroy(Spacer) destroyed} with the previous - * one, and {@link SpacerUpdater#init(Spacer) initialized} with the new - * one. - * - * @param spacerUpdater - * the new spacer updater - * @throws IllegalArgumentException - * if {@code spacerUpdater} is {@code null} - */ - void setSpacerUpdater(SpacerUpdater spacerUpdater) - throws IllegalArgumentException; - - /** - * Gets the spacer updater currently in use. - *

- * {@link SpacerUpdater#NULL} is the default. - * - * @return the spacer updater currently in use. Never null - */ - SpacerUpdater getSpacerUpdater(); - - /** - * {@inheritDoc} - *

- * Any spacers underneath {@code index} will be offset and "pushed" - * down. This also modifies the row index they are associated with. - */ - @Override - public void insertRows(int index, int numberOfRows) - throws IndexOutOfBoundsException, IllegalArgumentException; - - /** - * {@inheritDoc} - *

- * Any spacers underneath {@code index} will be offset and "pulled" up. - * This also modifies the row index they are associated with. Any - * spacers in the removed range will also be closed and removed. - */ - @Override - public void removeRows(int index, int numberOfRows) - throws IndexOutOfBoundsException, IllegalArgumentException; - } - - /** - * An arbitrary pixel height of a row, before any autodetection for the row - * height has been made. - * */ - public static final double INITIAL_DEFAULT_ROW_HEIGHT = 20; - - /** - * Returns the current {@link EscalatorUpdater} used to render cells. - * - * @return the current escalator updater - */ - public EscalatorUpdater getEscalatorUpdater(); - - /** - * Sets the {@link EscalatorUpdater} to use when displaying data in the - * escalator. - * - * @param escalatorUpdater - * the escalator updater to use to render cells. May not be - * null - * @throws IllegalArgumentException - * if {@code cellRenderer} is null - * @see EscalatorUpdater#NULL - */ - public void setEscalatorUpdater(EscalatorUpdater escalatorUpdater) - throws IllegalArgumentException; - - /** - * Removes rows at a certain index in the current row container. - * - * @param index - * the index of the first row to be removed - * @param numberOfRows - * the number of rows to remove, starting from the index - * @throws IndexOutOfBoundsException - * if any integer number in the range - * [index..(index+numberOfRows)] is not an existing - * row index - * @throws IllegalArgumentException - * if {@code numberOfRows} is less than 1. - */ - public void removeRows(int index, int numberOfRows) - throws IndexOutOfBoundsException, IllegalArgumentException; - - /** - * Adds rows at a certain index in this row container. - *

- * The new rows will be inserted between the row at the index, and the row - * before (an index of 0 means that the rows are inserted at the beginning). - * Therefore, the rows currently at the index and afterwards will be moved - * downwards. - *

- * The contents of the inserted rows will subsequently be queried from the - * escalator updater. - *

- * Note: Only the contents of the inserted rows will be rendered. - * If inserting new rows affects the contents of existing rows, - * {@link #refreshRows(int, int)} needs to be called for those rows - * separately. - * - * @param index - * the index of the row before which new rows are inserted, or - * {@link #getRowCount()} to add rows at the end - * @param numberOfRows - * the number of rows to insert after the index - * @see #setEscalatorUpdater(EscalatorUpdater) - * @throws IndexOutOfBoundsException - * if index is not an integer in the range - * [0..{@link #getRowCount()}] - * @throws IllegalArgumentException - * if {@code numberOfRows} is less than 1. - */ - public void insertRows(int index, int numberOfRows) - throws IndexOutOfBoundsException, IllegalArgumentException; - - /** - * Refreshes a range of rows in the current row container. - *

- * The data for the refreshed rows is queried from the current cell - * renderer. - * - * @param index - * the index of the first row that will be updated - * @param numberOfRows - * the number of rows to update, starting from the index - * @see #setEscalatorUpdater(EscalatorUpdater) - * @throws IndexOutOfBoundsException - * if any integer number in the range - * [index..(index+numberOfColumns)] is not an - * existing column index. - * @throws IllegalArgumentException - * if {@code numberOfRows} is less than 1. - */ - public void refreshRows(int index, int numberOfRows) - throws IndexOutOfBoundsException, IllegalArgumentException; - - /** - * Gets the number of rows in the current row container. - * - * @return the number of rows in the current row container - */ - public int getRowCount(); - - /** - * The default height of the rows in this RowContainer. - * - * @param px - * the default height in pixels of the rows in this RowContainer - * @throws IllegalArgumentException - * if px < 1 - * @see #getDefaultRowHeight() - */ - public void setDefaultRowHeight(double px) throws IllegalArgumentException; - - /** - * Returns the default height of the rows in this RowContainer. - *

- * This value will be equal to {@link #INITIAL_DEFAULT_ROW_HEIGHT} if the - * {@link Escalator} has not yet had a chance to autodetect the row height, - * or no explicit value has yet given via {@link #setDefaultRowHeight(int)} - * - * @return the default height of the rows in this RowContainer, in pixels - * @see #setDefaultRowHeight(int) - */ - public double getDefaultRowHeight(); - - /** - * Returns the cell object which contains information about the cell the - * element is in. - * - * @param element - * The element to get the cell for. If element is not present in - * row container then null is returned. - * - * @return the cell of the element, or null if element is not - * present in the {@link RowContainer}. - */ - public Cell getCell(Element element); - - /** - * Gets the row element with given logical index. For lazy loaded containers - * such as Escalators BodyRowContainer visibility should be checked before - * calling this function. See {@link Escalator#getVisibleRowRange()}. - * - * @param index - * the logical index of the element to retrieve - * @return the element at position {@code index} - * @throws IndexOutOfBoundsException - * if {@code index} is not valid within container - * @throws IllegalStateException - * if {@code index} is currently not available in the DOM - */ - public TableRowElement getRowElement(int index) - throws IndexOutOfBoundsException, IllegalStateException; - - /** - * Returns the root element of RowContainer - * - * @return RowContainer root element - */ - public TableSectionElement getElement(); -} diff --git a/client/src/com/vaadin/client/widget/escalator/RowVisibilityChangeEvent.java b/client/src/com/vaadin/client/widget/escalator/RowVisibilityChangeEvent.java deleted file mode 100644 index 5f1531e378..0000000000 --- a/client/src/com/vaadin/client/widget/escalator/RowVisibilityChangeEvent.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.widget.escalator; - -import com.google.gwt.event.shared.GwtEvent; -import com.vaadin.shared.ui.grid.Range; - -/** - * Event fired when the range of visible rows changes e.g. because of scrolling. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class RowVisibilityChangeEvent extends - GwtEvent { - /** - * The type of this event. - */ - public static final Type TYPE = new Type(); - - private final Range visibleRows; - - /** - * Creates a new row visibility change event - * - * @param firstVisibleRow - * the index of the first visible row - * @param visibleRowCount - * the number of visible rows - */ - public RowVisibilityChangeEvent(int firstVisibleRow, int visibleRowCount) { - visibleRows = Range.withLength(firstVisibleRow, visibleRowCount); - } - - /** - * Gets the index of the first row that is at least partially visible. - * - * @return the index of the first visible row - */ - public int getFirstVisibleRow() { - return visibleRows.getStart(); - } - - /** - * Gets the number of at least partially visible rows. - * - * @return the number of visible rows - */ - public int getVisibleRowCount() { - return visibleRows.length(); - } - - /** - * Gets the range of visible rows. - * - * @since 7.6 - * @return the visible rows - */ - public Range getVisibleRowRange() { - return visibleRows; - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.event.shared.GwtEvent#getAssociatedType() - */ - @Override - public Type getAssociatedType() { - return TYPE; - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.shared.GwtEvent#dispatch(com.google.gwt.event.shared - * .EventHandler) - */ - @Override - protected void dispatch(RowVisibilityChangeHandler handler) { - handler.onRowVisibilityChange(this); - } - -} diff --git a/client/src/com/vaadin/client/widget/escalator/RowVisibilityChangeHandler.java b/client/src/com/vaadin/client/widget/escalator/RowVisibilityChangeHandler.java deleted file mode 100644 index 80a30184c0..0000000000 --- a/client/src/com/vaadin/client/widget/escalator/RowVisibilityChangeHandler.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.widget.escalator; - -import com.google.gwt.event.shared.EventHandler; - -/** - * Event handler that gets notified when the range of visible rows changes e.g. - * because of scrolling. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface RowVisibilityChangeHandler extends EventHandler { - - /** - * Called when the range of visible rows changes e.g. because of scrolling. - * - * @param event - * the row visibility change event describing the change - */ - void onRowVisibilityChange(RowVisibilityChangeEvent event); - -} diff --git a/client/src/com/vaadin/client/widget/escalator/ScrollbarBundle.java b/client/src/com/vaadin/client/widget/escalator/ScrollbarBundle.java deleted file mode 100644 index 958029889d..0000000000 --- a/client/src/com/vaadin/client/widget/escalator/ScrollbarBundle.java +++ /dev/null @@ -1,866 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.widget.escalator; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.Style.Visibility; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.GwtEvent; -import com.google.gwt.event.shared.HandlerManager; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.EventListener; -import com.google.gwt.user.client.Timer; -import com.vaadin.client.DeferredWorker; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.widget.grid.events.ScrollEvent; -import com.vaadin.client.widget.grid.events.ScrollHandler; - -/** - * An element-like bundle representing a configurable and visual scrollbar in - * one axis. - * - * @since 7.4 - * @author Vaadin Ltd - * @see VerticalScrollbarBundle - * @see HorizontalScrollbarBundle - */ -public abstract class ScrollbarBundle implements DeferredWorker { - - private class ScrollEventFirer { - private final ScheduledCommand fireEventCommand = new ScheduledCommand() { - @Override - public void execute() { - - /* - * Some kind of native-scroll-event related asynchronous problem - * occurs here (at least on desktops) where the internal - * bookkeeping isn't up to date with the real scroll position. - * The weird thing is, that happens only once, and if you drag - * scrollbar fast enough. After it has failed once, it never - * fails again. - * - * Theory: the user drags the scrollbar, and this command is - * executed before the browser has a chance to fire a scroll - * event (which normally would correct this situation). This - * would explain why slow scrolling doesn't trigger the problem, - * while fast scrolling does. - * - * To make absolutely sure that we have the latest scroll - * position, let's update the internal value. - * - * This might lead to a slight performance hit (on my computer - * it was never more than 3ms on either of Chrome 38 or Firefox - * 31). It also _slightly_ counteracts the purpose of the - * internal bookkeeping. But since getScrollPos is called 3 - * times (on one direction) per scroll loop, it's still better - * to have take this small penalty than removing it altogether. - */ - updateScrollPosFromDom(); - - getHandlerManager().fireEvent(new ScrollEvent()); - isBeingFired = false; - } - }; - - private boolean isBeingFired; - - public void scheduleEvent() { - if (!isBeingFired) { - /* - * We'll gather all the scroll events, and only fire once, once - * everything has calmed down. - */ - Scheduler.get().scheduleDeferred(fireEventCommand); - isBeingFired = true; - } - } - } - - /** - * The orientation of the scrollbar. - */ - public enum Direction { - VERTICAL, HORIZONTAL; - } - - private class TemporaryResizer { - private static final int TEMPORARY_RESIZE_DELAY = 1000; - - private final Timer timer = new Timer() { - @Override - public void run() { - internalSetScrollbarThickness(1); - root.getStyle().setVisibility(Visibility.HIDDEN); - } - }; - - public void show() { - internalSetScrollbarThickness(OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX); - root.getStyle().setVisibility(Visibility.VISIBLE); - timer.schedule(TEMPORARY_RESIZE_DELAY); - } - } - - /** - * A means to listen to when the scrollbar handle in a - * {@link ScrollbarBundle} either appears or is removed. - */ - public interface VisibilityHandler extends EventHandler { - /** - * This method is called whenever the scrollbar handle's visibility is - * changed in a {@link ScrollbarBundle}. - * - * @param event - * the {@link VisibilityChangeEvent} - */ - void visibilityChanged(VisibilityChangeEvent event); - } - - public static class VisibilityChangeEvent extends - GwtEvent { - public static final Type TYPE = new Type() { - @Override - public String toString() { - return "VisibilityChangeEvent"; - } - }; - - private final boolean isScrollerVisible; - - private VisibilityChangeEvent(boolean isScrollerVisible) { - this.isScrollerVisible = isScrollerVisible; - } - - /** - * Checks whether the scroll handle is currently visible or not - * - * @return true if the scroll handle is currently visible. - * false if not. - */ - public boolean isScrollerVisible() { - return isScrollerVisible; - } - - @Override - public Type getAssociatedType() { - return TYPE; - } - - @Override - protected void dispatch(VisibilityHandler handler) { - handler.visibilityChanged(this); - } - } - - /** - * The pixel size for OSX's invisible scrollbars. - *

- * Touch devices don't show a scrollbar at all, so the scrollbar size is - * irrelevant in their case. There doesn't seem to be any other popular - * platforms that has scrollbars similar to OSX. Thus, this behavior is - * tailored for OSX only, until additional platforms start behaving this - * way. - */ - private static final int OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX = 13; - - /** - * A representation of a single vertical scrollbar. - * - * @see VerticalScrollbarBundle#getElement() - */ - public final static class VerticalScrollbarBundle extends ScrollbarBundle { - - @Override - public void setStylePrimaryName(String primaryStyleName) { - super.setStylePrimaryName(primaryStyleName); - root.addClassName(primaryStyleName + "-scroller-vertical"); - } - - @Override - protected void internalSetScrollPos(int px) { - root.setScrollTop(px); - } - - @Override - protected int internalGetScrollPos() { - return root.getScrollTop(); - } - - @Override - protected void internalSetScrollSize(double px) { - scrollSizeElement.getStyle().setHeight(px, Unit.PX); - } - - @Override - protected String internalGetScrollSize() { - return scrollSizeElement.getStyle().getHeight(); - } - - @Override - protected void internalSetOffsetSize(double px) { - root.getStyle().setHeight(px, Unit.PX); - } - - @Override - public String internalGetOffsetSize() { - return root.getStyle().getHeight(); - } - - @Override - protected void internalSetScrollbarThickness(double px) { - root.getStyle().setPaddingRight(px, Unit.PX); - root.getStyle().setWidth(0, Unit.PX); - scrollSizeElement.getStyle().setWidth(px, Unit.PX); - } - - @Override - protected String internalGetScrollbarThickness() { - return scrollSizeElement.getStyle().getWidth(); - } - - @Override - protected void internalForceScrollbar(boolean enable) { - if (enable) { - root.getStyle().setOverflowY(Overflow.SCROLL); - } else { - root.getStyle().clearOverflowY(); - } - } - - @Override - public Direction getDirection() { - return Direction.VERTICAL; - } - } - - /** - * A representation of a single horizontal scrollbar. - * - * @see HorizontalScrollbarBundle#getElement() - */ - public final static class HorizontalScrollbarBundle extends ScrollbarBundle { - - @Override - public void setStylePrimaryName(String primaryStyleName) { - super.setStylePrimaryName(primaryStyleName); - root.addClassName(primaryStyleName + "-scroller-horizontal"); - } - - @Override - protected void internalSetScrollPos(int px) { - root.setScrollLeft(px); - } - - @Override - protected int internalGetScrollPos() { - return root.getScrollLeft(); - } - - @Override - protected void internalSetScrollSize(double px) { - scrollSizeElement.getStyle().setWidth(px, Unit.PX); - } - - @Override - protected String internalGetScrollSize() { - return scrollSizeElement.getStyle().getWidth(); - } - - @Override - protected void internalSetOffsetSize(double px) { - root.getStyle().setWidth(px, Unit.PX); - } - - @Override - public String internalGetOffsetSize() { - return root.getStyle().getWidth(); - } - - @Override - protected void internalSetScrollbarThickness(double px) { - root.getStyle().setPaddingBottom(px, Unit.PX); - root.getStyle().setHeight(0, Unit.PX); - scrollSizeElement.getStyle().setHeight(px, Unit.PX); - } - - @Override - protected String internalGetScrollbarThickness() { - return scrollSizeElement.getStyle().getHeight(); - } - - @Override - protected void internalForceScrollbar(boolean enable) { - if (enable) { - root.getStyle().setOverflowX(Overflow.SCROLL); - } else { - root.getStyle().clearOverflowX(); - } - } - - @Override - public Direction getDirection() { - return Direction.HORIZONTAL; - } - } - - protected final Element root = DOM.createDiv(); - protected final Element scrollSizeElement = DOM.createDiv(); - protected boolean isInvisibleScrollbar = false; - - private double scrollPos = 0; - private double maxScrollPos = 0; - - private boolean scrollHandleIsVisible = false; - - private boolean isLocked = false; - - /** @deprecated access via {@link #getHandlerManager()} instead. */ - @Deprecated - private HandlerManager handlerManager; - - private TemporaryResizer invisibleScrollbarTemporaryResizer = new TemporaryResizer(); - - private final ScrollEventFirer scrollEventFirer = new ScrollEventFirer(); - - private HandlerRegistration scrollSizeTemporaryScrollHandler; - private HandlerRegistration offsetSizeTemporaryScrollHandler; - - private ScrollbarBundle() { - root.appendChild(scrollSizeElement); - root.getStyle().setDisplay(Display.NONE); - root.setTabIndex(-1); - } - - protected abstract String internalGetScrollSize(); - - /** - * Sets the primary style name - * - * @param primaryStyleName - * The primary style name to use - */ - public void setStylePrimaryName(String primaryStyleName) { - root.setClassName(primaryStyleName + "-scroller"); - } - - /** - * Gets the root element of this scrollbar-composition. - * - * @return the root element - */ - public final Element getElement() { - return root; - } - - /** - * Modifies the scroll position of this scrollbar by a number of pixels. - *

- * Note: Even though {@code double} values are used, they are - * currently only used as integers as large {@code int} (or small but fast - * {@code long}). This means, all values are truncated to zero decimal - * places. - * - * @param delta - * the delta in pixels to change the scroll position by - */ - public final void setScrollPosByDelta(double delta) { - if (delta != 0) { - setScrollPos(getScrollPos() + delta); - } - } - - /** - * Modifies {@link #root root's} dimensions in the axis the scrollbar is - * representing. - * - * @param px - * the new size of {@link #root} in the dimension this scrollbar - * is representing - */ - protected abstract void internalSetOffsetSize(double px); - - /** - * Sets the length of the scrollbar. - * - * @param px - * the length of the scrollbar in pixels - */ - public final void setOffsetSize(final double px) { - - /* - * This needs to be made step-by-step because IE8 flat-out refuses to - * fire a scroll event when the scroll size becomes smaller than the - * offset size. All other browser need to suffer alongside. - */ - - boolean newOffsetSizeIsGreaterThanScrollSize = px > getScrollSize(); - boolean offsetSizeBecomesGreaterThanScrollSize = showsScrollHandle() - && newOffsetSizeIsGreaterThanScrollSize; - if (offsetSizeBecomesGreaterThanScrollSize && getScrollPos() != 0) { - // must be a field because Java insists. - offsetSizeTemporaryScrollHandler = addScrollHandler(new ScrollHandler() { - @Override - public void onScroll(ScrollEvent event) { - setOffsetSizeNow(px); - } - }); - setScrollPos(0); - } else { - setOffsetSizeNow(px); - } - } - - private void setOffsetSizeNow(double px) { - internalSetOffsetSize(Math.max(0, px)); - recalculateMaxScrollPos(); - forceScrollbar(showsScrollHandle()); - fireVisibilityChangeIfNeeded(); - if (offsetSizeTemporaryScrollHandler != null) { - offsetSizeTemporaryScrollHandler.removeHandler(); - offsetSizeTemporaryScrollHandler = null; - } - } - - /** - * Force the scrollbar to be visible with CSS. In practice, this means to - * set either overflow-x or overflow-y to " - * scroll" in the scrollbar's direction. - *

- * This is an IE8 workaround, since it doesn't always show scrollbars with - * overflow: auto enabled. - */ - protected void forceScrollbar(boolean enable) { - if (enable) { - root.getStyle().clearDisplay(); - } else { - root.getStyle().setDisplay(Display.NONE); - } - internalForceScrollbar(enable); - } - - protected abstract void internalForceScrollbar(boolean enable); - - /** - * Gets the length of the scrollbar - * - * @return the length of the scrollbar in pixels - */ - public double getOffsetSize() { - return parseCssDimensionToPixels(internalGetOffsetSize()); - } - - public abstract String internalGetOffsetSize(); - - /** - * Sets the scroll position of the scrollbar in the axis the scrollbar is - * representing. - *

- * Note: Even though {@code double} values are used, they are - * currently only used as integers as large {@code int} (or small but fast - * {@code long}). This means, all values are truncated to zero decimal - * places. - * - * @param px - * the new scroll position in pixels - */ - public final void setScrollPos(double px) { - if (isLocked()) { - return; - } - - double oldScrollPos = scrollPos; - scrollPos = Math.max(0, Math.min(maxScrollPos, truncate(px))); - - if (!WidgetUtil.pixelValuesEqual(oldScrollPos, scrollPos)) { - if (isInvisibleScrollbar) { - invisibleScrollbarTemporaryResizer.show(); - } - - /* - * This is where the value needs to be converted into an integer no - * matter how we flip it, since GWT expects an integer value. - * There's no point making a JSNI method that accepts doubles as the - * scroll position, since the browsers themselves don't support such - * large numbers (as of today, 25.3.2014). This double-ranged is - * only facilitating future virtual scrollbars. - */ - internalSetScrollPos(toInt32(scrollPos)); - } - } - - /** - * Should be called whenever this bundle is attached to the DOM (typically, - * from the onLoad of the containing widget). Used to ensure the DOM scroll - * position is maintained when detaching and reattaching the bundle. - * - * @since 7.4.1 - */ - public void onLoad() { - internalSetScrollPos(toInt32(scrollPos)); - } - - /** - * Truncates a double such that no decimal places are retained. - *

- * E.g. {@code trunc(2.3d) == 2.0d} and {@code trunc(-2.3d) == -2.0d}. - * - * @param num - * the double value to be truncated - * @return the {@code num} value without any decimal digits - */ - private static double truncate(double num) { - if (num > 0) { - return Math.floor(num); - } else { - return Math.ceil(num); - } - } - - /** - * Modifies the element's scroll position (scrollTop or scrollLeft). - *

- * Note: The parameter here is a type of integer (instead of a - * double) by design. The browsers internally convert all double values into - * an integer value. To make this fact explicit, this API has chosen to - * force integers already at this level. - * - * @param px - * integer pixel value to scroll to - */ - protected abstract void internalSetScrollPos(int px); - - /** - * Gets the scroll position of the scrollbar in the axis the scrollbar is - * representing. - * - * @return the new scroll position in pixels - */ - public final double getScrollPos() { - assert internalGetScrollPos() == toInt32(scrollPos) : "calculated scroll position (" - + toInt32(scrollPos) - + ") did not match the DOM element scroll position (" - + internalGetScrollPos() + ")"; - return scrollPos; - } - - /** - * Retrieves the element's scroll position (scrollTop or scrollLeft). - *

- * Note: The parameter here is a type of integer (instead of a - * double) by design. The browsers internally convert all double values into - * an integer value. To make this fact explicit, this API has chosen to - * force integers already at this level. - * - * @return integer pixel value of the scroll position - */ - protected abstract int internalGetScrollPos(); - - /** - * Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in - * such a way that the scrollbar is able to scroll a certain number of - * pixels in the axis it is representing. - * - * @param px - * the new size of {@link #scrollSizeElement} in the dimension - * this scrollbar is representing - */ - protected abstract void internalSetScrollSize(double px); - - /** - * Sets the amount of pixels the scrollbar needs to be able to scroll - * through. - * - * @param px - * the number of pixels the scrollbar should be able to scroll - * through - */ - public final void setScrollSize(final double px) { - - /* - * This needs to be made step-by-step because IE8 flat-out refuses to - * fire a scroll event when the scroll size becomes smaller than the - * offset size. All other browser need to suffer alongside. - */ - - boolean newScrollSizeIsSmallerThanOffsetSize = px <= getOffsetSize(); - boolean scrollSizeBecomesSmallerThanOffsetSize = showsScrollHandle() - && newScrollSizeIsSmallerThanOffsetSize; - if (scrollSizeBecomesSmallerThanOffsetSize && getScrollPos() != 0) { - // must be a field because Java insists. - scrollSizeTemporaryScrollHandler = addScrollHandler(new ScrollHandler() { - @Override - public void onScroll(ScrollEvent event) { - setScrollSizeNow(px); - } - }); - setScrollPos(0); - } else { - setScrollSizeNow(px); - } - } - - private void setScrollSizeNow(double px) { - internalSetScrollSize(Math.max(0, px)); - recalculateMaxScrollPos(); - forceScrollbar(showsScrollHandle()); - fireVisibilityChangeIfNeeded(); - if (scrollSizeTemporaryScrollHandler != null) { - scrollSizeTemporaryScrollHandler.removeHandler(); - scrollSizeTemporaryScrollHandler = null; - } - } - - /** - * Gets the amount of pixels the scrollbar needs to be able to scroll - * through. - * - * @return the number of pixels the scrollbar should be able to scroll - * through - */ - public double getScrollSize() { - return parseCssDimensionToPixels(internalGetScrollSize()); - } - - /** - * Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in the - * opposite axis to what the scrollbar is representing. - * - * @param px - * the dimension that {@link #scrollSizeElement} should take in - * the opposite axis to what the scrollbar is representing - */ - protected abstract void internalSetScrollbarThickness(double px); - - /** - * Sets the scrollbar's thickness. - *

- * If the thickness is set to 0, the scrollbar will be treated as an - * "invisible" scrollbar. This means, the DOM structure will be given a - * non-zero size, but {@link #getScrollbarThickness()} will still return the - * value 0. - * - * @param px - * the scrollbar's thickness in pixels - */ - public final void setScrollbarThickness(double px) { - isInvisibleScrollbar = (px == 0); - - if (isInvisibleScrollbar) { - Event.sinkEvents(root, Event.ONSCROLL); - Event.setEventListener(root, new EventListener() { - @Override - public void onBrowserEvent(Event event) { - invisibleScrollbarTemporaryResizer.show(); - } - }); - root.getStyle().setVisibility(Visibility.HIDDEN); - } else { - Event.sinkEvents(root, 0); - Event.setEventListener(root, null); - root.getStyle().clearVisibility(); - } - - internalSetScrollbarThickness(Math.max(1d, px)); - } - - /** - * Gets the scrollbar's thickness as defined in the DOM. - * - * @return the scrollbar's thickness as defined in the DOM, in pixels - */ - protected abstract String internalGetScrollbarThickness(); - - /** - * Gets the scrollbar's thickness. - *

- * This value will differ from the value in the DOM, if the thickness was - * set to 0 with {@link #setScrollbarThickness(double)}, as the scrollbar is - * then treated as "invisible." - * - * @return the scrollbar's thickness in pixels - */ - public final double getScrollbarThickness() { - if (!isInvisibleScrollbar) { - return parseCssDimensionToPixels(internalGetScrollbarThickness()); - } else { - return 0; - } - } - - /** - * Checks whether the scrollbar's handle is visible. - *

- * In other words, this method checks whether the contents is larger than - * can visually fit in the element. - * - * @return true iff the scrollbar's handle is visible - */ - public boolean showsScrollHandle() { - return getScrollSize() - getOffsetSize() > WidgetUtil.PIXEL_EPSILON; - } - - public void recalculateMaxScrollPos() { - double scrollSize = getScrollSize(); - double offsetSize = getOffsetSize(); - maxScrollPos = Math.max(0, scrollSize - offsetSize); - - // make sure that the correct max scroll position is maintained. - setScrollPos(scrollPos); - } - - /** - * This is a method that JSNI can call to synchronize the object state from - * the DOM. - */ - private final void updateScrollPosFromDom() { - - /* - * TODO: this method probably shouldn't be called from Escalator's JSNI, - * but probably could be handled internally by this listening to its own - * element. Would clean up the code quite a bit. Needs further - * investigation. - */ - - int newScrollPos = internalGetScrollPos(); - if (!isLocked()) { - scrollPos = newScrollPos; - scrollEventFirer.scheduleEvent(); - } else if (scrollPos != newScrollPos) { - // we need to actually undo the setting of the scroll. - internalSetScrollPos(toInt32(scrollPos)); - } - } - - protected HandlerManager getHandlerManager() { - if (handlerManager == null) { - handlerManager = new HandlerManager(this); - } - return handlerManager; - } - - /** - * Adds handler for the scrollbar handle visibility. - * - * @param handler - * the {@link VisibilityHandler} to add - * @return {@link HandlerRegistration} used to remove the handler - */ - public HandlerRegistration addVisibilityHandler( - final VisibilityHandler handler) { - return getHandlerManager().addHandler(VisibilityChangeEvent.TYPE, - handler); - } - - private void fireVisibilityChangeIfNeeded() { - final boolean oldHandleIsVisible = scrollHandleIsVisible; - scrollHandleIsVisible = showsScrollHandle(); - if (oldHandleIsVisible != scrollHandleIsVisible) { - final VisibilityChangeEvent event = new VisibilityChangeEvent( - scrollHandleIsVisible); - getHandlerManager().fireEvent(event); - } - } - - /** - * Converts a double into an integer by JavaScript's terms. - *

- * Implementation copied from {@link Element#toInt32(double)}. - * - * @param val - * the double value to convert into an integer - * @return the double value converted to an integer - */ - private static native int toInt32(double val) - /*-{ - return val | 0; - }-*/; - - /** - * Locks or unlocks the scrollbar bundle. - *

- * A locked scrollbar bundle will refuse to scroll, both programmatically - * and via user-triggered events. - * - * @param isLocked - * true to lock, false to unlock - */ - public void setLocked(boolean isLocked) { - this.isLocked = isLocked; - } - - /** - * Checks whether the scrollbar bundle is locked or not. - * - * @return true iff the scrollbar bundle is locked - */ - public boolean isLocked() { - return isLocked; - } - - /** - * Returns the scroll direction of this scrollbar bundle. - * - * @return the scroll direction of this scrollbar bundle - */ - public abstract Direction getDirection(); - - /** - * Adds a scroll handler to the scrollbar bundle. - * - * @param handler - * the handler to add - * @return the registration object for the handler registration - */ - public HandlerRegistration addScrollHandler(final ScrollHandler handler) { - return getHandlerManager().addHandler(ScrollEvent.TYPE, handler); - } - - private static double parseCssDimensionToPixels(String size) { - - /* - * Sizes of elements are calculated from CSS rather than - * element.getOffset*() because those values are 0 whenever display: - * none. Because we know that all elements have populated - * CSS-dimensions, it's better to do it that way. - * - * Another solution would be to make the elements visible while - * measuring and then re-hide them, but that would cause unnecessary - * reflows that would probably kill the performance dead. - */ - - if (size.isEmpty()) { - return 0; - } else { - assert size.endsWith("px") : "Can't parse CSS dimension \"" + size - + "\""; - return Double.parseDouble(size.substring(0, size.length() - 2)); - } - } - - @Override - public boolean isWorkPending() { - return scrollSizeTemporaryScrollHandler != null - || offsetSizeTemporaryScrollHandler != null; - } -} diff --git a/client/src/com/vaadin/client/widget/escalator/Spacer.java b/client/src/com/vaadin/client/widget/escalator/Spacer.java deleted file mode 100644 index 789a64a21e..0000000000 --- a/client/src/com/vaadin/client/widget/escalator/Spacer.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.escalator; - -import com.google.gwt.dom.client.Element; - -/** - * A representation of a spacer element in a - * {@link com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer}. - * - * @since 7.5.0 - * @author Vaadin Ltd - */ -public interface Spacer { - - /** - * Gets the root element for the spacer content. - * - * @return the root element for the spacer content - */ - Element getElement(); - - /** - * Gets the decorative element for this spacer. - */ - Element getDecoElement(); - - /** - * Gets the row index. - * - * @return the row index. - */ - int getRow(); -} diff --git a/client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java b/client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java deleted file mode 100644 index 49adefd536..0000000000 --- a/client/src/com/vaadin/client/widget/escalator/SpacerUpdater.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.escalator; - -import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; - -/** - * An interface that handles the display of content for spacers. - *

- * The updater is responsible for making sure all elements are properly - * constructed and cleaned up. - * - * @since 7.5.0 - * @author Vaadin Ltd - * @see Spacer - * @see BodyRowContainer - */ -public interface SpacerUpdater { - - /** A spacer updater that does nothing. */ - public static final SpacerUpdater NULL = new SpacerUpdater() { - @Override - public void init(Spacer spacer) { - // NOOP - } - - @Override - public void destroy(Spacer spacer) { - // NOOP - } - }; - - /** - * Called whenever a spacer should be initialized with content. - * - * @param spacer - * the spacer reference that should be initialized - */ - void init(Spacer spacer); - - /** - * Called whenever a spacer should be cleaned. - *

- * The structure to clean up is the same that has been constructed by - * {@link #init(Spacer)}. - * - * @param spacer - * the spacer reference that should be destroyed - */ - void destroy(Spacer spacer); -} diff --git a/client/src/com/vaadin/client/widget/grid/AutoScroller.java b/client/src/com/vaadin/client/widget/grid/AutoScroller.java deleted file mode 100644 index 9cc238ac15..0000000000 --- a/client/src/com/vaadin/client/widget/grid/AutoScroller.java +++ /dev/null @@ -1,643 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid; - -import com.google.gwt.animation.client.AnimationScheduler; -import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback; -import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.TableElement; -import com.google.gwt.dom.client.TableSectionElement; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Event.NativePreviewEvent; -import com.google.gwt.user.client.Event.NativePreviewHandler; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.widgets.Grid; - -/** - * A class for handling automatic scrolling vertically / horizontally in the - * Grid when the cursor is close enough the edge of the body of the grid, - * depending on the scroll direction chosen. - * - * @since 7.5.0 - * @author Vaadin Ltd - */ -public class AutoScroller { - - /** - * Callback that notifies when the cursor is on top of a new row or column - * because of the automatic scrolling. - */ - public interface AutoScrollerCallback { - - /** - * Triggered when doing automatic scrolling. - *

- * Because the auto scroller currently only supports scrolling in one - * axis, this method is used for both vertical and horizontal scrolling. - * - * @param scrollDiff - * the amount of pixels that have been auto scrolled since - * last call - */ - void onAutoScroll(int scrollDiff); - - /** - * Triggered when the grid scroll has reached the minimum scroll - * position. Depending on the scroll axis, either scrollLeft or - * scrollTop is 0. - */ - void onAutoScrollReachedMin(); - - /** - * Triggered when the grid scroll has reached the max scroll position. - * Depending on the scroll axis, either scrollLeft or scrollTop is at - * its maximum value. - */ - void onAutoScrollReachedMax(); - } - - public enum ScrollAxis { - VERTICAL, HORIZONTAL - } - - /** The maximum number of pixels per second to autoscroll. */ - private static final int SCROLL_TOP_SPEED_PX_SEC = 500; - - /** - * The minimum area where the grid doesn't scroll while the pointer is - * pressed. - */ - private static final int MIN_NO_AUTOSCROLL_AREA_PX = 50; - - /** The size of the autoscroll area, both top/left and bottom/right. */ - private int scrollAreaPX = 100; - - /** - * This class's main objective is to listen when to stop autoscrolling, and - * make sure everything stops accordingly. - */ - private class TouchEventHandler implements NativePreviewHandler { - @Override - public void onPreviewNativeEvent(final NativePreviewEvent event) { - /* - * Remember: targetElement is always where touchstart started, not - * where the finger is pointing currently. - */ - switch (event.getTypeInt()) { - case Event.ONTOUCHSTART: { - if (event.getNativeEvent().getTouches().length() == 1) { - /* - * Something has dropped a touchend/touchcancel and the - * scroller is most probably running amok. Let's cancel it - * and pretend that everything's going as expected - * - * Because this is a preview, this code is run before start - * event can be passed to the start(...) method. - */ - stop(); - - /* - * Related TODO: investigate why iOS seems to ignore a - * touchend/touchcancel when frames are dropped, and/or if - * something can be done about that. - */ - } - break; - } - - case Event.ONTOUCHMOVE: - event.cancel(); - break; - - case Event.ONTOUCHEND: - case Event.ONTOUCHCANCEL: - // TODO investigate if this works as desired - stop(); - break; - } - } - - } - - /** - * This class's responsibility is to scroll the table while a pointer is - * kept in a scrolling zone. - *

- * Techical note: This class is an AnimationCallback because we - * need a timer: when the finger is kept in place while the grid scrolls, we - * still need to be able to make new selections. So, instead of relying on - * events (which won't be fired, since the pointer isn't necessarily - * moving), we do this check on each frame while the pointer is "active" - * (mouse is pressed, finger is on screen). - */ - private class AutoScrollingFrame implements AnimationCallback { - - /** - * If the acceleration gradient area is smaller than this, autoscrolling - * will be disabled (it becomes too quick to accelerate to be usable). - */ - private static final int GRADIENT_MIN_THRESHOLD_PX = 10; - - /** - * The speed at which the gradient area recovers, once scrolling in that - * direction has started. - */ - private static final int SCROLL_AREA_REBOUND_PX_PER_SEC = 1; - private static final double SCROLL_AREA_REBOUND_PX_PER_MS = SCROLL_AREA_REBOUND_PX_PER_SEC / 1000.0d; - - /** - * The lowest y/x-coordinate on the {@link Event#getClientY() client-y} - * or {@link Event#getClientX() client-x} from where we need to start - * scrolling towards the top/left. - */ - private int startBound = -1; - - /** - * The highest y/x-coordinate on the {@link Event#getClientY() client-y} - * or {@link Event#getClientX() client-x} from where we need to - * scrolling towards the bottom. - */ - private int endBound = -1; - - /** - * The area where the selection acceleration takes place. If < - * {@link #GRADIENT_MIN_THRESHOLD_PX}, autoscrolling is disabled - */ - private final int gradientArea; - - /** - * The number of pixels per seconds we currently are scrolling (negative - * is towards the top/left, positive is towards the bottom/right). - */ - private double scrollSpeed = 0; - - private double prevTimestamp = 0; - - /** - * This field stores fractions of pixels to scroll, to make sure that - * we're able to scroll less than one px per frame. - */ - private double pixelsToScroll = 0.0d; - - /** Should this animator be running. */ - private boolean running = false; - - /** The handle in which this instance is running. */ - private AnimationHandle handle; - - /** - * The pointer's pageY (VERTICAL) / pageX (HORIZONTAL) coordinate - * depending on scrolling axis. - */ - private int scrollingAxisPageCoordinate; - - /** @see #doScrollAreaChecks(int) */ - private int finalStartBound; - - /** @see #doScrollAreaChecks(int) */ - private int finalEndBound; - - private boolean scrollAreaShouldRebound = false; - - public AutoScrollingFrame(final int startBound, final int endBound, - final int gradientArea) { - finalStartBound = startBound; - finalEndBound = endBound; - this.gradientArea = gradientArea; - } - - @Override - public void execute(final double timestamp) { - final double timeDiff = timestamp - prevTimestamp; - prevTimestamp = timestamp; - - reboundScrollArea(timeDiff); - - pixelsToScroll += scrollSpeed * (timeDiff / 1000.0d); - final int intPixelsToScroll = (int) pixelsToScroll; - pixelsToScroll -= intPixelsToScroll; - - if (intPixelsToScroll != 0) { - double scrollPos; - double maxScrollPos; - double newScrollPos; - if (scrollDirection == ScrollAxis.VERTICAL) { - scrollPos = grid.getScrollTop(); - maxScrollPos = getMaxScrollTop(); - } else { - scrollPos = grid.getScrollLeft(); - maxScrollPos = getMaxScrollLeft(); - } - if (intPixelsToScroll > 0 && scrollPos < maxScrollPos - || intPixelsToScroll < 0 && scrollPos > 0) { - newScrollPos = scrollPos + intPixelsToScroll; - if (scrollDirection == ScrollAxis.VERTICAL) { - grid.setScrollTop(newScrollPos); - } else { - grid.setScrollLeft(newScrollPos); - } - callback.onAutoScroll(intPixelsToScroll); - if (newScrollPos <= 0) { - callback.onAutoScrollReachedMin(); - } else if (newScrollPos >= maxScrollPos) { - callback.onAutoScrollReachedMax(); - } - } - } - - reschedule(); - } - - /** - * If the scroll are has been offset by the pointer starting out there, - * move it back a bit - */ - private void reboundScrollArea(double timeDiff) { - if (!scrollAreaShouldRebound) { - return; - } - - int reboundPx = (int) Math.ceil(SCROLL_AREA_REBOUND_PX_PER_MS - * timeDiff); - if (startBound < finalStartBound) { - startBound += reboundPx; - startBound = Math.min(startBound, finalStartBound); - updateScrollSpeed(scrollingAxisPageCoordinate); - } else if (endBound > finalEndBound) { - endBound -= reboundPx; - endBound = Math.max(endBound, finalEndBound); - updateScrollSpeed(scrollingAxisPageCoordinate); - } - } - - private void updateScrollSpeed(final int pointerPageCordinate) { - - final double ratio; - if (pointerPageCordinate < startBound) { - final double distance = pointerPageCordinate - startBound; - ratio = Math.max(-1, distance / gradientArea); - } - - else if (pointerPageCordinate > endBound) { - final double distance = pointerPageCordinate - endBound; - ratio = Math.min(1, distance / gradientArea); - } - - else { - ratio = 0; - } - - scrollSpeed = ratio * SCROLL_TOP_SPEED_PX_SEC; - } - - public void start() { - running = true; - reschedule(); - } - - public void stop() { - running = false; - - if (handle != null) { - handle.cancel(); - handle = null; - } - } - - private void reschedule() { - if (running && gradientArea >= GRADIENT_MIN_THRESHOLD_PX) { - handle = AnimationScheduler.get().requestAnimationFrame(this, - grid.getElement()); - } - } - - public void updatePointerCoords(int pageX, int pageY) { - final int pageCordinate; - if (scrollDirection == ScrollAxis.VERTICAL) { - pageCordinate = pageY; - } else { - pageCordinate = pageX; - } - doScrollAreaChecks(pageCordinate); - updateScrollSpeed(pageCordinate); - scrollingAxisPageCoordinate = pageCordinate; - } - - /** - * This method checks whether the first pointer event started in an area - * that would start scrolling immediately, and does some actions - * accordingly. - *

- * If it is, that scroll area will be offset "beyond" the pointer (above - * if pointer is towards the top/left, otherwise below/right). - */ - private void doScrollAreaChecks(int pageCordinate) { - /* - * The first run makes sure that neither scroll position is - * underneath the finger, but offset to either direction from - * underneath the pointer. - */ - if (startBound == -1) { - startBound = Math.min(finalStartBound, pageCordinate); - endBound = Math.max(finalEndBound, pageCordinate); - } - - /* - * Subsequent runs make sure that the scroll area grows (but doesn't - * shrink) with the finger, but no further than the final bound. - */ - else { - int oldTopBound = startBound; - if (startBound < finalStartBound) { - startBound = Math.max(startBound, - Math.min(finalStartBound, pageCordinate)); - } - - int oldBottomBound = endBound; - if (endBound > finalEndBound) { - endBound = Math.min(endBound, - Math.max(finalEndBound, pageCordinate)); - } - - final boolean startDidNotMove = oldTopBound == startBound; - final boolean endDidNotMove = oldBottomBound == endBound; - final boolean wasMovement = pageCordinate != scrollingAxisPageCoordinate; - scrollAreaShouldRebound = (startDidNotMove && endDidNotMove && wasMovement); - } - } - } - - /** - * This handler makes sure that pointer movements are handled. - *

- * Essentially, a native preview handler is registered (so that selection - * gestures can happen outside of the selection column). The handler itself - * makes sure that it's detached when the pointer is "lifted". - */ - private final NativePreviewHandler scrollPreviewHandler = new NativePreviewHandler() { - @Override - public void onPreviewNativeEvent(final NativePreviewEvent event) { - if (autoScroller == null) { - stop(); - return; - } - - final NativeEvent nativeEvent = event.getNativeEvent(); - int pageY = 0; - int pageX = 0; - switch (event.getTypeInt()) { - case Event.ONMOUSEMOVE: - case Event.ONTOUCHMOVE: - pageY = WidgetUtil.getTouchOrMouseClientY(nativeEvent); - pageX = WidgetUtil.getTouchOrMouseClientX(nativeEvent); - autoScroller.updatePointerCoords(pageX, pageY); - break; - case Event.ONMOUSEUP: - case Event.ONTOUCHEND: - case Event.ONTOUCHCANCEL: - stop(); - break; - } - } - }; - /** The registration info for {@link #scrollPreviewHandler} */ - private HandlerRegistration handlerRegistration; - - /** - * The top/left bound, as calculated from the {@link Event#getClientY() - * client-y} or {@link Event#getClientX() client-x} coordinates. - */ - private double startingBound = -1; - - /** - * The bottom/right bound, as calculated from the {@link Event#getClientY() - * client-y} or or {@link Event#getClientX() client-x} coordinates. - */ - private int endingBound = -1; - - /** The size of the autoscroll acceleration area. */ - private int gradientArea; - - private Grid grid; - - private HandlerRegistration nativePreviewHandlerRegistration; - - private ScrollAxis scrollDirection; - - private AutoScrollingFrame autoScroller; - - private AutoScrollerCallback callback; - - /** - * Creates a new instance for scrolling the given grid. - * - * @param grid - * the grid to auto scroll - */ - public AutoScroller(Grid grid) { - this.grid = grid; - } - - /** - * Starts the automatic scrolling detection. - * - * @param startEvent - * the event that starts the automatic scroll - * @param scrollAxis - * the axis along which the scrolling should happen - * @param callback - * the callback for getting info about the automatic scrolling - */ - public void start(final NativeEvent startEvent, ScrollAxis scrollAxis, - AutoScrollerCallback callback) { - scrollDirection = scrollAxis; - this.callback = callback; - injectNativeHandler(); - start(); - startEvent.preventDefault(); - startEvent.stopPropagation(); - } - - /** - * Stops the automatic scrolling. - */ - public void stop() { - if (handlerRegistration != null) { - handlerRegistration.removeHandler(); - handlerRegistration = null; - } - - if (autoScroller != null) { - autoScroller.stop(); - autoScroller = null; - } - - removeNativeHandler(); - } - - /** - * Set the auto scroll area height or width depending on the scrolling axis. - * This is the amount of pixels from the edge of the grid that the scroll is - * triggered. - *

- * Defaults to 100px. - * - * @param px - * the pixel height/width for the auto scroll area depending on - * direction - */ - public void setScrollArea(int px) { - scrollAreaPX = px; - } - - /** - * Returns the size of the auto scroll area in pixels. - *

- * Defaults to 100px. - * - * @return size in pixels - */ - public int getScrollArea() { - return scrollAreaPX; - } - - private void start() { - /* - * bounds are updated whenever the autoscroll cycle starts, to make sure - * that the widget hasn't changed in size, moved around, or whatnot. - */ - updateScrollBounds(); - - assert handlerRegistration == null : "handlerRegistration was not null"; - assert autoScroller == null : "autoScroller was not null"; - handlerRegistration = Event - .addNativePreviewHandler(scrollPreviewHandler); - autoScroller = new AutoScrollingFrame((int) Math.ceil(startingBound), - endingBound, gradientArea); - autoScroller.start(); - } - - private void updateScrollBounds() { - double startBorder = getBodyClientStart(); - final int endBorder = getBodyClientEnd(); - startBorder += getFrozenColumnsWidth(); - - startingBound = startBorder + scrollAreaPX; - endingBound = endBorder - scrollAreaPX; - gradientArea = scrollAreaPX; - - // modify bounds if they're too tightly packed - if (endingBound - startingBound < MIN_NO_AUTOSCROLL_AREA_PX) { - double adjustment = MIN_NO_AUTOSCROLL_AREA_PX - - (endingBound - startingBound); - startingBound -= adjustment / 2; - endingBound += adjustment / 2; - gradientArea -= adjustment / 2; - } - } - - private void injectNativeHandler() { - removeNativeHandler(); - nativePreviewHandlerRegistration = Event - .addNativePreviewHandler(new TouchEventHandler()); - } - - private void removeNativeHandler() { - if (nativePreviewHandlerRegistration != null) { - nativePreviewHandlerRegistration.removeHandler(); - nativePreviewHandlerRegistration = null; - } - } - - private TableElement getTableElement() { - final Element root = grid.getElement(); - final Element tablewrapper = Element.as(root.getChild(2)); - if (tablewrapper != null) { - return TableElement.as(tablewrapper.getFirstChildElement()); - } else { - return null; - } - } - - private TableSectionElement getTheadElement() { - TableElement table = getTableElement(); - if (table != null) { - return table.getTHead(); - } else { - return null; - } - } - - private TableSectionElement getTfootElement() { - TableElement table = getTableElement(); - if (table != null) { - return table.getTFoot(); - } else { - return null; - } - } - - private int getBodyClientEnd() { - if (scrollDirection == ScrollAxis.VERTICAL) { - return getTfootElement().getAbsoluteTop() - 1; - } else { - return getTableElement().getAbsoluteRight(); - } - - } - - private int getBodyClientStart() { - if (scrollDirection == ScrollAxis.VERTICAL) { - return getTheadElement().getAbsoluteBottom() + 1; - } else { - return getTableElement().getAbsoluteLeft(); - } - } - - public double getFrozenColumnsWidth() { - double value = 0; - - for (int i = 0; i < getRealFrozenColumnCount(); i++) { - value += grid.getColumn(i).getWidthActual(); - } - - return value; - } - - private int getRealFrozenColumnCount() { - if (grid.getFrozenColumnCount() < 0) { - return 0; - } else if (grid.getSelectionModel().getSelectionColumnRenderer() != null) { - // includes the selection column - return grid.getFrozenColumnCount() + 1; - } else { - return grid.getFrozenColumnCount(); - } - } - - private double getMaxScrollLeft() { - return grid.getScrollWidth() - - (getTableElement().getParentElement().getOffsetWidth() - getFrozenColumnsWidth()); - } - - private double getMaxScrollTop() { - return grid.getScrollHeight() - getTfootElement().getOffsetHeight() - - getTheadElement().getOffsetHeight(); - } -} diff --git a/client/src/com/vaadin/client/widget/grid/CellReference.java b/client/src/com/vaadin/client/widget/grid/CellReference.java deleted file mode 100644 index e783cb92ae..0000000000 --- a/client/src/com/vaadin/client/widget/grid/CellReference.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid; - -import com.google.gwt.dom.client.TableCellElement; -import com.vaadin.client.widgets.Grid; - -/** - * A data class which contains information which identifies a cell in a - * {@link Grid}. - *

- * Since this class follows the Flyweight-pattern any instance of - * this object is subject to change without the user knowing it and so should - * not be stored anywhere outside of the method providing these instances. - * - * @author Vaadin Ltd - * @param - * the type of the row object containing this cell - * @since 7.4 - */ -public class CellReference { - - private int columnIndexDOM; - private int columnIndex; - private Grid.Column column; - private final RowReference rowReference; - - public CellReference(RowReference rowReference) { - this.rowReference = rowReference; - } - - /** - * Sets the identifying information for this cell. - *

- * The difference between {@link #columnIndexDOM} and {@link #columnIndex} - * comes from hidden columns. - * - * @param columnIndexDOM - * the index of the column in the DOM - * @param columnIndex - * the index of the column - * @param column - * the column object - */ - public void set(int columnIndexDOM, int columnIndex, - Grid.Column column) { - this.columnIndexDOM = columnIndexDOM; - this.columnIndex = columnIndex; - this.column = column; - } - - /** - * Gets the grid that contains the referenced cell. - * - * @return the grid that contains referenced cell - */ - public Grid getGrid() { - return rowReference.getGrid(); - } - - /** - * Gets the row index of the row. - * - * @return the index of the row - */ - public int getRowIndex() { - return rowReference.getRowIndex(); - } - - /** - * Gets the row data object. - * - * @return the row object - */ - public T getRow() { - return rowReference.getRow(); - } - - /** - * Gets the index of the column. - *

- * NOTE: The index includes hidden columns in the count, unlike - * {@link #getColumnIndexDOM()}. - * - * @return the index of the column - */ - public int getColumnIndex() { - return columnIndex; - } - - /** - * Gets the index of the cell in the DOM. The difference to - * {@link #getColumnIndex()} is caused by hidden columns. - * - * @since 7.5.0 - * @return the index of the column in the DOM - */ - public int getColumnIndexDOM() { - return columnIndexDOM; - } - - /** - * Gets the column objects. - * - * @return the column object - */ - public Grid.Column getColumn() { - return column; - } - - /** - * Gets the value of the cell. - * - * @return the value of the cell - */ - public Object getValue() { - return getColumn().getValue(getRow()); - } - - /** - * Get the element of the cell. - * - * @return the element of the cell - */ - public TableCellElement getElement() { - return rowReference.getElement().getCells().getItem(columnIndexDOM); - } - - /** - * Gets the RowReference for this CellReference. - * - * @return the row reference - */ - protected RowReference getRowReference() { - return rowReference; - } - -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/CellStyleGenerator.java b/client/src/com/vaadin/client/widget/grid/CellStyleGenerator.java deleted file mode 100644 index bbc540de64..0000000000 --- a/client/src/com/vaadin/client/widget/grid/CellStyleGenerator.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid; - -import com.vaadin.client.widgets.Grid; - -/** - * Callback interface for generating custom style names for cells - * - * @author Vaadin Ltd - * @param - * the row type of the target grid - * @see Grid#setCellStyleGenerator(CellStyleGenerator) - * @since 7.4 - */ -public interface CellStyleGenerator { - - /** - * Called by Grid to generate a style name for a column element. - * - * @param cellReference - * The cell to generate a style for - * @return the style name to add to this cell, or {@code null} to not set - * any style - */ - public abstract String getStyle(CellReference cellReference); -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/DataAvailableEvent.java b/client/src/com/vaadin/client/widget/grid/DataAvailableEvent.java deleted file mode 100644 index d88fce4e11..0000000000 --- a/client/src/com/vaadin/client/widget/grid/DataAvailableEvent.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid; - -import com.google.gwt.event.shared.GwtEvent; -import com.vaadin.shared.ui.grid.Range; - -/** - * Event object describing a change of row availability in DataSource of a Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class DataAvailableEvent extends GwtEvent { - - private Range rowsAvailable; - public static final Type TYPE = new Type(); - - public DataAvailableEvent(Range rowsAvailable) { - this.rowsAvailable = rowsAvailable; - } - - /** - * Returns the range of available rows in {@link DataSource} for this event. - * - * @return range of available rows - */ - public Range getAvailableRows() { - return rowsAvailable; - } - - @Override - public Type getAssociatedType() { - return TYPE; - } - - @Override - protected void dispatch(DataAvailableHandler handler) { - handler.onDataAvailable(this); - } - -} diff --git a/client/src/com/vaadin/client/widget/grid/DataAvailableHandler.java b/client/src/com/vaadin/client/widget/grid/DataAvailableHandler.java deleted file mode 100644 index 5e0650bc41..0000000000 --- a/client/src/com/vaadin/client/widget/grid/DataAvailableHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid; - -import com.google.gwt.event.shared.EventHandler; - -/** - * Handler for {@link DataAvailableEvent}s. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface DataAvailableHandler extends EventHandler { - - /** - * Called when DataSource has data available. Supplied with row range. - * - * @param availableRows - * Range of rows available in the DataSource - * @return true if the command was successfully completed, false to call - * again the next time new data is available - */ - public void onDataAvailable(DataAvailableEvent event); -} diff --git a/client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java b/client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java deleted file mode 100644 index e4a8783f54..0000000000 --- a/client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid; - -import com.google.gwt.core.client.Duration; -import com.google.gwt.dom.client.BrowserEvents; -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.ui.FocusUtil; -import com.vaadin.client.widgets.Grid.Editor; -import com.vaadin.client.widgets.Grid.EditorDomEvent; - -/** - * The default handler for Grid editor events. Offers several overridable - * protected methods for easier customization. - * - * @since 7.6 - * @author Vaadin Ltd - */ -public class DefaultEditorEventHandler implements Editor.EventHandler { - - public static final int KEYCODE_OPEN = KeyCodes.KEY_ENTER; - public static final int KEYCODE_MOVE_VERTICAL = KeyCodes.KEY_ENTER; - public static final int KEYCODE_CLOSE = KeyCodes.KEY_ESCAPE; - public static final int KEYCODE_MOVE_HORIZONTAL = KeyCodes.KEY_TAB; - public static final int KEYCODE_BUFFERED_SAVE = KeyCodes.KEY_ENTER; - - private double lastTouchEventTime = 0; - private int lastTouchEventX = -1; - private int lastTouchEventY = -1; - private int lastTouchEventRow = -1; - - /** - * Returns whether the given event is a touch event that should open the - * editor. - * - * @param event - * the received event - * @return whether the event is a touch open event - */ - protected boolean isTouchOpenEvent(EditorDomEvent event) { - final Event e = event.getDomEvent(); - final int type = e.getTypeInt(); - - final double now = Duration.currentTimeMillis(); - final int currentX = WidgetUtil.getTouchOrMouseClientX(e); - final int currentY = WidgetUtil.getTouchOrMouseClientY(e); - - final boolean validTouchOpenEvent = type == Event.ONTOUCHEND - && now - lastTouchEventTime < 500 - && lastTouchEventRow == event.getCell().getRowIndex() - && Math.abs(lastTouchEventX - currentX) < 20 - && Math.abs(lastTouchEventY - currentY) < 20; - - if (type == Event.ONTOUCHSTART) { - lastTouchEventX = currentX; - lastTouchEventY = currentY; - } - - if (type == Event.ONTOUCHEND) { - lastTouchEventTime = now; - lastTouchEventRow = event.getCell().getRowIndex(); - } - - return validTouchOpenEvent; - } - - /** - * Returns whether the given event should open the editor. The default - * implementation returns true if and only if the event is a doubleclick or - * if it is a keydown event and the keycode is {@link #KEYCODE_OPEN}. - * - * @param event - * the received event - * @return true if the event is an open event, false otherwise - */ - protected boolean isOpenEvent(EditorDomEvent event) { - final Event e = event.getDomEvent(); - return e.getTypeInt() == Event.ONDBLCLICK - || (e.getTypeInt() == Event.ONKEYDOWN && e.getKeyCode() == KEYCODE_OPEN) - || isTouchOpenEvent(event); - } - - /** - * Opens the editor on the appropriate row if the received event is an open - * event. The default implementation uses - * {@link #isOpenEvent(EditorDomEvent) isOpenEvent}. - * - * @param event - * the received event - * @return true if this method handled the event and nothing else should be - * done, false otherwise - */ - protected boolean handleOpenEvent(EditorDomEvent event) { - if (isOpenEvent(event)) { - final EventCellReference cell = event.getCell(); - - editRow(event, cell.getRowIndex(), cell.getColumnIndexDOM()); - - event.getDomEvent().preventDefault(); - - return true; - } - return false; - } - - /** - * Moves the editor to another row or another column if the received event - * is a move event. The default implementation moves the editor to the - * clicked row if the event is a click; otherwise, if the event is a keydown - * and the keycode is {@link #KEYCODE_MOVE_VERTICAL}, moves the editor one - * row up or down if the shift key is pressed or not, respectively. Keydown - * event with keycode {@link #KEYCODE_MOVE_HORIZONTAL} moves the editor left - * or right if shift key is pressed or not, respectively. - * - * @param event - * the received event - * @return true if this method handled the event and nothing else should be - * done, false otherwise - */ - protected boolean handleMoveEvent(EditorDomEvent event) { - Event e = event.getDomEvent(); - final EventCellReference cell = event.getCell(); - - // TODO: Move on touch events - if (e.getTypeInt() == Event.ONCLICK) { - - editRow(event, cell.getRowIndex(), cell.getColumnIndexDOM()); - - return true; - } - - else if (e.getTypeInt() == Event.ONKEYDOWN) { - - int rowDelta = 0; - int colDelta = 0; - - if (e.getKeyCode() == KEYCODE_MOVE_VERTICAL) { - rowDelta = (e.getShiftKey() ? -1 : +1); - } else if (e.getKeyCode() == KEYCODE_MOVE_HORIZONTAL) { - colDelta = (e.getShiftKey() ? -1 : +1); - // Prevent tab out of Grid Editor - event.getDomEvent().preventDefault(); - } - - final boolean changed = rowDelta != 0 || colDelta != 0; - - if (changed) { - - int columnCount = event.getGrid().getVisibleColumns().size(); - - int colIndex = event.getFocusedColumnIndex() + colDelta; - int rowIndex = event.getRowIndex(); - - // Handle row change with horizontal move when column goes out - // of range. - if (rowDelta == 0) { - if (colIndex >= columnCount - && rowIndex < event.getGrid().getDataSource() - .size() - 1) { - rowDelta = 1; - colIndex = 0; - } else if (colIndex < 0 && rowIndex > 0) { - rowDelta = -1; - colIndex = columnCount - 1; - } - } - - editRow(event, rowIndex + rowDelta, colIndex); - } - - return changed; - } - - return false; - } - - /** - * Moves the editor to another column if the received event is a move event. - * By default the editor is moved on a keydown event with keycode - * {@link #KEYCODE_MOVE_HORIZONTAL}. This moves the editor left or right if - * shift key is pressed or not, respectively. - * - * @param event - * the received event - * @return true if this method handled the event and nothing else should be - * done, false otherwise - */ - protected boolean handleBufferedMoveEvent(EditorDomEvent event) { - Event e = event.getDomEvent(); - - if (e.getType().equals(BrowserEvents.CLICK) - && event.getRowIndex() == event.getCell().getRowIndex()) { - - editRow(event, event.getRowIndex(), event.getCell() - .getColumnIndexDOM()); - - return true; - - } else if (e.getType().equals(BrowserEvents.KEYDOWN) - && e.getKeyCode() == KEYCODE_MOVE_HORIZONTAL) { - - // Prevent tab out of Grid Editor - event.getDomEvent().preventDefault(); - - editRow(event, event.getRowIndex(), event.getFocusedColumnIndex() - + (e.getShiftKey() ? -1 : +1)); - - return true; - } else if (e.getType().equals(BrowserEvents.KEYDOWN) - && e.getKeyCode() == KEYCODE_BUFFERED_SAVE) { - triggerValueChangeEvent(event); - - // Save and close. - event.getGrid().getEditor().save(); - return true; - } - - return false; - } - - /** - * Returns whether the given event should close the editor. The default - * implementation returns true if and only if the event is a keydown event - * and the keycode is {@link #KEYCODE_CLOSE}. - * - * @param event - * the received event - * @return true if the event is a close event, false otherwise - */ - protected boolean isCloseEvent(EditorDomEvent event) { - final Event e = event.getDomEvent(); - return e.getTypeInt() == Event.ONKEYDOWN - && e.getKeyCode() == KEYCODE_CLOSE; - } - - /** - * Closes the editor if the received event is a close event. The default - * implementation uses {@link #isCloseEvent(EditorDomEvent) isCloseEvent}. - * - * @param event - * the received event - * @return true if this method handled the event and nothing else should be - * done, false otherwise - */ - protected boolean handleCloseEvent(EditorDomEvent event) { - if (isCloseEvent(event)) { - event.getEditor().cancel(); - FocusUtil.setFocus(event.getGrid(), true); - return true; - } - return false; - } - - protected void editRow(EditorDomEvent event, int rowIndex, int colIndex) { - int rowCount = event.getGrid().getDataSource().size(); - // Limit rowIndex between 0 and rowCount - 1 - rowIndex = Math.max(0, Math.min(rowCount - 1, rowIndex)); - - int colCount = event.getGrid().getVisibleColumns().size(); - // Limit colIndex between 0 and colCount - 1 - colIndex = Math.max(0, Math.min(colCount - 1, colIndex)); - - if (rowIndex != event.getRowIndex()) { - triggerValueChangeEvent(event); - } - - event.getEditor().editRow(rowIndex, colIndex); - } - - /** - * Triggers a value change event from the editor field if it has focus. This - * is based on the assumption that editor field will fire the value change - * when a blur event occurs. - * - * @param event - * the editor DOM event - */ - private void triggerValueChangeEvent(EditorDomEvent event) { - // Force a blur to cause a value change event - Widget editorWidget = event.getEditorWidget(); - if (editorWidget != null) { - Element focusedElement = WidgetUtil.getFocusedElement(); - if (editorWidget.getElement().isOrHasChild(focusedElement)) { - focusedElement.blur(); - focusedElement.focus(); - } - } - } - - @Override - public boolean handleEvent(EditorDomEvent event) { - final Editor editor = event.getEditor(); - final boolean isBody = event.getCell().isBody(); - - final boolean handled; - if (event.getGrid().isEditorActive()) { - handled = handleCloseEvent(event) - || (!editor.isBuffered() && isBody && handleMoveEvent(event)) - || (editor.isBuffered() && isBody && handleBufferedMoveEvent(event)); - } else { - handled = event.getGrid().isEnabled() && isBody - && handleOpenEvent(event); - } - - // Buffered mode should swallow all events, if not already handled. - boolean swallowEvent = event.getGrid().isEditorActive() - && editor.isBuffered(); - - return handled || swallowEvent; - } -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java b/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java deleted file mode 100644 index b9427091a7..0000000000 --- a/client/src/com/vaadin/client/widget/grid/DetailsGenerator.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid; - -import com.google.gwt.user.client.ui.Widget; - -/** - * A callback interface for generating details for a particular row in Grid. - * - * @since 7.5.0 - * @author Vaadin Ltd - */ -public interface DetailsGenerator { - - /** A details generator that provides no details */ - public static final DetailsGenerator NULL = new DetailsGenerator() { - @Override - public Widget getDetails(int rowIndex) { - return null; - } - }; - - /** - * This method is called for whenever a new details row needs to be - * generated. - * - * @param rowIndex - * the index of the row for which to generate details - * @return the details for the given row, or null to leave the - * details empty. - */ - Widget getDetails(int rowIndex); -} diff --git a/client/src/com/vaadin/client/widget/grid/EditorHandler.java b/client/src/com/vaadin/client/widget/grid/EditorHandler.java deleted file mode 100644 index 91198700ca..0000000000 --- a/client/src/com/vaadin/client/widget/grid/EditorHandler.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid; - -import java.util.Collection; - -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.widgets.Grid; - -/** - * An interface for binding widgets and data to the grid row editor. Used by the - * editor to support different row types, data sources and custom data binding - * mechanisms. - * - * @param - * the row data type - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface EditorHandler { - - /** - * A request class passed as a parameter to the editor handler methods. The - * request is callback-based to facilitate usage with remote or otherwise - * asynchronous data sources. - *

- * An implementation must call either {@link #success()} or {@link #fail()}, - * according to whether the operation was a success or failed during - * execution, respectively. - * - * @param - * the row data type - */ - public interface EditorRequest { - /** - * Returns the index of the row being requested. - * - * @return the row index - */ - public int getRowIndex(); - - /** - * Returns the index of the column being focused. - * - * @return the column index - */ - public int getColumnIndex(); - - /** - * Returns the row data related to the row being requested. - * - * @return the row data - */ - public T getRow(); - - /** - * Returns the grid instance related to this editor request. - * - * @return the grid instance - */ - public Grid getGrid(); - - /** - * Returns the editor widget used to edit the values of the given - * column. - * - * @param column - * the column whose widget to get - * @return the widget related to the column - */ - public Widget getWidget(Grid.Column column); - - /** - * Informs Grid that the editor request was a success. - */ - public void success(); - - /** - * Informs Grid that an error occurred while trying to process the - * request. - * - * @param errorMessage - * and error message to show to the user, or - * null to not show any message. - * @param errorColumns - * a collection of columns for which an error indicator - * should be shown, or null if no columns should - * be marked as erroneous. - */ - public void failure(String errorMessage, - Collection> errorColumns); - - /** - * Checks whether the request is completed or not. - * - * @return true iff the request is completed - */ - public boolean isCompleted(); - } - - /** - * Binds row data to the editor widgets. Called by the editor when it is - * opened for editing. - *

- * The implementation must call either - * {@link EditorRequest#success()} or - * {@link EditorRequest#failure(String, Collection)} to signal a successful - * or a failed (respectively) bind action. - * - * @param request - * the data binding request - * - * @see Grid#editRow(int) - */ - public void bind(EditorRequest request); - - /** - * Called by the editor when editing is cancelled. This method may have an - * empty implementation in case no special processing is required. - *

- * In contrast to {@link #bind(EditorRequest)} and - * {@link #save(EditorRequest)}, any calls to - * {@link EditorRequest#success()} or - * {@link EditorRequest#failure(String, Collection)} have no effect on the - * outcome of the cancel action. The editor is already closed when this - * method is called. - * - * @param request - * the cancel request - * - * @see Grid#cancelEditor() - */ - public void cancel(EditorRequest request); - - /** - * Commits changes in the currently active edit to the data source. Called - * by the editor when changes are saved. - *

- * The implementation must call either - * {@link EditorRequest#success()} or {@link EditorRequest#fail()} to signal - * a successful or a failed (respectively) save action. - * - * @param request - * the save request - * - * @see Grid#saveEditor() - */ - public void save(EditorRequest request); - - /** - * Returns a widget instance that is used to edit the values in the given - * column. A null return value means the column is not editable. - * - * @param column - * the column whose values should be edited - * @return the editor widget for the column or null if the column is not - * editable - */ - public Widget getWidget(Grid.Column column); -} diff --git a/client/src/com/vaadin/client/widget/grid/EventCellReference.java b/client/src/com/vaadin/client/widget/grid/EventCellReference.java deleted file mode 100644 index 4d37be2cc1..0000000000 --- a/client/src/com/vaadin/client/widget/grid/EventCellReference.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid; - -import com.google.gwt.dom.client.TableCellElement; -import com.vaadin.client.widget.escalator.Cell; -import com.vaadin.client.widgets.Grid; -import com.vaadin.client.widgets.Grid.Column; -import com.vaadin.shared.ui.grid.GridConstants.Section; - -/** - * A data class which contains information which identifies a cell being the - * target of an event from {@link Grid}. - *

- * Since this class follows the Flyweight-pattern any instance of - * this object is subject to change without the user knowing it and so should - * not be stored anywhere outside of the method providing these instances. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class EventCellReference extends CellReference { - - private Section section; - private TableCellElement element; - - public EventCellReference(Grid grid) { - super(new RowReference(grid)); - } - - /** - * Sets the RowReference and CellReference to point to given Cell. - * - * @param targetCell - * cell to point to - */ - public void set(Cell targetCell, Section section) { - Grid grid = getGrid(); - - int columnIndexDOM = targetCell.getColumn(); - Column column = null; - if (columnIndexDOM >= 0 - && columnIndexDOM < grid.getVisibleColumns().size()) { - column = grid.getVisibleColumns().get(columnIndexDOM); - } - - int row = targetCell.getRow(); - // Row objects only make sense for body section of Grid. - T rowObject; - if (section == Section.BODY && row >= 0 - && row < grid.getDataSource().size()) { - rowObject = grid.getDataSource().getRow(row); - } else { - rowObject = null; - } - - // At least for now we don't need to have the actual TableRowElement - // available. - getRowReference().set(row, rowObject, null); - - int columnIndex = grid.getColumns().indexOf(column); - set(columnIndexDOM, columnIndex, column); - - this.element = targetCell.getElement(); - this.section = section; - } - - @Override - public TableCellElement getElement() { - return element; - } - - /** - * Is the cell reference for a cell in the header of the Grid. - * - * @since 7.5 - * @return true if referenced cell is in the header, - * false if not - */ - public boolean isHeader() { - return section == Section.HEADER; - } - - /** - * Is the cell reference for a cell in the body of the Grid. - * - * @since 7.5 - * @return true if referenced cell is in the body, - * false if not - */ - public boolean isBody() { - return section == Section.BODY; - } - - /** - * Is the cell reference for a cell in the footer of the Grid. - * - * @since 7.5 - * @return true if referenced cell is in the footer, - * false if not - */ - public boolean isFooter() { - return section == Section.FOOTER; - } - - /** - * Gets the Grid section where the referenced cell is. - * - * @since 7.5 - * @return grid section - */ - public Section getSection() { - return section; - } -} diff --git a/client/src/com/vaadin/client/widget/grid/HeightAwareDetailsGenerator.java b/client/src/com/vaadin/client/widget/grid/HeightAwareDetailsGenerator.java deleted file mode 100644 index 5364de3411..0000000000 --- a/client/src/com/vaadin/client/widget/grid/HeightAwareDetailsGenerator.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid; - -/** - * {@link DetailsGenerator} that is aware of content heights. - *

- * FOR INTERNAL USE ONLY! This class exists only for the sake of a - * temporary workaround and might be removed or renamed at any time. - *

- * - * @since 7.6.1 - * @author Vaadin Ltd - */ -@Deprecated -public interface HeightAwareDetailsGenerator extends DetailsGenerator { - - /** - * This method is called for whenever a details row's height needs to be - * calculated. - *

- * FOR INTERNAL USE ONLY! This method exists only for the sake of a - * temporary workaround and might be removed or renamed at any time. - *

- * - * @since 7.6.1 - * @param rowIndex - * the index of the row for which to calculate details row height - * @return height of the details row - */ - public double getDetailsHeight(int rowIndex); -} diff --git a/client/src/com/vaadin/client/widget/grid/RendererCellReference.java b/client/src/com/vaadin/client/widget/grid/RendererCellReference.java deleted file mode 100644 index 994db50aa0..0000000000 --- a/client/src/com/vaadin/client/widget/grid/RendererCellReference.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid; - -import com.google.gwt.dom.client.TableCellElement; -import com.vaadin.client.widget.escalator.FlyweightCell; -import com.vaadin.client.widgets.Grid; - -/** - * A data class which contains information which identifies a cell being - * rendered in a {@link Grid}. - *

- * Since this class follows the Flyweight-pattern any instance of - * this object is subject to change without the user knowing it and so should - * not be stored anywhere outside of the method providing these instances. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class RendererCellReference extends CellReference { - - /** - * Creates a new renderer cell reference bound to a row reference. - * - * @param rowReference - * the row reference to bind to - */ - public RendererCellReference(RowReference rowReference) { - super(rowReference); - } - - private FlyweightCell cell; - - /** - * Sets the identifying information for this cell. - * - * @param cell - * the flyweight cell to reference - * @param columnIndex - * the index of the column in the grid, including hidden cells - * @param column - * the column to reference - */ - public void set(FlyweightCell cell, int columnIndex, - Grid.Column column) { - this.cell = cell; - super.set(cell.getColumn(), columnIndex, - (Grid.Column) column); - } - - /** - * Returns the element of the cell. Can be either a TD element - * or a TH element. - * - * @return the element of the cell - */ - @Override - public TableCellElement getElement() { - return cell.getElement(); - } - - /** - * Sets the colspan attribute of the element of this cell. - * - * @param numberOfCells - * the number of columns that the cell should span - */ - public void setColSpan(int numberOfCells) { - cell.setColSpan(numberOfCells); - } - - /** - * Gets the colspan attribute of the element of this cell. - * - * @return the number of columns that the cell should span - */ - public int getColSpan() { - return cell.getColSpan(); - } -} diff --git a/client/src/com/vaadin/client/widget/grid/RowReference.java b/client/src/com/vaadin/client/widget/grid/RowReference.java deleted file mode 100644 index 8874fcc5cc..0000000000 --- a/client/src/com/vaadin/client/widget/grid/RowReference.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid; - -import com.google.gwt.dom.client.TableRowElement; -import com.vaadin.client.widgets.Grid; - -/** - * A data class which contains information which identifies a row in a - * {@link Grid}. - *

- * Since this class follows the Flyweight-pattern any instance of - * this object is subject to change without the user knowing it and so should - * not be stored anywhere outside of the method providing these instances. - * - * @author Vaadin Ltd - * @param - * the row object type - * @since 7.4 - */ -public class RowReference { - private final Grid grid; - - private int rowIndex; - private T row; - - private TableRowElement element; - - /** - * Creates a new row reference for the given grid. - * - * @param grid - * the grid that the row belongs to - */ - public RowReference(Grid grid) { - this.grid = grid; - } - - /** - * Sets the identifying information for this row. - * - * @param rowIndex - * the index of the row - * @param row - * the row object - * @param elemenet - * the element of the row - */ - public void set(int rowIndex, T row, TableRowElement element) { - this.rowIndex = rowIndex; - this.row = row; - this.element = element; - } - - /** - * Gets the grid that contains the referenced row. - * - * @return the grid that contains referenced row - */ - public Grid getGrid() { - return grid; - } - - /** - * Gets the row index of the row. - * - * @return the index of the row - */ - public int getRowIndex() { - return rowIndex; - } - - /** - * Gets the row data object. - * - * @return the row object - */ - public T getRow() { - return row; - } - - /** - * Gets the table row element of the row. - * - * @return the element of the row - */ - public TableRowElement getElement() { - return element; - } - -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/RowStyleGenerator.java b/client/src/com/vaadin/client/widget/grid/RowStyleGenerator.java deleted file mode 100644 index a12a9ff47d..0000000000 --- a/client/src/com/vaadin/client/widget/grid/RowStyleGenerator.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid; - -import java.io.Serializable; - -/** - * Callback interface for generating custom style names for data rows - * - * @author Vaadin Ltd - * @param - * the row type of the target grid - * @see Grid#setRowStyleGenerator(RowStyleGenerator) - * @since 7.4 - */ -public interface RowStyleGenerator extends Serializable { - - /** - * Called by Grid to generate a style name for a row. - * - * @param rowReference - * The row to generate a style for - * @return the style name to add to this row, or {@code null} to not set any - * style - */ - public abstract String getStyle(RowReference rowReference); -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/datasources/ListDataSource.java b/client/src/com/vaadin/client/widget/grid/datasources/ListDataSource.java deleted file mode 100644 index cf7ec53e68..0000000000 --- a/client/src/com/vaadin/client/widget/grid/datasources/ListDataSource.java +++ /dev/null @@ -1,478 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.datasources; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; - -import com.vaadin.client.data.DataChangeHandler; -import com.vaadin.client.data.DataSource; -import com.vaadin.client.widget.grid.events.SelectAllEvent; -import com.vaadin.client.widget.grid.events.SelectAllHandler; -import com.vaadin.shared.util.SharedUtil; - -/** - * A simple list based on an in-memory data source for simply adding a list of - * row pojos to the grid. Based on a wrapped list instance which supports adding - * and removing of items. - * - *

- * Usage: - * - *

- * ListDataSource<Integer> ds = new ListDataSource<Integer>(1, 2, 3, 4);
- * 
- * // Add item to the data source
- * ds.asList().add(5);
- * 
- * // Remove item from the data source
- * ds.asList().remove(3);
- * 
- * // Add multiple items
- * ds.asList().addAll(Arrays.asList(5, 6, 7));
- * 
- * - * @since 7.4 - * @author Vaadin Ltd - */ -public class ListDataSource implements DataSource { - - private class RowHandleImpl extends RowHandle { - - private final T row; - - public RowHandleImpl(T row) { - this.row = row; - } - - @Override - public T getRow() { - /* - * We'll cheat here and don't throw an IllegalStateException even if - * this isn't pinned, because we know that the reference never gets - * stale. - */ - return row; - } - - @Override - public void pin() { - // NOOP, really - } - - @Override - public void unpin() throws IllegalStateException { - /* - * Just to make things easier for everyone, we won't throw the - * exception, even in illegal situations. - */ - } - - @Override - protected boolean equalsExplicit(Object obj) { - if (obj instanceof ListDataSource.RowHandleImpl) { - /* - * Java prefers AbstractRemoteDataSource.RowHandleImpl. I - * like the @SuppressWarnings more (keeps the line length in - * check.) - */ - @SuppressWarnings("unchecked") - RowHandleImpl rhi = (RowHandleImpl) obj; - return SharedUtil.equals(row, rhi.row); - } else { - return false; - } - } - - @Override - protected int hashCodeExplicit() { - return row.hashCode(); - } - - @Override - public void updateRow() { - if (changeHandler != null) { - changeHandler.dataUpdated(ds.indexOf(getRow()), 1); - } - } - } - - /** - * Wraps the datasource list and notifies the change handler of changing to - * the list - */ - private class ListWrapper implements List { - - @Override - public int size() { - return ds.size(); - } - - @Override - public boolean isEmpty() { - return ds.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return ds.contains(o); - } - - @Override - public Iterator iterator() { - return new ListWrapperIterator(ds.iterator()); - } - - @Override - public Object[] toArray() { - return ds.toArray(); - } - - @Override - @SuppressWarnings("hiding") - public T[] toArray(T[] a) { - return ds.toArray(a); - } - - @Override - public boolean add(T e) { - if (ds.add(e)) { - if (changeHandler != null) { - changeHandler.dataAdded(ds.size() - 1, 1); - } - return true; - } - return false; - } - - @Override - public boolean remove(Object o) { - int index = ds.indexOf(o); - if (ds.remove(o)) { - if (changeHandler != null) { - changeHandler.dataRemoved(index, 1); - } - return true; - } - return false; - } - - @Override - public boolean containsAll(Collection c) { - return ds.containsAll(c); - } - - @Override - public boolean addAll(Collection c) { - int idx = ds.size(); - if (ds.addAll(c)) { - if (changeHandler != null) { - changeHandler.dataAdded(idx, c.size()); - } - return true; - } - return false; - } - - @Override - public boolean addAll(int index, Collection c) { - if (ds.addAll(index, c)) { - if (changeHandler != null) { - changeHandler.dataAdded(index, c.size()); - } - return true; - } - return false; - } - - @Override - public boolean removeAll(Collection c) { - if (ds.removeAll(c)) { - if (changeHandler != null) { - // Have to update the whole list as the removal does not - // have to be a continuous range - changeHandler.dataUpdated(0, ds.size()); - changeHandler.dataAvailable(0, ds.size()); - } - return true; - } - return false; - } - - @Override - public boolean retainAll(Collection c) { - if (ds.retainAll(c)) { - if (changeHandler != null) { - // Have to update the whole list as the retain does not - // have to be a continuous range - changeHandler.dataUpdated(0, ds.size()); - changeHandler.dataAvailable(0, ds.size()); - } - return true; - } - return false; - } - - @Override - public void clear() { - int size = ds.size(); - ds.clear(); - if (changeHandler != null) { - changeHandler.dataRemoved(0, size); - } - } - - @Override - public T get(int index) { - return ds.get(index); - } - - @Override - public T set(int index, T element) { - T prev = ds.set(index, element); - if (changeHandler != null) { - changeHandler.dataUpdated(index, 1); - } - return prev; - } - - @Override - public void add(int index, T element) { - ds.add(index, element); - if (changeHandler != null) { - changeHandler.dataAdded(index, 1); - } - } - - @Override - public T remove(int index) { - T removed = ds.remove(index); - if (changeHandler != null) { - changeHandler.dataRemoved(index, 1); - } - return removed; - } - - @Override - public int indexOf(Object o) { - return ds.indexOf(o); - } - - @Override - public int lastIndexOf(Object o) { - return ds.lastIndexOf(o); - } - - @Override - public ListIterator listIterator() { - // TODO could be implemented by a custom iterator. - throw new UnsupportedOperationException( - "List iterators not supported at this time."); - } - - @Override - public ListIterator listIterator(int index) { - // TODO could be implemented by a custom iterator. - throw new UnsupportedOperationException( - "List iterators not supported at this time."); - } - - @Override - public List subList(int fromIndex, int toIndex) { - throw new UnsupportedOperationException("Sub lists not supported."); - } - } - - /** - * Iterator returned by {@link ListWrapper} - */ - private class ListWrapperIterator implements Iterator { - - private final Iterator iterator; - - /** - * Constructs a new iterator - */ - public ListWrapperIterator(Iterator iterator) { - this.iterator = iterator; - } - - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public T next() { - return iterator.next(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException( - "Iterator.remove() is not supported by this iterator."); - } - } - - /** - * Datasource for providing row pojo's - */ - private final List ds; - - /** - * Wrapper that wraps the data source - */ - private final ListWrapper wrapper; - - /** - * Handler for listening to changes in the underlying list. - */ - private DataChangeHandler changeHandler; - - /** - * Constructs a new list data source. - *

- * Note: Modifications to the original list will not be reflected in the - * data source after the data source has been constructed. To add or remove - * items to the data source after it has been constructed use - * {@link ListDataSource#asList()}. - * - * - * @param datasource - * The list to use for providing the data to the grid - */ - public ListDataSource(List datasource) { - if (datasource == null) { - throw new IllegalArgumentException("datasource cannot be null"); - } - ds = new ArrayList(datasource); - wrapper = new ListWrapper(); - } - - /** - * Constructs a data source with a set of rows. You can dynamically add and - * remove rows from the data source via the list you get from - * {@link ListDataSource#asList()} - * - * @param rows - * The rows to initially add to the data source - */ - public ListDataSource(T... rows) { - if (rows == null) { - ds = new ArrayList(); - } else { - ds = new ArrayList(Arrays.asList(rows)); - } - wrapper = new ListWrapper(); - } - - @Override - public void ensureAvailability(int firstRowIndex, int numberOfRows) { - if (firstRowIndex >= ds.size()) { - throw new IllegalStateException( - "Trying to fetch rows outside of array"); - } - - if (changeHandler != null) { - changeHandler.dataAvailable(firstRowIndex, numberOfRows); - } - } - - @Override - public T getRow(int rowIndex) { - return ds.get(rowIndex); - } - - @Override - public int size() { - return ds.size(); - } - - @Override - public void setDataChangeHandler(DataChangeHandler dataChangeHandler) { - this.changeHandler = dataChangeHandler; - } - - /** - * Gets the list that backs this datasource. Any changes made to this list - * will be reflected in the datasource. - *

- * Note: The list is not the same list as passed into the data source via - * the constructor. - * - * @return Returns a list implementation that wraps the real list that backs - * the data source and provides events for the data source - * listeners. - */ - public List asList() { - return wrapper; - } - - @Override - public RowHandle getHandle(T row) throws IllegalStateException { - assert ds.contains(row) : "This data source doesn't contain the row " - + row; - return new RowHandleImpl(row); - } - - /** - * Sort entire container according to a {@link Comparator}. - * - * @param comparator - * a comparator object, which compares two data source entries - * (beans/pojos) - */ - public void sort(Comparator comparator) { - Collections.sort(ds, comparator); - if (changeHandler != null) { - changeHandler.dataUpdated(0, ds.size()); - } - } - - /** - * Retrieves the index for given row object. - *

- * Note: This method does not verify that the given row object - * exists at all in this DataSource. - * - * @param row - * the row object - * @return index of the row; or -1 if row is not available - */ - public int indexOf(T row) { - return ds.indexOf(row); - } - - /** - * Returns a {@link SelectAllHandler} for this ListDataSource. - * - * @return select all handler - */ - public SelectAllHandler getSelectAllHandler() { - return new SelectAllHandler() { - @Override - public void onSelectAll(SelectAllEvent event) { - event.getSelectionModel().select(asList()); - } - }; - } -} diff --git a/client/src/com/vaadin/client/widget/grid/datasources/ListSorter.java b/client/src/com/vaadin/client/widget/grid/datasources/ListSorter.java deleted file mode 100644 index 69bea629b0..0000000000 --- a/client/src/com/vaadin/client/widget/grid/datasources/ListSorter.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.datasources; - -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.google.gwt.event.shared.HandlerRegistration; -import com.vaadin.client.data.DataSource; -import com.vaadin.client.widget.grid.sort.SortEvent; -import com.vaadin.client.widget.grid.sort.SortHandler; -import com.vaadin.client.widget.grid.sort.SortOrder; -import com.vaadin.client.widgets.Grid; -import com.vaadin.shared.data.sort.SortDirection; - -/** - * Provides sorting facility from Grid for the {@link ListDataSource} in-memory - * data source. - * - * @author Vaadin Ltd - * @param - * Grid row data type - * @since 7.4 - */ -public class ListSorter { - - private Grid grid; - private Map, Comparator> comparators; - private HandlerRegistration sortHandlerRegistration; - - public ListSorter(Grid grid) { - - if (grid == null) { - throw new IllegalArgumentException("Grid can not be null"); - } - - this.grid = grid; - comparators = new HashMap, Comparator>(); - - sortHandlerRegistration = grid.addSortHandler(new SortHandler() { - @Override - public void sort(SortEvent event) { - ListSorter.this.sort(event.getOrder()); - } - }); - } - - /** - * Detach this Sorter from the Grid. This unregisters the sort event handler - * which was used to apply sorting to the ListDataSource. - */ - public void removeFromGrid() { - sortHandlerRegistration.removeHandler(); - } - - /** - * Assign or remove a comparator for a column. This comparator method, if - * defined, is always used in favour of 'natural' comparison of objects - * (i.e. the compareTo of objects implementing the Comparable interface, - * which includes all standard data classes like String, Number derivatives - * and Dates). Any existing comparator can be removed by passing in a - * non-null GridColumn and a null Comparator. - * - * @param column - * a grid column. May not be null. - * @param comparator - * comparator method for the values returned by the grid column. - * If null, any existing comparator is removed. - */ - public void setComparator(Grid.Column column, - Comparator comparator) { - if (column == null) { - throw new IllegalArgumentException( - "Column reference can not be null"); - } - if (comparator == null) { - comparators.remove(column); - } else { - comparators.put(column, comparator); - } - } - - /** - * Retrieve the comparator assigned for a specific grid column. - * - * @param column - * a grid column. May not be null. - * @return a comparator, or null if no comparator for the specified grid - * column has been set. - */ - @SuppressWarnings("unchecked") - public Comparator getComparator(Grid.Column column) { - if (column == null) { - throw new IllegalArgumentException( - "Column reference can not be null"); - } - return (Comparator) comparators.get(column); - } - - /** - * Remove all comparator mappings. Useful if the data source has changed but - * this Sorter is being re-used. - */ - public void clearComparators() { - comparators.clear(); - } - - /** - * Apply sorting to the current ListDataSource. - * - * @param order - * the sort order list provided by the grid sort event - */ - private void sort(final List order) { - DataSource ds = grid.getDataSource(); - if (!(ds instanceof ListDataSource)) { - throw new IllegalStateException("Grid " + grid - + " data source is not a ListDataSource!"); - } - - ((ListDataSource) ds).sort(new Comparator() { - - @Override - @SuppressWarnings({ "rawtypes", "unchecked" }) - public int compare(T a, T b) { - - for (SortOrder o : order) { - - Grid.Column column = o.getColumn(); - Comparator cmp = ListSorter.this.comparators.get(column); - int result = 0; - Object value_a = column.getValue(a); - Object value_b = column.getValue(b); - if (cmp != null) { - result = cmp.compare(value_a, value_b); - } else { - if (!(value_a instanceof Comparable)) { - throw new IllegalStateException("Column " + column - + " has no assigned comparator and value " - + value_a + " isn't naturally comparable"); - } - result = ((Comparable) value_a).compareTo(value_b); - } - - if (result != 0) { - return o.getDirection() == SortDirection.ASCENDING ? result - : -result; - } - } - - if (order.size() > 0) { - return order.get(0).getDirection() == SortDirection.ASCENDING ? a - .hashCode() - b.hashCode() - : b.hashCode() - a.hashCode(); - } - return a.hashCode() - b.hashCode(); - } - }); - } -} diff --git a/client/src/com/vaadin/client/widget/grid/events/AbstractGridKeyEventHandler.java b/client/src/com/vaadin/client/widget/grid/events/AbstractGridKeyEventHandler.java deleted file mode 100644 index 120c32d380..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/AbstractGridKeyEventHandler.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.event.shared.EventHandler; -import com.vaadin.client.widgets.Grid.AbstractGridKeyEvent; - -/** - * Base interface of all handlers for {@link AbstractGridKeyEvent}s. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public abstract interface AbstractGridKeyEventHandler extends EventHandler { - - public abstract interface GridKeyDownHandler extends - AbstractGridKeyEventHandler { - public void onKeyDown(GridKeyDownEvent event); - } - - public abstract interface GridKeyUpHandler extends - AbstractGridKeyEventHandler { - public void onKeyUp(GridKeyUpEvent event); - } - - public abstract interface GridKeyPressHandler extends - AbstractGridKeyEventHandler { - public void onKeyPress(GridKeyPressEvent event); - } - -} diff --git a/client/src/com/vaadin/client/widget/grid/events/AbstractGridMouseEventHandler.java b/client/src/com/vaadin/client/widget/grid/events/AbstractGridMouseEventHandler.java deleted file mode 100644 index 15e22a6d57..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/AbstractGridMouseEventHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.event.shared.EventHandler; -import com.vaadin.client.widgets.Grid.AbstractGridMouseEvent; - -/** - * Base interface of all handlers for {@link AbstractGridMouseEvent}s. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public abstract interface AbstractGridMouseEventHandler extends EventHandler { - - public abstract interface GridClickHandler extends - AbstractGridMouseEventHandler { - public void onClick(GridClickEvent event); - } - - public abstract interface GridDoubleClickHandler extends - AbstractGridMouseEventHandler { - public void onDoubleClick(GridDoubleClickEvent event); - } - -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/BodyClickHandler.java b/client/src/com/vaadin/client/widget/grid/events/BodyClickHandler.java deleted file mode 100644 index a66e170524..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/BodyClickHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridClickHandler; - -/** - * Handler for {@link GridClickEvent}s that happen in the body of the Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface BodyClickHandler extends GridClickHandler { - -} diff --git a/client/src/com/vaadin/client/widget/grid/events/BodyDoubleClickHandler.java b/client/src/com/vaadin/client/widget/grid/events/BodyDoubleClickHandler.java deleted file mode 100644 index 7be29920e7..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/BodyDoubleClickHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridDoubleClickHandler; - -/** - * Handler for {@link GridDoubleClickEvent}s that happen in the body of the - * Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface BodyDoubleClickHandler extends GridDoubleClickHandler { - -} diff --git a/client/src/com/vaadin/client/widget/grid/events/BodyKeyDownHandler.java b/client/src/com/vaadin/client/widget/grid/events/BodyKeyDownHandler.java deleted file mode 100644 index ff1ae82d2e..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/BodyKeyDownHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyDownHandler; - -/** - * Handler for {@link GridKeyDownEvent}s that happen when the focused cell is in - * the body of the Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface BodyKeyDownHandler extends GridKeyDownHandler { -} diff --git a/client/src/com/vaadin/client/widget/grid/events/BodyKeyPressHandler.java b/client/src/com/vaadin/client/widget/grid/events/BodyKeyPressHandler.java deleted file mode 100644 index 245250d4c0..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/BodyKeyPressHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyPressHandler; - -/** - * Handler for {@link GridKeyPressEvent}s that happen when the focused cell is - * in the body of the Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface BodyKeyPressHandler extends GridKeyPressHandler { -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/BodyKeyUpHandler.java b/client/src/com/vaadin/client/widget/grid/events/BodyKeyUpHandler.java deleted file mode 100644 index 2c0951ea40..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/BodyKeyUpHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyUpHandler; - -/** - * Handler for {@link GridKeyUpEvent}s that happen when the focused cell is in - * the body of the Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface BodyKeyUpHandler extends GridKeyUpHandler { -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java deleted file mode 100644 index 1712871089..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.event.shared.GwtEvent; - -/** - * An event for notifying that the columns in the Grid have been reordered. - * - * @param - * The row type of the grid. The row type is the POJO type from where - * the data is retrieved into the column cells. - * @since 7.5.0 - * @author Vaadin Ltd - */ -public class ColumnReorderEvent extends GwtEvent> { - - /** - * Handler type. - */ - private final static Type> TYPE = new Type>(); - - public static final Type> getType() { - return TYPE; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public Type> getAssociatedType() { - return (Type) TYPE; - } - - @Override - protected void dispatch(ColumnReorderHandler handler) { - handler.onColumnReorder(this); - } - -} diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java b/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java deleted file mode 100644 index 29c476058e..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.event.shared.EventHandler; - -/** - * Handler for a Grid column reorder event, called when the Grid's columns has - * been reordered. - * - * @param - * The row type of the grid. The row type is the POJO type from where - * the data is retrieved into the column cells. - * @since 7.5.0 - * @author Vaadin Ltd - */ -public interface ColumnReorderHandler extends EventHandler { - - /** - * A column reorder event, fired by Grid when the columns of the Grid have - * been reordered. - * - * @param event - * column reorder event - */ - public void onColumnReorder(ColumnReorderEvent event); -} diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnResizeEvent.java b/client/src/com/vaadin/client/widget/grid/events/ColumnResizeEvent.java deleted file mode 100644 index f5c8c0fa83..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/ColumnResizeEvent.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.event.shared.GwtEvent; -import com.vaadin.client.widgets.Grid.Column; - -/** - * An event for notifying that the columns in the Grid have been resized. - * - * @param - * The row type of the grid. The row type is the POJO type from where - * the data is retrieved into the column cells. - * @since 7.6 - * @author Vaadin Ltd - */ -public class ColumnResizeEvent extends GwtEvent> { - - /** - * Handler type. - */ - private final static Type> TYPE = new Type>(); - - private Column column; - - /** - * @param column - */ - public ColumnResizeEvent(Column column) { - this.column = column; - } - - public static final Type> getType() { - return TYPE; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public Type> getAssociatedType() { - return (Type) TYPE; - } - - @Override - protected void dispatch(ColumnResizeHandler handler) { - handler.onColumnResize(this); - } - - /** - * @return the column - */ - public Column getColumn() { - return column; - } -} diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnResizeHandler.java b/client/src/com/vaadin/client/widget/grid/events/ColumnResizeHandler.java deleted file mode 100644 index a66dbf7bd2..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/ColumnResizeHandler.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.event.shared.EventHandler; - -/** - * Handler for a Grid column resize event, called when the Grid's columns has - * been resized. - * - * @param - * The row type of the grid. The row type is the POJO type from where - * the data is retrieved into the column cells. - * @since 7.6 - * @author Vaadin Ltd - */ -public interface ColumnResizeHandler extends EventHandler { - - /** - * A column resize event, fired by Grid when the columns of the Grid have - * been resized. - * - * @param event - * column resize event - */ - public void onColumnResize(ColumnResizeEvent event); -} diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java deleted file mode 100644 index 63b788bcf2..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.event.shared.GwtEvent; -import com.vaadin.client.widgets.Grid.Column; - -/** - * An event for notifying that the columns in the Grid's have changed - * visibility. - * - * @param - * The row type of the grid. The row type is the POJO type from where - * the data is retrieved into the column cells. - * @since 7.5.0 - * @author Vaadin Ltd - */ -public class ColumnVisibilityChangeEvent extends - GwtEvent> { - - private final static Type> TYPE = new Type>(); - - public static final Type> getType() { - return TYPE; - } - - private final Column column; - - private final boolean userOriginated; - - private final boolean hidden; - - public ColumnVisibilityChangeEvent(Column column, boolean hidden, - boolean userOriginated) { - this.column = column; - this.hidden = hidden; - this.userOriginated = userOriginated; - } - - /** - * Returns the column where the visibility change occurred. - * - * @return the column where the visibility change occurred. - */ - public Column getColumn() { - return column; - } - - /** - * Was the column set hidden or visible. - * - * @return true if the column was hidden false if - * it was set visible - */ - public boolean isHidden() { - return hidden; - } - - /** - * Is the visibility change triggered by user. - * - * @return true if the change was triggered by user, - * false if not - */ - public boolean isUserOriginated() { - return userOriginated; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public com.google.gwt.event.shared.GwtEvent.Type> getAssociatedType() { - return (Type) TYPE; - } - - @Override - protected void dispatch(ColumnVisibilityChangeHandler handler) { - handler.onVisibilityChange(this); - } - -} diff --git a/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java b/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java deleted file mode 100644 index 542fe4e3c1..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.event.shared.EventHandler; - -/** - * Handler for a Grid column visibility change event, called when the Grid's - * columns have changed visibility to hidden or visible. - * - * @param The row type of the grid. The row type is the POJO type from where - * the data is retrieved into the column cells. - * @since 7.5.0 - * @author Vaadin Ltd - */ -public interface ColumnVisibilityChangeHandler extends EventHandler { - - /** - * A column visibility change event, fired by Grid when a column in the Grid - * has changed visibility. - * - * @param event - * column visibility change event - */ - public void onVisibilityChange(ColumnVisibilityChangeEvent event); -} diff --git a/client/src/com/vaadin/client/widget/grid/events/FooterClickHandler.java b/client/src/com/vaadin/client/widget/grid/events/FooterClickHandler.java deleted file mode 100644 index 51fa38c948..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/FooterClickHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridClickHandler; - -/** - * Handler for {@link GridClickEvent}s that happen in the footer of the Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface FooterClickHandler extends GridClickHandler { - -} diff --git a/client/src/com/vaadin/client/widget/grid/events/FooterDoubleClickHandler.java b/client/src/com/vaadin/client/widget/grid/events/FooterDoubleClickHandler.java deleted file mode 100644 index 2f5ba21787..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/FooterDoubleClickHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridDoubleClickHandler; - -/** - * Handler for {@link GridDoubleClickEvent}s that happen in the footer of the - * Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface FooterDoubleClickHandler extends GridDoubleClickHandler { - -} diff --git a/client/src/com/vaadin/client/widget/grid/events/FooterKeyDownHandler.java b/client/src/com/vaadin/client/widget/grid/events/FooterKeyDownHandler.java deleted file mode 100644 index 85f83970f2..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/FooterKeyDownHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyDownHandler; - -/** - * Handler for {@link GridKeyDownEvent}s that happen when the focused cell is in - * the footer of the Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface FooterKeyDownHandler extends GridKeyDownHandler { -} diff --git a/client/src/com/vaadin/client/widget/grid/events/FooterKeyPressHandler.java b/client/src/com/vaadin/client/widget/grid/events/FooterKeyPressHandler.java deleted file mode 100644 index 09778f6873..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/FooterKeyPressHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyPressHandler; - -/** - * Handler for {@link GridKeyPressEvent}s that happen when the focused cell is - * in the footer of the Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface FooterKeyPressHandler extends GridKeyPressHandler { -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/FooterKeyUpHandler.java b/client/src/com/vaadin/client/widget/grid/events/FooterKeyUpHandler.java deleted file mode 100644 index 688f89880f..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/FooterKeyUpHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyUpHandler; - -/** - * Handler for {@link GridKeyUpEvent}s that happen when the focused cell is in - * the footer of the Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface FooterKeyUpHandler extends GridKeyUpHandler { -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/GridClickEvent.java b/client/src/com/vaadin/client/widget/grid/events/GridClickEvent.java deleted file mode 100644 index 33a4c923b7..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/GridClickEvent.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.dom.client.BrowserEvents; -import com.vaadin.client.widget.grid.CellReference; -import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridClickHandler; -import com.vaadin.client.widgets.Grid; -import com.vaadin.client.widgets.Grid.AbstractGridMouseEvent; -import com.vaadin.shared.ui.grid.GridConstants.Section; - -/** - * Represents native mouse click event in Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class GridClickEvent extends AbstractGridMouseEvent { - - public GridClickEvent(Grid grid, CellReference targetCell) { - super(grid, targetCell); - } - - @Override - protected String getBrowserEventType() { - return BrowserEvents.CLICK; - } - - @Override - protected void doDispatch(GridClickHandler handler, Section section) { - if ((section == Section.BODY && handler instanceof BodyClickHandler) - || (section == Section.HEADER && handler instanceof HeaderClickHandler) - || (section == Section.FOOTER && handler instanceof FooterClickHandler)) { - handler.onClick(this); - } - } -} diff --git a/client/src/com/vaadin/client/widget/grid/events/GridDoubleClickEvent.java b/client/src/com/vaadin/client/widget/grid/events/GridDoubleClickEvent.java deleted file mode 100644 index 64a1a88b42..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/GridDoubleClickEvent.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.dom.client.BrowserEvents; -import com.vaadin.client.widget.grid.CellReference; -import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridDoubleClickHandler; -import com.vaadin.client.widgets.Grid; -import com.vaadin.client.widgets.Grid.AbstractGridMouseEvent; -import com.vaadin.shared.ui.grid.GridConstants.Section; - -/** - * Represents native mouse double click event in Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class GridDoubleClickEvent extends - AbstractGridMouseEvent { - - public GridDoubleClickEvent(Grid grid, CellReference targetCell) { - super(grid, targetCell); - } - - @Override - protected String getBrowserEventType() { - return BrowserEvents.DBLCLICK; - } - - @Override - protected void doDispatch(GridDoubleClickHandler handler, Section section) { - if ((section == Section.BODY && handler instanceof BodyDoubleClickHandler) - || (section == Section.HEADER && handler instanceof HeaderDoubleClickHandler) - || (section == Section.FOOTER && handler instanceof FooterDoubleClickHandler)) { - handler.onDoubleClick(this); - } - } - -} diff --git a/client/src/com/vaadin/client/widget/grid/events/GridKeyDownEvent.java b/client/src/com/vaadin/client/widget/grid/events/GridKeyDownEvent.java deleted file mode 100644 index 9849137982..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/GridKeyDownEvent.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.dom.client.BrowserEvents; -import com.google.gwt.event.dom.client.KeyCodes; -import com.vaadin.client.widget.grid.CellReference; -import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyDownHandler; -import com.vaadin.client.widgets.Grid; -import com.vaadin.client.widgets.Grid.AbstractGridKeyEvent; -import com.vaadin.shared.ui.grid.GridConstants.Section; - -/** - * Represents native key down event in Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class GridKeyDownEvent extends AbstractGridKeyEvent { - - public GridKeyDownEvent(Grid grid, CellReference targetCell) { - super(grid, targetCell); - } - - @Override - protected void doDispatch(GridKeyDownHandler handler, Section section) { - if ((section == Section.BODY && handler instanceof BodyKeyDownHandler) - || (section == Section.HEADER && handler instanceof HeaderKeyDownHandler) - || (section == Section.FOOTER && handler instanceof FooterKeyDownHandler)) { - handler.onKeyDown(this); - } - } - - @Override - protected String getBrowserEventType() { - return BrowserEvents.KEYDOWN; - } - - /** - * Does the key code represent an arrow key? - * - * @param keyCode - * the key code - * @return if it is an arrow key code - */ - public static boolean isArrow(int keyCode) { - switch (keyCode) { - case KeyCodes.KEY_DOWN: - case KeyCodes.KEY_RIGHT: - case KeyCodes.KEY_UP: - case KeyCodes.KEY_LEFT: - return true; - default: - return false; - } - } - - /** - * Gets the native key code. These key codes are enumerated in the - * {@link KeyCodes} class. - * - * @return the key code - */ - public int getNativeKeyCode() { - return getNativeEvent().getKeyCode(); - } - - /** - * Is this a key down arrow? - * - * @return whether this is a down arrow key event - */ - public boolean isDownArrow() { - return getNativeKeyCode() == KeyCodes.KEY_DOWN; - } - - /** - * Is this a left arrow? - * - * @return whether this is a left arrow key event - */ - public boolean isLeftArrow() { - return getNativeKeyCode() == KeyCodes.KEY_LEFT; - } - - /** - * Is this a right arrow? - * - * @return whether this is a right arrow key event - */ - public boolean isRightArrow() { - return getNativeKeyCode() == KeyCodes.KEY_RIGHT; - } - - /** - * Is this a up arrow? - * - * @return whether this is a right arrow key event - */ - public boolean isUpArrow() { - return getNativeKeyCode() == KeyCodes.KEY_UP; - } - - @Override - public String toDebugString() { - return super.toDebugString() + "[" + getNativeKeyCode() + "]"; - } -} diff --git a/client/src/com/vaadin/client/widget/grid/events/GridKeyPressEvent.java b/client/src/com/vaadin/client/widget/grid/events/GridKeyPressEvent.java deleted file mode 100644 index 35a3af0c2e..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/GridKeyPressEvent.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.dom.client.BrowserEvents; -import com.vaadin.client.widget.grid.CellReference; -import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyPressHandler; -import com.vaadin.client.widgets.Grid; -import com.vaadin.client.widgets.Grid.AbstractGridKeyEvent; -import com.vaadin.shared.ui.grid.GridConstants.Section; - -/** - * Represents native key press event in Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class GridKeyPressEvent extends - AbstractGridKeyEvent { - - public GridKeyPressEvent(Grid grid, CellReference targetCell) { - super(grid, targetCell); - } - - @Override - protected void doDispatch(GridKeyPressHandler handler, Section section) { - if ((section == Section.BODY && handler instanceof BodyKeyPressHandler) - || (section == Section.HEADER && handler instanceof HeaderKeyPressHandler) - || (section == Section.FOOTER && handler instanceof FooterKeyPressHandler)) { - handler.onKeyPress(this); - } - } - - @Override - protected String getBrowserEventType() { - return BrowserEvents.KEYPRESS; - } - - /** - * Gets the char code for this event. - * - * @return the char code - */ - public char getCharCode() { - return (char) getUnicodeCharCode(); - } - - /** - * Gets the Unicode char code (code point) for this event. - * - * @return the Unicode char code - */ - public int getUnicodeCharCode() { - return getNativeEvent().getCharCode(); - } - - @Override - public String toDebugString() { - return super.toDebugString() + "[" + getCharCode() + "]"; - } -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/GridKeyUpEvent.java b/client/src/com/vaadin/client/widget/grid/events/GridKeyUpEvent.java deleted file mode 100644 index d273835233..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/GridKeyUpEvent.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.dom.client.BrowserEvents; -import com.google.gwt.event.dom.client.KeyCodes; -import com.vaadin.client.widget.grid.CellReference; -import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyUpHandler; -import com.vaadin.client.widgets.Grid; -import com.vaadin.client.widgets.Grid.AbstractGridKeyEvent; -import com.vaadin.shared.ui.grid.GridConstants.Section; - -/** - * Represents native key up event in Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class GridKeyUpEvent extends AbstractGridKeyEvent { - - public GridKeyUpEvent(Grid grid, CellReference targetCell) { - super(grid, targetCell); - } - - @Override - protected void doDispatch(GridKeyUpHandler handler, Section section) { - if ((section == Section.BODY && handler instanceof BodyKeyUpHandler) - || (section == Section.HEADER && handler instanceof HeaderKeyUpHandler) - || (section == Section.FOOTER && handler instanceof FooterKeyUpHandler)) { - handler.onKeyUp(this); - } - } - - @Override - protected String getBrowserEventType() { - return BrowserEvents.KEYUP; - } - - /** - * Does the key code represent an arrow key? - * - * @param keyCode - * the key code - * @return if it is an arrow key code - */ - public static boolean isArrow(int keyCode) { - switch (keyCode) { - case KeyCodes.KEY_DOWN: - case KeyCodes.KEY_RIGHT: - case KeyCodes.KEY_UP: - case KeyCodes.KEY_LEFT: - return true; - default: - return false; - } - } - - /** - * Gets the native key code. These key codes are enumerated in the - * {@link KeyCodes} class. - * - * @return the key code - */ - public int getNativeKeyCode() { - return getNativeEvent().getKeyCode(); - } - - /** - * Is this a key down arrow? - * - * @return whether this is a down arrow key event - */ - public boolean isDownArrow() { - return getNativeKeyCode() == KeyCodes.KEY_DOWN; - } - - /** - * Is this a left arrow? - * - * @return whether this is a left arrow key event - */ - public boolean isLeftArrow() { - return getNativeKeyCode() == KeyCodes.KEY_LEFT; - } - - /** - * Is this a right arrow? - * - * @return whether this is a right arrow key event - */ - public boolean isRightArrow() { - return getNativeKeyCode() == KeyCodes.KEY_RIGHT; - } - - /** - * Is this a up arrow? - * - * @return whether this is a right arrow key event - */ - public boolean isUpArrow() { - return getNativeKeyCode() == KeyCodes.KEY_UP; - } - - @Override - public String toDebugString() { - return super.toDebugString() + "[" + getNativeKeyCode() + "]"; - } -} diff --git a/client/src/com/vaadin/client/widget/grid/events/HeaderClickHandler.java b/client/src/com/vaadin/client/widget/grid/events/HeaderClickHandler.java deleted file mode 100644 index da20e80905..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/HeaderClickHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridClickHandler; - -/** - * Handler for {@link GridClickEvent}s that happen in the header of the Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface HeaderClickHandler extends GridClickHandler { - -} diff --git a/client/src/com/vaadin/client/widget/grid/events/HeaderDoubleClickHandler.java b/client/src/com/vaadin/client/widget/grid/events/HeaderDoubleClickHandler.java deleted file mode 100644 index 16a4cfe1f5..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/HeaderDoubleClickHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridDoubleClickHandler; - -/** - * Handler for {@link GridDoubleClickEvent}s that happen in the header of the - * Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface HeaderDoubleClickHandler extends GridDoubleClickHandler { - -} diff --git a/client/src/com/vaadin/client/widget/grid/events/HeaderKeyDownHandler.java b/client/src/com/vaadin/client/widget/grid/events/HeaderKeyDownHandler.java deleted file mode 100644 index 555eb936af..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/HeaderKeyDownHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyDownHandler; - -/** - * Handler for {@link GridKeyDownEvent}s that happen when the focused cell is in - * the header of the Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface HeaderKeyDownHandler extends GridKeyDownHandler { -} diff --git a/client/src/com/vaadin/client/widget/grid/events/HeaderKeyPressHandler.java b/client/src/com/vaadin/client/widget/grid/events/HeaderKeyPressHandler.java deleted file mode 100644 index c4dd312f93..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/HeaderKeyPressHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyPressHandler; - -/** - * Handler for {@link GridKeyPressEvent}s that happen when the focused cell is - * in the header of the Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface HeaderKeyPressHandler extends GridKeyPressHandler { -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/HeaderKeyUpHandler.java b/client/src/com/vaadin/client/widget/grid/events/HeaderKeyUpHandler.java deleted file mode 100644 index 4dbe1c681e..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/HeaderKeyUpHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyUpHandler; - -/** - * Handler for {@link GridKeyUpEvent}s that happen when the focused cell is in - * the header of the Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface HeaderKeyUpHandler extends GridKeyUpHandler { -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/ScrollEvent.java b/client/src/com/vaadin/client/widget/grid/events/ScrollEvent.java deleted file mode 100644 index 08e1e07eab..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/ScrollEvent.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.event.shared.GwtEvent; - -/** - * An event that signifies that a scrollbar bundle has been scrolled - * - * @author Vaadin Ltd - * @since 7.4 - */ -public class ScrollEvent extends GwtEvent { - - /** The type of this event */ - public static final Type TYPE = new Type(); - - @Override - public Type getAssociatedType() { - return TYPE; - } - - @Override - protected void dispatch(final ScrollHandler handler) { - handler.onScroll(this); - } -} diff --git a/client/src/com/vaadin/client/widget/grid/events/ScrollHandler.java b/client/src/com/vaadin/client/widget/grid/events/ScrollHandler.java deleted file mode 100644 index 1ce901e707..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/ScrollHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.event.shared.EventHandler; - -/** - * A handler that gets called whenever a scrollbar bundle is scrolled - * - * @author Vaadin Ltd - * @since 7.4 - */ -public interface ScrollHandler extends EventHandler { - /** - * A callback method that is called once a scrollbar bundle has been - * scrolled. - * - * @param event - * the scroll event - */ - public void onScroll(ScrollEvent event); -} diff --git a/client/src/com/vaadin/client/widget/grid/events/SelectAllEvent.java b/client/src/com/vaadin/client/widget/grid/events/SelectAllEvent.java deleted file mode 100644 index 43c2055e95..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/SelectAllEvent.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.event.shared.GwtEvent; -import com.vaadin.client.widget.grid.selection.SelectionModel; - -/** - * A select all event, fired by the Grid when it needs all rows in data source - * to be selected. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class SelectAllEvent extends GwtEvent> { - - /** - * Handler type. - */ - private final static Type> TYPE = new Type>();; - - private SelectionModel.Multi selectionModel; - - public SelectAllEvent(SelectionModel.Multi selectionModel) { - this.selectionModel = selectionModel; - } - - public static final Type> getType() { - return TYPE; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public Type> getAssociatedType() { - return (Type) TYPE; - } - - @Override - protected void dispatch(SelectAllHandler handler) { - handler.onSelectAll(this); - } - - public SelectionModel.Multi getSelectionModel() { - return selectionModel; - } -} diff --git a/client/src/com/vaadin/client/widget/grid/events/SelectAllHandler.java b/client/src/com/vaadin/client/widget/grid/events/SelectAllHandler.java deleted file mode 100644 index 2cdee8d1b3..0000000000 --- a/client/src/com/vaadin/client/widget/grid/events/SelectAllHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.events; - -import com.google.gwt.event.shared.EventHandler; - -/** - * Handler for a Grid select all event, called when the Grid needs all rows in - * data source to be selected. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface SelectAllHandler extends EventHandler { - - /** - * Called when select all value in SelectionColumn header changes value. - * - * @param event - * select all event telling that all rows should be selected - */ - public void onSelectAll(SelectAllEvent event); - -} diff --git a/client/src/com/vaadin/client/widget/grid/selection/AbstractRowHandleSelectionModel.java b/client/src/com/vaadin/client/widget/grid/selection/AbstractRowHandleSelectionModel.java deleted file mode 100644 index 6b7bbb6294..0000000000 --- a/client/src/com/vaadin/client/widget/grid/selection/AbstractRowHandleSelectionModel.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.selection; - -import com.vaadin.client.data.DataSource.RowHandle; - -/** - * An abstract class that adds a consistent API for common methods that's needed - * by Vaadin's server-based selection models to work. - *

- * Note: This should be an interface instead of an abstract class, if - * only we could define protected methods in an interface. - * - * @author Vaadin Ltd - * @param - * The grid's row type - * @since 7.4 - */ -public abstract class AbstractRowHandleSelectionModel implements - SelectionModel { - /** - * Select a row, based on its - * {@link com.vaadin.client.data.DataSource.RowHandle RowHandle}. - *

- * Note: this method may not fire selection change events. - * - * @param handle - * the handle to select by - * @return true iff the selection state was changed by this - * call - * @throws UnsupportedOperationException - * if the selection model does not support either handles or - * selection - */ - protected abstract boolean selectByHandle(RowHandle handle); - - /** - * Deselect a row, based on its - * {@link com.vaadin.client.data.DataSource.RowHandle RowHandle}. - *

- * Note: this method may not fire selection change events. - * - * @param handle - * the handle to deselect by - * @return true iff the selection state was changed by this - * call - * @throws UnsupportedOperationException - * if the selection model does not support either handles or - * deselection - */ - protected abstract boolean deselectByHandle(RowHandle handle) - throws UnsupportedOperationException; -} diff --git a/client/src/com/vaadin/client/widget/grid/selection/ClickSelectHandler.java b/client/src/com/vaadin/client/widget/grid/selection/ClickSelectHandler.java deleted file mode 100644 index c6bc52dd1c..0000000000 --- a/client/src/com/vaadin/client/widget/grid/selection/ClickSelectHandler.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.selection; - -import com.google.gwt.event.shared.HandlerRegistration; -import com.vaadin.client.widget.grid.events.BodyClickHandler; -import com.vaadin.client.widget.grid.events.GridClickEvent; -import com.vaadin.client.widgets.Grid; - -/** - * Generic class to perform selections when clicking on cells in body of Grid. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class ClickSelectHandler { - - private Grid grid; - private HandlerRegistration clickHandler; - private boolean deselectAllowed = true; - - private class RowClickHandler implements BodyClickHandler { - - @Override - public void onClick(GridClickEvent event) { - T row = (T) event.getTargetCell().getRow(); - if (!grid.isSelected(row)) { - grid.select(row); - } else if (deselectAllowed) { - grid.deselect(row); - } - } - } - - /** - * Constructor for ClickSelectHandler. This constructor will add all - * necessary handlers for selecting rows by clicking cells. - * - * @param grid - * grid to attach to - */ - public ClickSelectHandler(Grid grid) { - this.grid = grid; - clickHandler = grid.addBodyClickHandler(new RowClickHandler()); - } - - /** - * Clean up function for removing all now obsolete handlers. - */ - public void removeHandler() { - clickHandler.removeHandler(); - } - - /** - * Sets whether clicking the currently selected row should deselect the row. - * - * @param deselectAllowed - * true to allow deselecting the selected row; - * otherwise false - */ - public void setDeselectAllowed(boolean deselectAllowed) { - this.deselectAllowed = deselectAllowed; - } -} diff --git a/client/src/com/vaadin/client/widget/grid/selection/HasSelectionHandlers.java b/client/src/com/vaadin/client/widget/grid/selection/HasSelectionHandlers.java deleted file mode 100644 index ffcad4c903..0000000000 --- a/client/src/com/vaadin/client/widget/grid/selection/HasSelectionHandlers.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.selection; - -import com.google.gwt.event.shared.HandlerRegistration; - -/** - * Marker interface for widgets that fires selection events. - * - * @author Vaadin Ltd - * @since 7.4 - */ -public interface HasSelectionHandlers { - - /** - * Register a selection change handler. - *

- * This handler is called whenever a - * {@link com.vaadin.ui.components.grid.selection.SelectionModel - * SelectionModel} detects a change in selection state. - * - * @param handler - * a {@link SelectionHandler} - * @return a handler registration object, which can be used to remove the - * handler. - */ - public HandlerRegistration addSelectionHandler(SelectionHandler handler); - -} diff --git a/client/src/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java b/client/src/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java deleted file mode 100644 index c64908f24c..0000000000 --- a/client/src/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java +++ /dev/null @@ -1,763 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.selection; - -import java.util.Collection; -import java.util.HashSet; - -import com.google.gwt.animation.client.AnimationScheduler; -import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback; -import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle; -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.BrowserEvents; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.TableElement; -import com.google.gwt.dom.client.TableSectionElement; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.MouseDownEvent; -import com.google.gwt.event.dom.client.MouseDownHandler; -import com.google.gwt.event.dom.client.TouchStartEvent; -import com.google.gwt.event.dom.client.TouchStartHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Event.NativePreviewEvent; -import com.google.gwt.user.client.Event.NativePreviewHandler; -import com.google.gwt.user.client.ui.CheckBox; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.renderers.ClickableRenderer; -import com.vaadin.client.widget.grid.CellReference; -import com.vaadin.client.widget.grid.RendererCellReference; -import com.vaadin.client.widget.grid.selection.SelectionModel.Multi.Batched; -import com.vaadin.client.widgets.Grid; - -/** - * Renderer showing multi selection check boxes. - * - * @author Vaadin Ltd - * @param - * the type of the associated grid - * @since 7.4 - */ -public class MultiSelectionRenderer extends - ClickableRenderer { - - private static final String SELECTION_CHECKBOX_CLASSNAME = "-selection-checkbox"; - - /** The size of the autoscroll area, both top and bottom. */ - private static final int SCROLL_AREA_GRADIENT_PX = 100; - - /** The maximum number of pixels per second to autoscroll. */ - private static final int SCROLL_TOP_SPEED_PX_SEC = 500; - - /** - * The minimum area where the grid doesn't scroll while the pointer is - * pressed. - */ - private static final int MIN_NO_AUTOSCROLL_AREA_PX = 50; - - /** - * Handler for MouseDown and TouchStart events for selection checkboxes. - * - * @since 7.5 - */ - private final class CheckBoxEventHandler implements MouseDownHandler, - TouchStartHandler, ClickHandler { - private final CheckBox checkBox; - - /** - * @param checkBox - * checkbox widget for this handler - */ - private CheckBoxEventHandler(CheckBox checkBox) { - this.checkBox = checkBox; - } - - @Override - public void onMouseDown(MouseDownEvent event) { - if (event.getNativeButton() == NativeEvent.BUTTON_LEFT) { - startDragSelect(event.getNativeEvent(), checkBox.getElement()); - } - } - - @Override - public void onTouchStart(TouchStartEvent event) { - startDragSelect(event.getNativeEvent(), checkBox.getElement()); - } - - @Override - public void onClick(ClickEvent event) { - // Clicking is already handled with MultiSelectionRenderer - event.preventDefault(); - event.stopPropagation(); - } - } - - /** - * This class's main objective is to listen when to stop autoscrolling, and - * make sure everything stops accordingly. - */ - private class TouchEventHandler implements NativePreviewHandler { - @Override - public void onPreviewNativeEvent(final NativePreviewEvent event) { - switch (event.getTypeInt()) { - case Event.ONTOUCHSTART: { - if (event.getNativeEvent().getTouches().length() == 1) { - /* - * Something has dropped a touchend/touchcancel and the - * scroller is most probably running amok. Let's cancel it - * and pretend that everything's going as expected - * - * Because this is a preview, this code is run before the - * event handler in MultiSelectionRenderer.onBrowserEvent. - * Therefore, we can simply kill everything and let that - * method restart things as they should. - */ - autoScrollHandler.stop(); - - /* - * Related TODO: investigate why iOS seems to ignore a - * touchend/touchcancel when frames are dropped, and/or if - * something can be done about that. - */ - } - break; - } - - case Event.ONTOUCHMOVE: - event.cancel(); - break; - - case Event.ONTOUCHEND: - case Event.ONTOUCHCANCEL: - /* - * Remember: targetElement is always where touchstart started, - * not where the finger is pointing currently. - */ - final Element targetElement = Element.as(event.getNativeEvent() - .getEventTarget()); - if (isInFirstColumn(targetElement)) { - removeNativeHandler(); - event.cancel(); - } - break; - } - } - - private boolean isInFirstColumn(final Element element) { - if (element == null) { - return false; - } - final Element tbody = getTbodyElement(); - - if (tbody == null || !tbody.isOrHasChild(element)) { - return false; - } - - /* - * The null-parent in the while clause is in the case where element - * is an immediate tr child in the tbody. Should never happen in - * internal code, but hey... - */ - Element cursor = element; - while (cursor.getParentElement() != null - && cursor.getParentElement().getParentElement() != tbody) { - cursor = cursor.getParentElement(); - } - - final Element tr = cursor.getParentElement(); - return tr.getFirstChildElement().equals(cursor); - } - } - - /** - * This class's responsibility is to - *

    - *
  • scroll the table while a pointer is kept in a scrolling zone and - *
  • select rows whenever a pointer is "activated" on a selection cell - *
- *

- * Techical note: This class is an AnimationCallback because we - * need a timer: when the finger is kept in place while the grid scrolls, we - * still need to be able to make new selections. So, instead of relying on - * events (which won't be fired, since the pointer isn't necessarily - * moving), we do this check on each frame while the pointer is "active" - * (mouse is pressed, finger is on screen). - */ - private class AutoScrollerAndSelector implements AnimationCallback { - - /** - * If the acceleration gradient area is smaller than this, autoscrolling - * will be disabled (it becomes too quick to accelerate to be usable). - */ - private static final int GRADIENT_MIN_THRESHOLD_PX = 10; - - /** - * The speed at which the gradient area recovers, once scrolling in that - * direction has started. - */ - private static final int SCROLL_AREA_REBOUND_PX_PER_SEC = 1; - private static final double SCROLL_AREA_REBOUND_PX_PER_MS = SCROLL_AREA_REBOUND_PX_PER_SEC / 1000.0d; - - /** - * The lowest y-coordinate on the {@link Event#getClientY() client} from - * where we need to start scrolling towards the top. - */ - private int topBound = -1; - - /** - * The highest y-coordinate on the {@link Event#getClientY() client} - * from where we need to scrolling towards the bottom. - */ - private int bottomBound = -1; - - /** - * true if the pointer is selecting, false if - * the pointer is deselecting. - */ - private final boolean selectionPaint; - - /** - * The area where the selection acceleration takes place. If < - * {@link #GRADIENT_MIN_THRESHOLD_PX}, autoscrolling is disabled - */ - private final int gradientArea; - - /** - * The number of pixels per seconds we currently are scrolling (negative - * is towards the top, positive is towards the bottom). - */ - private double scrollSpeed = 0; - - private double prevTimestamp = 0; - - /** - * This field stores fractions of pixels to scroll, to make sure that - * we're able to scroll less than one px per frame. - */ - private double pixelsToScroll = 0.0d; - - /** Should this animator be running. */ - private boolean running = false; - - /** The handle in which this instance is running. */ - private AnimationHandle handle; - - /** The pointer's pageX coordinate of the first click. */ - private int initialPageX = -1; - - /** The pointer's pageY coordinate. */ - private int pageY; - - /** The logical index of the row that was most recently modified. */ - private int lastModifiedLogicalRow = -1; - - /** @see #doScrollAreaChecks(int) */ - private int finalTopBound; - - /** @see #doScrollAreaChecks(int) */ - private int finalBottomBound; - - private boolean scrollAreaShouldRebound = false; - - private final int bodyAbsoluteTop; - private final int bodyAbsoluteBottom; - - public AutoScrollerAndSelector(final int topBound, - final int bottomBound, final int gradientArea, - final boolean selectionPaint) { - finalTopBound = topBound; - finalBottomBound = bottomBound; - this.gradientArea = gradientArea; - this.selectionPaint = selectionPaint; - - bodyAbsoluteTop = getBodyClientTop(); - bodyAbsoluteBottom = getBodyClientBottom(); - } - - @Override - public void execute(final double timestamp) { - final double timeDiff = timestamp - prevTimestamp; - prevTimestamp = timestamp; - - reboundScrollArea(timeDiff); - - pixelsToScroll += scrollSpeed * (timeDiff / 1000.0d); - final int intPixelsToScroll = (int) pixelsToScroll; - pixelsToScroll -= intPixelsToScroll; - - if (intPixelsToScroll != 0) { - grid.setScrollTop(grid.getScrollTop() + intPixelsToScroll); - } - - int constrainedPageY = Math.max(bodyAbsoluteTop, - Math.min(bodyAbsoluteBottom, pageY)); - int logicalRow = getLogicalRowIndex(WidgetUtil.getElementFromPoint( - initialPageX, constrainedPageY)); - - int incrementOrDecrement = (logicalRow > lastModifiedLogicalRow) ? 1 - : -1; - - /* - * Both pageY and initialPageX have their initialized (and - * unupdated) values while the cursor hasn't moved since the first - * invocation. This will lead to logicalRow being -1, until the - * pointer has been moved. - */ - while (logicalRow != -1 && lastModifiedLogicalRow != logicalRow) { - lastModifiedLogicalRow += incrementOrDecrement; - setSelected(lastModifiedLogicalRow, selectionPaint); - } - - reschedule(); - } - - /** - * If the scroll are has been offset by the pointer starting out there, - * move it back a bit - */ - private void reboundScrollArea(double timeDiff) { - if (!scrollAreaShouldRebound) { - return; - } - - int reboundPx = (int) Math.ceil(SCROLL_AREA_REBOUND_PX_PER_MS - * timeDiff); - if (topBound < finalTopBound) { - topBound += reboundPx; - topBound = Math.min(topBound, finalTopBound); - updateScrollSpeed(pageY); - } else if (bottomBound > finalBottomBound) { - bottomBound -= reboundPx; - bottomBound = Math.max(bottomBound, finalBottomBound); - updateScrollSpeed(pageY); - } - } - - private void updateScrollSpeed(final int pointerPageY) { - - final double ratio; - if (pointerPageY < topBound) { - final double distance = pointerPageY - topBound; - ratio = Math.max(-1, distance / gradientArea); - } - - else if (pointerPageY > bottomBound) { - final double distance = pointerPageY - bottomBound; - ratio = Math.min(1, distance / gradientArea); - } - - else { - ratio = 0; - } - - scrollSpeed = ratio * SCROLL_TOP_SPEED_PX_SEC; - } - - public void start(int logicalRowIndex) { - running = true; - setSelected(logicalRowIndex, selectionPaint); - lastModifiedLogicalRow = logicalRowIndex; - reschedule(); - } - - public void stop() { - running = false; - - if (handle != null) { - handle.cancel(); - handle = null; - } - } - - private void reschedule() { - if (running && gradientArea >= GRADIENT_MIN_THRESHOLD_PX) { - handle = AnimationScheduler.get().requestAnimationFrame(this, - grid.getElement()); - } - } - - public void updatePointerCoords(int pageX, int pageY) { - doScrollAreaChecks(pageY); - updateScrollSpeed(pageY); - this.pageY = pageY; - - if (initialPageX == -1) { - initialPageX = pageX; - } - } - - /** - * This method checks whether the first pointer event started in an area - * that would start scrolling immediately, and does some actions - * accordingly. - *

- * If it is, that scroll area will be offset "beyond" the pointer (above - * if pointer is towards the top, otherwise below). - *

- * *) This behavior will change in - * future patches (henrik paul 2.7.2014) - */ - private void doScrollAreaChecks(int pageY) { - /* - * The first run makes sure that neither scroll position is - * underneath the finger, but offset to either direction from - * underneath the pointer. - */ - if (topBound == -1) { - topBound = Math.min(finalTopBound, pageY); - bottomBound = Math.max(finalBottomBound, pageY); - } - - /* - * Subsequent runs make sure that the scroll area grows (but doesn't - * shrink) with the finger, but no further than the final bound. - */ - else { - int oldTopBound = topBound; - if (topBound < finalTopBound) { - topBound = Math.max(topBound, - Math.min(finalTopBound, pageY)); - } - - int oldBottomBound = bottomBound; - if (bottomBound > finalBottomBound) { - bottomBound = Math.min(bottomBound, - Math.max(finalBottomBound, pageY)); - } - - final boolean topDidNotMove = oldTopBound == topBound; - final boolean bottomDidNotMove = oldBottomBound == bottomBound; - final boolean wasVerticalMovement = pageY != this.pageY; - scrollAreaShouldRebound = (topDidNotMove && bottomDidNotMove && wasVerticalMovement); - } - } - } - - /** - * This class makes sure that pointer movemenets are registered and - * delegated to the autoscroller so that it can: - *

    - *
  • modify the speed in which we autoscroll. - *
  • "paint" a new row with the selection. - *
- * Essentially, when a pointer is pressed on the selection column, a native - * preview handler is registered (so that selection gestures can happen - * outside of the selection column). The handler itself makes sure that it's - * detached when the pointer is "lifted". - */ - private class AutoScrollHandler { - private AutoScrollerAndSelector autoScroller; - - /** The registration info for {@link #scrollPreviewHandler} */ - private HandlerRegistration handlerRegistration; - - private final NativePreviewHandler scrollPreviewHandler = new NativePreviewHandler() { - @Override - public void onPreviewNativeEvent(final NativePreviewEvent event) { - if (autoScroller == null) { - stop(); - return; - } - - final NativeEvent nativeEvent = event.getNativeEvent(); - int pageY = 0; - int pageX = 0; - switch (event.getTypeInt()) { - case Event.ONMOUSEMOVE: - case Event.ONTOUCHMOVE: - pageY = WidgetUtil.getTouchOrMouseClientY(nativeEvent); - pageX = WidgetUtil.getTouchOrMouseClientX(nativeEvent); - autoScroller.updatePointerCoords(pageX, pageY); - break; - case Event.ONMOUSEUP: - case Event.ONTOUCHEND: - case Event.ONTOUCHCANCEL: - stop(); - break; - } - } - }; - - /** - * The top bound, as calculated from the {@link Event#getClientY() - * client} coordinates. - */ - private int topBound = -1; - - /** - * The bottom bound, as calculated from the {@link Event#getClientY() - * client} coordinates. - */ - private int bottomBound = -1; - - /** The size of the autoscroll acceleration area. */ - private int gradientArea; - - public void start(int logicalRowIndex) { - - SelectionModel model = grid.getSelectionModel(); - if (model instanceof Batched) { - Batched batchedModel = (Batched) model; - batchedModel.startBatchSelect(); - } - - /* - * bounds are updated whenever the autoscroll cycle starts, to make - * sure that the widget hasn't changed in size, moved around, or - * whatnot. - */ - updateScrollBounds(); - - assert handlerRegistration == null : "handlerRegistration was not null"; - assert autoScroller == null : "autoScroller was not null"; - handlerRegistration = Event - .addNativePreviewHandler(scrollPreviewHandler); - - autoScroller = new AutoScrollerAndSelector(topBound, bottomBound, - gradientArea, !isSelected(logicalRowIndex)); - autoScroller.start(logicalRowIndex); - } - - private void updateScrollBounds() { - final int topBorder = getBodyClientTop(); - final int bottomBorder = getBodyClientBottom(); - - topBound = topBorder + SCROLL_AREA_GRADIENT_PX; - bottomBound = bottomBorder - SCROLL_AREA_GRADIENT_PX; - gradientArea = SCROLL_AREA_GRADIENT_PX; - - // modify bounds if they're too tightly packed - if (bottomBound - topBound < MIN_NO_AUTOSCROLL_AREA_PX) { - int adjustment = MIN_NO_AUTOSCROLL_AREA_PX - - (bottomBound - topBound); - topBound -= adjustment / 2; - bottomBound += adjustment / 2; - gradientArea -= adjustment / 2; - } - } - - public void stop() { - if (handlerRegistration != null) { - handlerRegistration.removeHandler(); - handlerRegistration = null; - } - - if (autoScroller != null) { - autoScroller.stop(); - autoScroller = null; - } - - SelectionModel model = grid.getSelectionModel(); - if (model instanceof Batched) { - Batched batchedModel = (Batched) model; - batchedModel.commitBatchSelect(); - } - - removeNativeHandler(); - } - } - - private static final String LOGICAL_ROW_PROPERTY_INT = "vEscalatorLogicalRow"; - - private final Grid grid; - private HandlerRegistration nativePreviewHandlerRegistration; - - private final AutoScrollHandler autoScrollHandler = new AutoScrollHandler(); - - public MultiSelectionRenderer(final Grid grid) { - this.grid = grid; - } - - @Override - public void destroy() { - if (nativePreviewHandlerRegistration != null) { - removeNativeHandler(); - } - } - - @Override - public CheckBox createWidget() { - final CheckBox checkBox = GWT.create(CheckBox.class); - checkBox.setStylePrimaryName(grid.getStylePrimaryName() - + SELECTION_CHECKBOX_CLASSNAME); - CheckBoxEventHandler handler = new CheckBoxEventHandler(checkBox); - - // Sink events - checkBox.sinkBitlessEvent(BrowserEvents.MOUSEDOWN); - checkBox.sinkBitlessEvent(BrowserEvents.TOUCHSTART); - checkBox.sinkBitlessEvent(BrowserEvents.CLICK); - - // Add handlers - checkBox.addMouseDownHandler(handler); - checkBox.addTouchStartHandler(handler); - checkBox.addClickHandler(handler); - - return checkBox; - } - - @Override - public void render(final RendererCellReference cell, final Boolean data, - CheckBox checkBox) { - checkBox.setValue(data, false); - checkBox.setEnabled(!grid.isEditorActive()); - checkBox.getElement().setPropertyInt(LOGICAL_ROW_PROPERTY_INT, - cell.getRowIndex()); - } - - @Override - public Collection getConsumedEvents() { - final HashSet events = new HashSet(); - - /* - * this column's first interest is only to attach a NativePreventHandler - * that does all the magic. These events are the beginning of that - * cycle. - */ - events.add(BrowserEvents.MOUSEDOWN); - events.add(BrowserEvents.TOUCHSTART); - - return events; - } - - @Override - public boolean onBrowserEvent(final CellReference cell, - final NativeEvent event) { - if (BrowserEvents.TOUCHSTART.equals(event.getType()) - || (BrowserEvents.MOUSEDOWN.equals(event.getType()) && event - .getButton() == NativeEvent.BUTTON_LEFT)) { - startDragSelect(event, Element.as(event.getEventTarget())); - return true; - } else { - throw new IllegalStateException("received unexpected event: " - + event.getType()); - } - } - - private void startDragSelect(NativeEvent event, final Element target) { - injectNativeHandler(); - int logicalRowIndex = getLogicalRowIndex(target); - autoScrollHandler.start(logicalRowIndex); - event.preventDefault(); - event.stopPropagation(); - } - - private void injectNativeHandler() { - removeNativeHandler(); - nativePreviewHandlerRegistration = Event - .addNativePreviewHandler(new TouchEventHandler()); - } - - private void removeNativeHandler() { - if (nativePreviewHandlerRegistration != null) { - nativePreviewHandlerRegistration.removeHandler(); - nativePreviewHandlerRegistration = null; - } - } - - private int getLogicalRowIndex(final Element target) { - if (target == null) { - return -1; - } - - /* - * We can't simply go backwards until we find a first element, - * because of the table-in-table scenario. We need to, unfortunately, go - * up from our known root. - */ - final Element tbody = getTbodyElement(); - Element tr = tbody.getFirstChildElement(); - while (tr != null) { - if (tr.isOrHasChild(target)) { - final Element td = tr.getFirstChildElement(); - assert td != null : "Cell has disappeared"; - - final Element checkbox = td.getFirstChildElement(); - assert checkbox != null : "Checkbox has disappeared"; - - return checkbox.getPropertyInt(LOGICAL_ROW_PROPERTY_INT); - } - tr = tr.getNextSiblingElement(); - } - return -1; - } - - private TableElement getTableElement() { - final Element root = grid.getElement(); - final Element tablewrapper = Element.as(root.getChild(2)); - if (tablewrapper != null) { - return TableElement.as(tablewrapper.getFirstChildElement()); - } else { - return null; - } - } - - private TableSectionElement getTbodyElement() { - TableElement table = getTableElement(); - if (table != null) { - return table.getTBodies().getItem(0); - } else { - return null; - } - } - - private TableSectionElement getTheadElement() { - TableElement table = getTableElement(); - if (table != null) { - return table.getTHead(); - } else { - return null; - } - } - - private TableSectionElement getTfootElement() { - TableElement table = getTableElement(); - if (table != null) { - return table.getTFoot(); - } else { - return null; - } - } - - /** Get the "top" of an element in relation to "client" coordinates. */ - private int getClientTop(final Element e) { - return e.getAbsoluteTop(); - } - - private int getBodyClientBottom() { - return getClientTop(getTfootElement()) - 1; - } - - private int getBodyClientTop() { - // Off by one pixel miscalculation. possibly border related. - return getClientTop(grid.getElement()) - + getTheadElement().getOffsetHeight() + 1; - } - - protected boolean isSelected(final int logicalRow) { - return grid.isSelected(grid.getDataSource().getRow(logicalRow)); - } - - protected void setSelected(final int logicalRow, final boolean select) { - T row = grid.getDataSource().getRow(logicalRow); - if (select) { - grid.select(row); - } else { - grid.deselect(row); - } - } -} diff --git a/client/src/com/vaadin/client/widget/grid/selection/SelectionEvent.java b/client/src/com/vaadin/client/widget/grid/selection/SelectionEvent.java deleted file mode 100644 index 528beb5809..0000000000 --- a/client/src/com/vaadin/client/widget/grid/selection/SelectionEvent.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.selection; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import com.google.gwt.event.shared.GwtEvent; -import com.vaadin.client.widgets.Grid; - -/** - * Event object describing a change in Grid row selection state. - * - * @since 7.4 - * @author Vaadin Ltd - */ -@SuppressWarnings("rawtypes") -public class SelectionEvent extends GwtEvent { - - private static final Type eventType = new Type(); - - private final Grid grid; - private final List added; - private final List removed; - private final boolean batched; - - /** - * Creates an event with a single added or removed row. - * - * @param grid - * grid reference, used for getSource - * @param added - * the added row, or null if a row was not added - * @param removed - * the removed row, or null if a row was not removed - * @param batched - * whether or not this selection change event is triggered during - * a batched selection/deselection action - * @see SelectionModel.Multi.Batched - */ - public SelectionEvent(Grid grid, T added, T removed, boolean batched) { - this.grid = grid; - this.batched = batched; - - if (added != null) { - this.added = Collections.singletonList(added); - } else { - this.added = Collections.emptyList(); - } - - if (removed != null) { - this.removed = Collections.singletonList(removed); - } else { - this.removed = Collections.emptyList(); - } - } - - /** - * Creates an event where several rows have been added or removed. - * - * @param grid - * Grid reference, used for getSource - * @param added - * a collection of added rows, or null if no rows - * were added - * @param removed - * a collection of removed rows, or null if no rows - * were removed - * @param batched - * whether or not this selection change event is triggered during - * a batched selection/deselection action - * @see SelectionModel.Multi.Batched - */ - public SelectionEvent(Grid grid, Collection added, - Collection removed, boolean batched) { - this.grid = grid; - this.batched = batched; - - if (added != null) { - this.added = new ArrayList(added); - } else { - this.added = Collections.emptyList(); - } - - if (removed != null) { - this.removed = new ArrayList(removed); - } else { - this.removed = Collections.emptyList(); - } - } - - /** - * Gets a reference to the Grid object that fired this event. - * - * @return a grid reference - */ - @Override - public Grid getSource() { - return grid; - } - - /** - * Gets all rows added to the selection since the last - * {@link SelectionEvent} . - * - * @return a collection of added rows. Empty collection if no rows were - * added. - */ - public Collection getAdded() { - return Collections.unmodifiableCollection(added); - } - - /** - * Gets all rows removed from the selection since the last - * {@link SelectionEvent}. - * - * @return a collection of removed rows. Empty collection if no rows were - * removed. - */ - public Collection getRemoved() { - return Collections.unmodifiableCollection(removed); - } - - /** - * Gets currently selected rows. - * - * @return a non-null collection containing all currently selected rows. - */ - public Collection getSelected() { - return grid.getSelectedRows(); - } - - /** - * Gets a type identifier for this event. - * - * @return a {@link Type} identifier. - */ - public static Type getType() { - return eventType; - } - - @Override - public Type getAssociatedType() { - return eventType; - } - - @Override - @SuppressWarnings("unchecked") - protected void dispatch(SelectionHandler handler) { - handler.onSelect(this); - } - - /** - * Checks if this selection change event is fired during a batched - * selection/deselection operation. - * - * @return true iff this event is fired during a batched - * selection/deselection operation - */ - public boolean isBatchedSelection() { - return batched; - } -} diff --git a/client/src/com/vaadin/client/widget/grid/selection/SelectionHandler.java b/client/src/com/vaadin/client/widget/grid/selection/SelectionHandler.java deleted file mode 100644 index 4f939fa798..0000000000 --- a/client/src/com/vaadin/client/widget/grid/selection/SelectionHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.selection; - -import com.google.gwt.event.shared.EventHandler; - -/** - * Handler for {@link SelectionEvent}s. - * - * @author Vaadin Ltd - * @param - * The row data type - * @since 7.4 - */ -public interface SelectionHandler extends EventHandler { - - /** - * Called when a selection model's selection state is changed. - * - * @param event - * a selection event, containing info about rows that have been - * added to or removed from the selection. - */ - public void onSelect(SelectionEvent event); - -} diff --git a/client/src/com/vaadin/client/widget/grid/selection/SelectionModel.java b/client/src/com/vaadin/client/widget/grid/selection/SelectionModel.java deleted file mode 100644 index ec36ab52e8..0000000000 --- a/client/src/com/vaadin/client/widget/grid/selection/SelectionModel.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.selection; - -import java.util.Collection; - -import com.vaadin.client.renderers.Renderer; -import com.vaadin.client.widgets.Grid; - -/** - * Common interface for all selection models. - *

- * Selection models perform tracking of selected rows in the Grid, as well as - * dispatching events when the selection state changes. - * - * @author Vaadin Ltd - * @param - * Grid's row type - * @since 7.4 - */ -public interface SelectionModel { - - /** - * Return true if the provided row is considered selected under the - * implementing selection model. - * - * @param row - * row object instance - * @return true, if the row given as argument is considered - * selected. - */ - public boolean isSelected(T row); - - /** - * Return the {@link Renderer} responsible for rendering the selection - * column. - * - * @return a renderer instance. If null is returned, a selection column will - * not be drawn. - */ - public Renderer getSelectionColumnRenderer(); - - /** - * Tells this SelectionModel which Grid it belongs to. - *

- * Implementations are free to have this be a no-op. This method is called - * internally by Grid. - * - * @param grid - * a {@link Grid} instance; null when removing from - * Grid - */ - public void setGrid(Grid grid); - - /** - * Resets the SelectionModel to the initial state. - *

- * This method can be called internally, for example, when the attached - * Grid's data source changes. - */ - public void reset(); - - /** - * Returns a Collection containing all selected rows. - * - * @return a non-null collection. - */ - public Collection getSelectedRows(); - - /** - * Selection model that allows a maximum of one row to be selected at any - * one time. - * - * @param - * type parameter corresponding with Grid row type - */ - public interface Single extends SelectionModel { - - /** - * Selects a row. - * - * @param row - * a {@link Grid} row object - * @return true, if this row as not previously selected. - */ - public boolean select(T row); - - /** - * Deselects a row. - *

- * This is a no-op unless {@link row} is the currently selected row. - * - * @param row - * a {@link Grid} row object - * @return true, if the currently selected row was deselected. - */ - public boolean deselect(T row); - - /** - * Returns the currently selected row. - * - * @return a {@link Grid} row object or null, if nothing is selected. - */ - public T getSelectedRow(); - - /** - * Sets whether it's allowed to deselect the selected row through the - * UI. Deselection is allowed by default. - * - * @param deselectAllowed - * true if the selected row can be deselected - * without selecting another row instead; otherwise - * false. - */ - public void setDeselectAllowed(boolean deselectAllowed); - - /** - * Sets whether it's allowed to deselect the selected row through the - * UI. - * - * @return true if deselection is allowed; otherwise - * false - */ - public boolean isDeselectAllowed(); - - } - - /** - * Selection model that allows for several rows to be selected at once. - * - * @param - * type parameter corresponding with Grid row type - */ - public interface Multi extends SelectionModel { - - /** - * A multi selection model that can send selections and deselections in - * a batch, instead of committing them one-by-one. - * - * @param - * type parameter corresponding with Grid row type - */ - public interface Batched extends Multi { - /** - * Starts a batch selection. - *

- * Any commands to any select or deselect method will be batched - * into one, and a final selection event will be fired when - * {@link #commitBatchSelect()} is called. - *

- * Note: {@link SelectionEvent SelectionChangeEvents} will - * still be fired for each selection/deselection. You should check - * whether the event is a part of a batch or not with - * {@link SelectionEvent#isBatchedSelection()}. - */ - public void startBatchSelect(); - - /** - * Commits and ends a batch selection. - *

- * Any and all selections and deselections since the last invocation - * of {@link #startBatchSelect()} will be fired at once as one - * collated {@link SelectionEvent}. - */ - public void commitBatchSelect(); - - /** - * Checks whether or not a batch has been started. - * - * @return true iff a batch has been started - */ - public boolean isBeingBatchSelected(); - - /** - * Gets all the rows that would become selected in this batch. - * - * @return a collection of the rows that would become selected - */ - public Collection getSelectedRowsBatch(); - - /** - * Gets all the rows that would become deselected in this batch. - * - * @return a collection of the rows that would become deselected - */ - public Collection getDeselectedRowsBatch(); - } - - /** - * Selects one or more rows. - * - * @param rows - * {@link Grid} row objects - * @return true, if the set of selected rows was changed. - */ - public boolean select(T... rows); - - /** - * Deselects one or more rows. - * - * @param rows - * Grid row objects - * @return true, if the set of selected rows was changed. - */ - public boolean deselect(T... rows); - - /** - * De-selects all rows. - * - * @return true, if any row was previously selected. - */ - public boolean deselectAll(); - - /** - * Select all rows in a {@link Collection}. - * - * @param rows - * a collection of Grid row objects - * @return true, if the set of selected rows was changed. - */ - public boolean select(Collection rows); - - /** - * Deselect all rows in a {@link Collection}. - * - * @param rows - * a collection of Grid row objects - * @return true, if the set of selected rows was changed. - */ - public boolean deselect(Collection rows); - - } - - /** - * Interface for a selection model that does not allow anything to be - * selected. - * - * @param - * type parameter corresponding with Grid row type - */ - public interface None extends SelectionModel { - - } - -} diff --git a/client/src/com/vaadin/client/widget/grid/selection/SelectionModelMulti.java b/client/src/com/vaadin/client/widget/grid/selection/SelectionModelMulti.java deleted file mode 100644 index d654a28b7d..0000000000 --- a/client/src/com/vaadin/client/widget/grid/selection/SelectionModelMulti.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.selection; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; - -import com.vaadin.client.data.DataSource.RowHandle; -import com.vaadin.client.renderers.Renderer; -import com.vaadin.client.widgets.Grid; - -/** - * Multi-row selection model. - * - * @author Vaadin Ltd - * @since 7.4 - */ -public class SelectionModelMulti extends AbstractRowHandleSelectionModel - implements SelectionModel.Multi.Batched { - - private final LinkedHashSet> selectedRows; - private Renderer renderer; - private Grid grid; - - private boolean batchStarted = false; - private final LinkedHashSet> selectionBatch = new LinkedHashSet>(); - private final LinkedHashSet> deselectionBatch = new LinkedHashSet>(); - - /* Event handling for selection with space key */ - private SpaceSelectHandler spaceSelectHandler; - - public SelectionModelMulti() { - grid = null; - renderer = null; - selectedRows = new LinkedHashSet>(); - } - - @Override - public boolean isSelected(T row) { - return isSelectedByHandle(grid.getDataSource().getHandle(row)); - } - - @Override - public Renderer getSelectionColumnRenderer() { - return renderer; - } - - @Override - public void setGrid(Grid grid) { - if (this.grid != null && grid != null) { - // Trying to replace grid - throw new IllegalStateException( - "Selection model is already attached to a grid. " - + "Remove the selection model first from " - + "the grid and then add it."); - } - - this.grid = grid; - if (this.grid != null) { - spaceSelectHandler = new SpaceSelectHandler(grid); - this.renderer = new MultiSelectionRenderer(grid); - } else { - spaceSelectHandler.removeHandler(); - spaceSelectHandler = null; - this.renderer = null; - } - - } - - @Override - public boolean select(T... rows) { - if (rows == null) { - throw new IllegalArgumentException("Rows cannot be null"); - } - return select(Arrays.asList(rows)); - } - - @Override - public boolean deselect(T... rows) { - if (rows == null) { - throw new IllegalArgumentException("Rows cannot be null"); - } - return deselect(Arrays.asList(rows)); - } - - @Override - public boolean deselectAll() { - if (selectedRows.size() > 0) { - - @SuppressWarnings("unchecked") - final LinkedHashSet> selectedRowsClone = (LinkedHashSet>) selectedRows - .clone(); - SelectionEvent event = new SelectionEvent(grid, null, - getSelectedRows(), isBeingBatchSelected()); - selectedRows.clear(); - - if (isBeingBatchSelected()) { - selectionBatch.clear(); - deselectionBatch.clear(); - deselectionBatch.addAll(selectedRowsClone); - } - - grid.fireEvent(event); - return true; - } - return false; - } - - @Override - public boolean select(Collection rows) { - if (rows == null) { - throw new IllegalArgumentException("Rows cannot be null"); - } - - Set added = new LinkedHashSet(); - - for (T row : rows) { - RowHandle handle = grid.getDataSource().getHandle(row); - if (selectByHandle(handle)) { - added.add(row); - } - } - - if (added.size() > 0) { - grid.fireEvent(new SelectionEvent(grid, added, null, - isBeingBatchSelected())); - - return true; - } - return false; - } - - @Override - public boolean deselect(Collection rows) { - if (rows == null) { - throw new IllegalArgumentException("Rows cannot be null"); - } - - Set removed = new LinkedHashSet(); - - for (T row : rows) { - RowHandle handle = grid.getDataSource().getHandle(row); - if (deselectByHandle(handle)) { - removed.add(row); - } - } - - if (removed.size() > 0) { - grid.fireEvent(new SelectionEvent(grid, null, removed, - isBeingBatchSelected())); - return true; - } - return false; - } - - protected boolean isSelectedByHandle(RowHandle handle) { - return selectedRows.contains(handle); - } - - @Override - protected boolean selectByHandle(RowHandle handle) { - if (selectedRows.add(handle)) { - handle.pin(); - - if (isBeingBatchSelected()) { - deselectionBatch.remove(handle); - selectionBatch.add(handle); - } - - return true; - } - return false; - } - - @Override - protected boolean deselectByHandle(RowHandle handle) { - if (selectedRows.remove(handle)) { - - if (!isBeingBatchSelected()) { - handle.unpin(); - } else { - selectionBatch.remove(handle); - deselectionBatch.add(handle); - } - return true; - } - return false; - } - - @Override - public Collection getSelectedRows() { - Set selected = new LinkedHashSet(); - for (RowHandle handle : selectedRows) { - selected.add(handle.getRow()); - } - return Collections.unmodifiableSet(selected); - } - - @Override - public void reset() { - deselectAll(); - } - - @Override - public void startBatchSelect() { - assert !isBeingBatchSelected() : "Batch has already been started"; - batchStarted = true; - } - - @Override - public void commitBatchSelect() { - assert isBeingBatchSelected() : "Batch was never started"; - if (!isBeingBatchSelected()) { - return; - } - - batchStarted = false; - - final Collection added = getSelectedRowsBatch(); - selectionBatch.clear(); - - final Collection removed = getDeselectedRowsBatch(); - - // unpin deselected rows - for (RowHandle handle : deselectionBatch) { - handle.unpin(); - } - deselectionBatch.clear(); - - grid.fireEvent(new SelectionEvent(grid, added, removed, - isBeingBatchSelected())); - } - - @Override - public boolean isBeingBatchSelected() { - return batchStarted; - } - - @Override - public Collection getSelectedRowsBatch() { - return rowHandlesToRows(selectionBatch); - } - - @Override - public Collection getDeselectedRowsBatch() { - return rowHandlesToRows(deselectionBatch); - } - - private ArrayList rowHandlesToRows(Collection> rowHandles) { - ArrayList rows = new ArrayList(rowHandles.size()); - for (RowHandle handle : rowHandles) { - rows.add(handle.getRow()); - } - return rows; - } -} diff --git a/client/src/com/vaadin/client/widget/grid/selection/SelectionModelNone.java b/client/src/com/vaadin/client/widget/grid/selection/SelectionModelNone.java deleted file mode 100644 index 4a8b203a94..0000000000 --- a/client/src/com/vaadin/client/widget/grid/selection/SelectionModelNone.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.selection; - -import java.util.Collection; -import java.util.Collections; - -import com.vaadin.client.data.DataSource.RowHandle; -import com.vaadin.client.renderers.Renderer; -import com.vaadin.client.widgets.Grid; - -/** - * No-row selection model. - * - * @author Vaadin Ltd - * @since 7.4 - */ -public class SelectionModelNone extends AbstractRowHandleSelectionModel - implements SelectionModel.None { - - @Override - public boolean isSelected(T row) { - return false; - } - - @Override - public Renderer getSelectionColumnRenderer() { - return null; - } - - @Override - public void setGrid(Grid grid) { - // noop - } - - @Override - public void reset() { - // noop - } - - @Override - public Collection getSelectedRows() { - return Collections.emptySet(); - } - - @Override - protected boolean selectByHandle(RowHandle handle) - throws UnsupportedOperationException { - throw new UnsupportedOperationException("This selection model " - + "does not support selection"); - } - - @Override - protected boolean deselectByHandle(RowHandle handle) - throws UnsupportedOperationException { - throw new UnsupportedOperationException("This selection model " - + "does not support deselection"); - } - -} diff --git a/client/src/com/vaadin/client/widget/grid/selection/SelectionModelSingle.java b/client/src/com/vaadin/client/widget/grid/selection/SelectionModelSingle.java deleted file mode 100644 index 38605db12c..0000000000 --- a/client/src/com/vaadin/client/widget/grid/selection/SelectionModelSingle.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.selection; - -import java.util.Collection; -import java.util.Collections; - -import com.vaadin.client.data.DataSource.RowHandle; -import com.vaadin.client.renderers.Renderer; -import com.vaadin.client.widgets.Grid; - -/** - * Single-row selection model. - * - * @author Vaadin Ltd - * @since 7.4 - */ -public class SelectionModelSingle extends AbstractRowHandleSelectionModel - implements SelectionModel.Single { - - private Grid grid; - private RowHandle selectedRow; - - /** Event handling for selection with space key */ - private SpaceSelectHandler spaceSelectHandler; - - /** Event handling for selection by clicking cells */ - private ClickSelectHandler clickSelectHandler; - - private boolean deselectAllowed = true; - - @Override - public boolean isSelected(T row) { - return selectedRow != null - && selectedRow.equals(grid.getDataSource().getHandle(row)); - } - - @Override - public Renderer getSelectionColumnRenderer() { - // No Selection column renderer for single selection - return null; - } - - @Override - public void setGrid(Grid grid) { - if (this.grid != null && grid != null) { - // Trying to replace grid - throw new IllegalStateException( - "Selection model is already attached to a grid. " - + "Remove the selection model first from " - + "the grid and then add it."); - } - - this.grid = grid; - if (this.grid != null) { - spaceSelectHandler = new SpaceSelectHandler(grid); - clickSelectHandler = new ClickSelectHandler(grid); - updateHandlerDeselectAllowed(); - } else { - spaceSelectHandler.removeHandler(); - clickSelectHandler.removeHandler(); - spaceSelectHandler = null; - clickSelectHandler = null; - } - } - - @Override - public boolean select(T row) { - - if (row == null) { - throw new IllegalArgumentException("Row cannot be null"); - } - - T removed = getSelectedRow(); - if (selectByHandle(grid.getDataSource().getHandle(row))) { - grid.fireEvent(new SelectionEvent(grid, row, removed, false)); - - return true; - } - return false; - } - - @Override - public boolean deselect(T row) { - - if (row == null) { - throw new IllegalArgumentException("Row cannot be null"); - } - - if (isSelected(row)) { - deselectByHandle(selectedRow); - grid.fireEvent(new SelectionEvent(grid, null, row, false)); - return true; - } - - return false; - } - - @Override - public T getSelectedRow() { - return (selectedRow != null ? selectedRow.getRow() : null); - } - - @Override - public void reset() { - if (selectedRow != null) { - deselect(getSelectedRow()); - } - } - - @Override - public Collection getSelectedRows() { - if (getSelectedRow() != null) { - return Collections.singleton(getSelectedRow()); - } - return Collections.emptySet(); - } - - @Override - protected boolean selectByHandle(RowHandle handle) { - if (handle != null && !handle.equals(selectedRow)) { - deselectByHandle(selectedRow); - selectedRow = handle; - selectedRow.pin(); - return true; - } else { - return false; - } - } - - @Override - protected boolean deselectByHandle(RowHandle handle) { - if (handle != null && handle.equals(selectedRow)) { - selectedRow.unpin(); - selectedRow = null; - return true; - } else { - return false; - } - } - - @Override - public void setDeselectAllowed(boolean deselectAllowed) { - this.deselectAllowed = deselectAllowed; - updateHandlerDeselectAllowed(); - } - - @Override - public boolean isDeselectAllowed() { - return deselectAllowed; - } - - private void updateHandlerDeselectAllowed() { - if (spaceSelectHandler != null) { - spaceSelectHandler.setDeselectAllowed(deselectAllowed); - } - if (clickSelectHandler != null) { - clickSelectHandler.setDeselectAllowed(deselectAllowed); - } - } - -} diff --git a/client/src/com/vaadin/client/widget/grid/selection/SpaceSelectHandler.java b/client/src/com/vaadin/client/widget/grid/selection/SpaceSelectHandler.java deleted file mode 100644 index 3e04a6dfac..0000000000 --- a/client/src/com/vaadin/client/widget/grid/selection/SpaceSelectHandler.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.selection; - -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.shared.HandlerRegistration; -import com.vaadin.client.widget.grid.DataAvailableEvent; -import com.vaadin.client.widget.grid.DataAvailableHandler; -import com.vaadin.client.widget.grid.events.BodyKeyDownHandler; -import com.vaadin.client.widget.grid.events.BodyKeyUpHandler; -import com.vaadin.client.widget.grid.events.GridKeyDownEvent; -import com.vaadin.client.widget.grid.events.GridKeyUpEvent; -import com.vaadin.client.widgets.Grid; -import com.vaadin.shared.ui.grid.ScrollDestination; - -/** - * Generic class to perform selections when pressing space key. - * - * @author Vaadin Ltd - * @param - * row data type - * @since 7.4 - */ -public class SpaceSelectHandler { - - /** - * Handler for space key down events in Grid Body - */ - private class SpaceKeyDownHandler implements BodyKeyDownHandler { - private HandlerRegistration scrollHandler = null; - - @Override - public void onKeyDown(GridKeyDownEvent event) { - if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE || spaceDown) { - return; - } - - // Prevent space page scrolling - event.getNativeEvent().preventDefault(); - - spaceDown = true; - final int rowIndex = event.getFocusedCell().getRowIndex(); - - if (scrollHandler != null) { - scrollHandler.removeHandler(); - scrollHandler = null; - } - - scrollHandler = grid - .addDataAvailableHandler(new DataAvailableHandler() { - - @Override - public void onDataAvailable( - DataAvailableEvent dataAvailableEvent) { - if (dataAvailableEvent.getAvailableRows().contains( - rowIndex)) { - setSelected(grid, rowIndex); - scrollHandler.removeHandler(); - scrollHandler = null; - } - } - }); - grid.scrollToRow(rowIndex, ScrollDestination.ANY); - } - - protected void setSelected(Grid grid, int rowIndex) { - T row = grid.getDataSource().getRow(rowIndex); - - if (!grid.isSelected(row)) { - grid.select(row); - } else if (deselectAllowed) { - grid.deselect(row); - } - } - } - - private boolean spaceDown = false; - private Grid grid; - private HandlerRegistration spaceUpHandler; - private HandlerRegistration spaceDownHandler; - private boolean deselectAllowed = true; - - /** - * Constructor for SpaceSelectHandler. This constructor will add all - * necessary handlers for selecting rows with space. - * - * @param grid - * grid to attach to - */ - public SpaceSelectHandler(Grid grid) { - this.grid = grid; - spaceDownHandler = grid - .addBodyKeyDownHandler(new SpaceKeyDownHandler()); - spaceUpHandler = grid.addBodyKeyUpHandler(new BodyKeyUpHandler() { - - @Override - public void onKeyUp(GridKeyUpEvent event) { - if (event.getNativeKeyCode() == KeyCodes.KEY_SPACE) { - spaceDown = false; - } - } - }); - } - - /** - * Clean up function for removing all now obsolete handlers. - */ - public void removeHandler() { - spaceDownHandler.removeHandler(); - spaceUpHandler.removeHandler(); - } - - /** - * Sets whether pressing space for the currently selected row should - * deselect the row. - * - * @param deselectAllowed - * true to allow deselecting the selected row; - * otherwise false - */ - public void setDeselectAllowed(boolean deselectAllowed) { - this.deselectAllowed = deselectAllowed; - } -} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/sort/Sort.java b/client/src/com/vaadin/client/widget/grid/sort/Sort.java deleted file mode 100644 index b1f3c6e39a..0000000000 --- a/client/src/com/vaadin/client/widget/grid/sort/Sort.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.sort; - -import java.util.ArrayList; -import java.util.List; - -import com.vaadin.client.widgets.Grid; -import com.vaadin.shared.data.sort.SortDirection; - -/** - * Fluid Sort descriptor object. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class Sort { - - private final Sort previous; - private final SortOrder order; - private final int count; - - /** - * Basic constructor, used by the {@link #by(GridColumn)} and - * {@link #by(GridColumn, SortDirection)} methods. - * - * @param column - * a grid column - * @param direction - * a sort direction - */ - private Sort(Grid.Column column, SortDirection direction) { - previous = null; - count = 1; - order = new SortOrder(column, direction); - } - - /** - * Extension constructor. Performs object equality checks on all previous - * Sort objects in the chain to make sure that the column being passed in - * isn't already used earlier (which would indicate a bug). If the column - * has been used before, this constructor throws an - * {@link IllegalStateException}. - * - * @param previous - * the sort instance that the new sort instance is to extend - * @param column - * a (previously unused) grid column reference - * @param direction - * a sort direction - */ - private Sort(Sort previous, Grid.Column column, - SortDirection direction) { - this.previous = previous; - count = previous.count + 1; - order = new SortOrder(column, direction); - - Sort s = previous; - while (s != null) { - if (s.order.getColumn() == column) { - throw new IllegalStateException( - "Can not sort along the same column twice"); - } - s = s.previous; - } - } - - /** - * Start building a Sort order by sorting a provided column in ascending - * order. - * - * @param column - * a grid column object reference - * @return a sort instance, typed to the grid data type - */ - public static Sort by(Grid.Column column) { - return by(column, SortDirection.ASCENDING); - } - - /** - * Start building a Sort order by sorting a provided column. - * - * @param column - * a grid column object reference - * @param direction - * indicator of sort direction - either ascending or descending - * @return a sort instance, typed to the grid data type - */ - public static Sort by(Grid.Column column, SortDirection direction) { - return new Sort(column, direction); - } - - /** - * Continue building a Sort order. The provided column is sorted in - * ascending order if the previously added columns have been evaluated as - * equals. - * - * @param column - * a grid column object reference - * @return a sort instance, typed to the grid data type - */ - public Sort then(Grid.Column column) { - return then(column, SortDirection.ASCENDING); - } - - /** - * Continue building a Sort order. The provided column is sorted in - * specified order if the previously added columns have been evaluated as - * equals. - * - * @param column - * a grid column object reference - * @param direction - * indicator of sort direction - either ascending or descending - * @return a sort instance, typed to the grid data type - */ - public Sort then(Grid.Column column, SortDirection direction) { - return new Sort(this, column, direction); - } - - /** - * Build a sort order list. This method is called internally by Grid when - * calling {@link com.vaadin.client.ui.grid.Grid#sort(Sort)}, but can also - * be called manually to create a SortOrder list, which can also be provided - * directly to Grid. - * - * @return a sort order list. - */ - public List build() { - - List order = new ArrayList(count); - - Sort s = this; - for (int i = count - 1; i >= 0; --i) { - order.add(0, s.order); - s = s.previous; - } - - return order; - } -} diff --git a/client/src/com/vaadin/client/widget/grid/sort/SortEvent.java b/client/src/com/vaadin/client/widget/grid/sort/SortEvent.java deleted file mode 100644 index 2aad6e4f95..0000000000 --- a/client/src/com/vaadin/client/widget/grid/sort/SortEvent.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.sort; - -import java.util.List; - -import com.google.gwt.event.shared.GwtEvent; -import com.vaadin.client.widgets.Grid; - -/** - * A sort event, fired by the Grid when it needs its data source to provide data - * sorted in a specific manner. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class SortEvent extends GwtEvent> { - - private static final Type> TYPE = new Type>(); - - private final Grid grid; - private final List order; - private final boolean userOriginated; - - /** - * Creates a new Sort Event. All provided parameters are final, and passed - * on as-is. - * - * @param grid - * a grid reference - * @param order - * an array dictating the desired sort order of the data source - * @param originator - * a value indicating where this event originated from - */ - public SortEvent(Grid grid, List order, boolean userOriginated) { - this.grid = grid; - this.order = order; - this.userOriginated = userOriginated; - } - - @Override - public Type> getAssociatedType() { - return TYPE; - } - - /** - * Static access to the GWT event type identifier associated with this Event - * class - * - * @return a type object, uniquely describing this event type. - */ - public static Type> getType() { - return TYPE; - } - - /** - * Get access to the Grid that fired this event - * - * @return the grid instance - */ - @Override - public Grid getSource() { - return grid; - } - - /** - * Get access to the Grid that fired this event - * - * @return the grid instance - */ - public Grid getGrid() { - return grid; - } - - /** - * Get the sort ordering that is to be applied to the Grid - * - * @return a list of sort order objects - */ - public List getOrder() { - return order; - } - - /** - * Returns whether this event originated from actions done by the user. - * - * @return true if sort event originated from user interaction - */ - public boolean isUserOriginated() { - return userOriginated; - } - - @SuppressWarnings("unchecked") - @Override - protected void dispatch(SortHandler handler) { - ((SortHandler) handler).sort(this); - } - -} diff --git a/client/src/com/vaadin/client/widget/grid/sort/SortHandler.java b/client/src/com/vaadin/client/widget/grid/sort/SortHandler.java deleted file mode 100644 index 330cbe9d58..0000000000 --- a/client/src/com/vaadin/client/widget/grid/sort/SortHandler.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.sort; - -import com.google.gwt.event.shared.EventHandler; - -/** - * Handler for a Grid sort event, called when the Grid needs its data source to - * provide data sorted in a specific manner. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public interface SortHandler extends EventHandler { - - /** - * Handle sorting of the Grid. This method is called when a re-sorting of - * the Grid's data is requested. - * - * @param event - * the sort event - */ - public void sort(SortEvent event); - -} diff --git a/client/src/com/vaadin/client/widget/grid/sort/SortOrder.java b/client/src/com/vaadin/client/widget/grid/sort/SortOrder.java deleted file mode 100644 index 8166f1e6ed..0000000000 --- a/client/src/com/vaadin/client/widget/grid/sort/SortOrder.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widget.grid.sort; - -import com.vaadin.client.widgets.Grid; -import com.vaadin.shared.data.sort.SortDirection; - -/** - * Sort order descriptor. Contains column and direction references. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class SortOrder { - - private final Grid.Column column; - private final SortDirection direction; - - /** - * Create a sort order descriptor with a default sorting direction value of - * {@link SortDirection#ASCENDING}. - * - * @param column - * a grid column descriptor object - */ - public SortOrder(Grid.Column column) { - this(column, SortDirection.ASCENDING); - } - - /** - * Create a sort order descriptor. - * - * @param column - * a grid column descriptor object - * @param direction - * a sorting direction value (ascending or descending) - */ - public SortOrder(Grid.Column column, SortDirection direction) { - if (column == null) { - throw new IllegalArgumentException( - "Grid column reference can not be null!"); - } - if (direction == null) { - throw new IllegalArgumentException( - "Direction value can not be null!"); - } - this.column = column; - this.direction = direction; - } - - /** - * Returns the {@link GridColumn} reference given in the constructor. - * - * @return a grid column reference - */ - public Grid.Column getColumn() { - return column; - } - - /** - * Returns the {@link SortDirection} value given in the constructor. - * - * @return a sort direction value - */ - public SortDirection getDirection() { - return direction; - } - - /** - * Returns a new SortOrder object with the sort direction reversed. - * - * @return a new sort order object - */ - public SortOrder getOpposite() { - return new SortOrder(column, direction.getOpposite()); - } -} diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java deleted file mode 100644 index 3585be1d60..0000000000 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ /dev/null @@ -1,6697 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widgets; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.TreeMap; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.google.gwt.animation.client.Animation; -import com.google.gwt.animation.client.AnimationScheduler; -import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback; -import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle; -import com.google.gwt.core.client.Duration; -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.JsArray; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.TableCellElement; -import com.google.gwt.dom.client.TableRowElement; -import com.google.gwt.dom.client.TableSectionElement; -import com.google.gwt.dom.client.Touch; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.logging.client.LogConfiguration; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.RequiresResize; -import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.UIObject; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.DeferredWorker; -import com.vaadin.client.Profiler; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.ui.SubPartAware; -import com.vaadin.client.widget.escalator.Cell; -import com.vaadin.client.widget.escalator.ColumnConfiguration; -import com.vaadin.client.widget.escalator.EscalatorUpdater; -import com.vaadin.client.widget.escalator.FlyweightCell; -import com.vaadin.client.widget.escalator.FlyweightRow; -import com.vaadin.client.widget.escalator.PositionFunction; -import com.vaadin.client.widget.escalator.PositionFunction.AbsolutePosition; -import com.vaadin.client.widget.escalator.PositionFunction.Translate3DPosition; -import com.vaadin.client.widget.escalator.PositionFunction.TranslatePosition; -import com.vaadin.client.widget.escalator.PositionFunction.WebkitTranslate3DPosition; -import com.vaadin.client.widget.escalator.Row; -import com.vaadin.client.widget.escalator.RowContainer; -import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; -import com.vaadin.client.widget.escalator.RowVisibilityChangeEvent; -import com.vaadin.client.widget.escalator.RowVisibilityChangeHandler; -import com.vaadin.client.widget.escalator.ScrollbarBundle; -import com.vaadin.client.widget.escalator.ScrollbarBundle.HorizontalScrollbarBundle; -import com.vaadin.client.widget.escalator.ScrollbarBundle.VerticalScrollbarBundle; -import com.vaadin.client.widget.escalator.Spacer; -import com.vaadin.client.widget.escalator.SpacerUpdater; -import com.vaadin.client.widget.grid.events.ScrollEvent; -import com.vaadin.client.widget.grid.events.ScrollHandler; -import com.vaadin.client.widgets.Escalator.JsniUtil.TouchHandlerBundle; -import com.vaadin.shared.ui.grid.HeightMode; -import com.vaadin.shared.ui.grid.Range; -import com.vaadin.shared.ui.grid.ScrollDestination; -import com.vaadin.shared.util.SharedUtil; - -/*- - - Maintenance Notes! Reading these might save your day. - (note for editors: line width is 80 chars, including the - one-space indentation) - - - == Row Container Structure - - AbstractRowContainer - |-- AbstractStaticRowContainer - | |-- HeaderRowContainer - | `-- FooterContainer - `---- BodyRowContainerImpl - - AbstractRowContainer is intended to contain all common logic - between RowContainers. It manages the bookkeeping of row - count, makes sure that all individual cells are rendered - the same way, and so on. - - AbstractStaticRowContainer has some special logic that is - required by all RowContainers that don't scroll (hence the - word "static"). HeaderRowContainer and FooterRowContainer - are pretty thin special cases of a StaticRowContainer - (mostly relating to positioning of the root element). - - BodyRowContainerImpl could also be split into an additional - "AbstractScrollingRowContainer", but I felt that no more - inner classes were needed. So it contains both logic - required for making things scroll about, and equivalent - special cases for layouting, as are found in - Header/FooterRowContainers. - - - == The Three Indices - - Each RowContainer can be thought to have three levels of - indices for any given displayed row (but the distinction - matters primarily for the BodyRowContainerImpl, because of - the way it scrolls through data): - - - Logical index - - Physical (or DOM) index - - Visual index - - LOGICAL INDEX is the index that is linked to the data - source. If you want your data source to represent a SQL - database with 10 000 rows, the 7 000:th row in the SQL has a - logical index of 6 999, since the index is 0-based (unless - that data source does some funky logic). - - PHYSICAL INDEX is the index for a row that you see in a - browser's DOM inspector. If your row is the second - element within a tag, it has a physical index of 1 - (because of 0-based indices). In Header and - FooterRowContainers, you are safe to assume that the logical - index is the same as the physical index. But because the - BodyRowContainerImpl never displays large data sources - entirely in the DOM, a physical index usually has no - apparent direct relationship with its logical index. - - VISUAL INDEX is the index relating to the order that you - see a row in, in the browser, as it is rendered. The - topmost row is 0, the second is 1, and so on. The visual - index is similar to the physical index in the sense that - Header and FooterRowContainers can assume a 1:1 - relationship between visual index and logical index. And - again, BodyRowContainerImpl has no such relationship. The - body's visual index has additionally no apparent - relationship with its physical index. Because the tags - are reused in the body and visually repositioned with CSS - as the user scrolls, the relationship between physical - index and visual index is quickly broken. You can get an - element's visual index via the field - BodyRowContainerImpl.visualRowOrder. - - Currently, the physical and visual indices are kept in sync - _most of the time_ by a deferred rearrangement of rows. - They become desynced when scrolling. This is to help screen - readers to read the contents from the DOM in a natural - order. See BodyRowContainerImpl.DeferredDomSorter for more - about that. - - */ - -/** - * A workaround-class for GWT and JSNI. - *

- * GWT is unable to handle some method calls to Java methods in inner-classes - * from within JSNI blocks. Having that inner class extend a non-inner-class (or - * implement such an interface), makes it possible for JSNI to indirectly refer - * to the inner class, by invoking methods and fields in the non-inner-class - * API. - * - * @see Escalator.Scroller - */ -abstract class JsniWorkaround { - /** - * A JavaScript function that handles the scroll DOM event, and passes it on - * to Java code. - * - * @see #createScrollListenerFunction(Escalator) - * @see Escalator#onScroll() - * @see Escalator.Scroller#onScroll() - */ - protected final JavaScriptObject scrollListenerFunction; - - /** - * A JavaScript function that handles the mousewheel DOM event, and passes - * it on to Java code. - * - * @see #createMousewheelListenerFunction(Escalator) - * @see Escalator#onScroll() - * @see Escalator.Scroller#onScroll() - */ - protected final JavaScriptObject mousewheelListenerFunction; - - /** - * A JavaScript function that handles the touch start DOM event, and passes - * it on to Java code. - * - * @see TouchHandlerBundle#touchStart(Escalator.JsniUtil.TouchHandlerBundle.CustomTouchEvent) - */ - protected JavaScriptObject touchStartFunction; - - /** - * A JavaScript function that handles the touch move DOM event, and passes - * it on to Java code. - * - * @see TouchHandlerBundle#touchMove(Escalator.JsniUtil.TouchHandlerBundle.CustomTouchEvent) - */ - protected JavaScriptObject touchMoveFunction; - - /** - * A JavaScript function that handles the touch end and cancel DOM events, - * and passes them on to Java code. - * - * @see TouchHandlerBundle#touchEnd(Escalator.JsniUtil.TouchHandlerBundle.CustomTouchEvent) - */ - protected JavaScriptObject touchEndFunction; - - protected TouchHandlerBundle touchHandlerBundle; - - protected JsniWorkaround(final Escalator escalator) { - scrollListenerFunction = createScrollListenerFunction(escalator); - mousewheelListenerFunction = createMousewheelListenerFunction(escalator); - - touchHandlerBundle = new TouchHandlerBundle(escalator); - touchStartFunction = touchHandlerBundle.getTouchStartHandler(); - touchMoveFunction = touchHandlerBundle.getTouchMoveHandler(); - touchEndFunction = touchHandlerBundle.getTouchEndHandler(); - } - - /** - * A method that constructs the JavaScript function that will be stored into - * {@link #scrollListenerFunction}. - * - * @param esc - * a reference to the current instance of {@link Escalator} - * @see Escalator#onScroll() - */ - protected abstract JavaScriptObject createScrollListenerFunction( - Escalator esc); - - /** - * A method that constructs the JavaScript function that will be stored into - * {@link #mousewheelListenerFunction}. - * - * @param esc - * a reference to the current instance of {@link Escalator} - * @see Escalator#onScroll() - */ - protected abstract JavaScriptObject createMousewheelListenerFunction( - Escalator esc); -} - -/** - * A low-level table-like widget that features a scrolling virtual viewport and - * lazily generated rows. - * - * @since 7.4 - * @author Vaadin Ltd - */ -public class Escalator extends Widget implements RequiresResize, - DeferredWorker, SubPartAware { - - // todo comments legend - /* - * [[optimize]]: There's an opportunity to rewrite the code in such a way - * that it _might_ perform better (rememeber to measure, implement, - * re-measure) - */ - /* - * [[mpixscroll]]: This code will require alterations that are relevant for - * supporting the scrolling through more pixels than some browsers normally - * would support. (i.e. when we support more than "a million" pixels in the - * escalator DOM). NOTE: these bits can most often also be identified by - * searching for code that call scrollElem.getScrollTop();. - */ - /* - * [[spacer]]: Code that is important to make spacers work. - */ - - /** - * A utility class that contains utility methods that are usually called - * from JSNI. - *

- * The methods are moved in this class to minimize the amount of JSNI code - * as much as feasible. - */ - static class JsniUtil { - public static class TouchHandlerBundle { - - /** - * A JavaScriptObject overlay for the JavaScript - * TouchEvent object. - *

- * This needs to be used in the touch event handlers, since GWT's - * {@link com.google.gwt.event.dom.client.TouchEvent TouchEvent} - * can't be cast from the JSNI call, and the - * {@link com.google.gwt.dom.client.NativeEvent NativeEvent} isn't - * properly populated with the correct values. - */ - private final static class CustomTouchEvent extends - JavaScriptObject { - protected CustomTouchEvent() { - } - - public native NativeEvent getNativeEvent() - /*-{ - return this; - }-*/; - - public native int getPageX() - /*-{ - return this.targetTouches[0].pageX; - }-*/; - - public native int getPageY() - /*-{ - return this.targetTouches[0].pageY; - }-*/; - } - - private final Escalator escalator; - - public TouchHandlerBundle(final Escalator escalator) { - this.escalator = escalator; - } - - public native JavaScriptObject getTouchStartHandler() - /*-{ - // we need to store "this", since it won't be preserved on call. - var self = this; - return $entry(function (e) { - self.@com.vaadin.client.widgets.Escalator.JsniUtil.TouchHandlerBundle::touchStart(*)(e); - }); - }-*/; - - public native JavaScriptObject getTouchMoveHandler() - /*-{ - // we need to store "this", since it won't be preserved on call. - var self = this; - return $entry(function (e) { - self.@com.vaadin.client.widgets.Escalator.JsniUtil.TouchHandlerBundle::touchMove(*)(e); - }); - }-*/; - - public native JavaScriptObject getTouchEndHandler() - /*-{ - // we need to store "this", since it won't be preserved on call. - var self = this; - return $entry(function (e) { - self.@com.vaadin.client.widgets.Escalator.JsniUtil.TouchHandlerBundle::touchEnd(*)(e); - }); - }-*/; - - // Duration of the inertial scrolling simulation. Devices with - // larger screens take longer durations. - private static final int DURATION = Window.getClientHeight(); - // multiply scroll velocity with repeated touching - private int acceleration = 1; - private boolean touching = false; - // Two movement objects for storing status and processing touches - private Movement yMov, xMov; - final double MIN_VEL = 0.6, MAX_VEL = 4, F_VEL = 1500, F_ACC = 0.7, - F_AXIS = 1; - - // The object to deal with one direction scrolling - private class Movement { - final List speeds = new ArrayList(); - final ScrollbarBundle scroll; - double position, offset, velocity, prevPos, prevTime, delta; - boolean run, vertical; - - public Movement(boolean vertical) { - this.vertical = vertical; - scroll = vertical ? escalator.verticalScrollbar - : escalator.horizontalScrollbar; - } - - public void startTouch(CustomTouchEvent event) { - speeds.clear(); - prevPos = pagePosition(event); - prevTime = Duration.currentTimeMillis(); - } - - public void moveTouch(CustomTouchEvent event) { - double pagePosition = pagePosition(event); - if (pagePosition > -1) { - delta = prevPos - pagePosition; - double now = Duration.currentTimeMillis(); - double ellapsed = now - prevTime; - velocity = delta / ellapsed; - // if last speed was so low, reset speeds and start - // storing again - if (speeds.size() > 0 && !validSpeed(speeds.get(0))) { - speeds.clear(); - run = true; - } - speeds.add(0, velocity); - prevTime = now; - prevPos = pagePosition; - } - } - - public void endTouch(CustomTouchEvent event) { - // Compute average speed - velocity = 0; - for (double s : speeds) { - velocity += s / speeds.size(); - } - position = scroll.getScrollPos(); - - // Compute offset, and adjust it with an easing curve so as - // movement is smoother. - offset = F_VEL * velocity * acceleration - * easingInOutCos(velocity, MAX_VEL); - - // Enable or disable inertia movement in this axis - run = validSpeed(velocity); - if (run) { - event.getNativeEvent().preventDefault(); - } - } - - void validate(Movement other) { - if (!run || other.velocity > 0 - && Math.abs(velocity / other.velocity) < F_AXIS) { - delta = offset = 0; - run = false; - } - } - - void stepAnimation(double progress) { - scroll.setScrollPos(position + offset * progress); - } - - int pagePosition(CustomTouchEvent event) { - JsArray a = event.getNativeEvent().getTouches(); - return vertical ? a.get(0).getPageY() : a.get(0).getPageX(); - } - - boolean validSpeed(double speed) { - return Math.abs(speed) > MIN_VEL; - } - } - - // Using GWT animations which take care of native animation frames. - private Animation animation = new Animation() { - @Override - public void onUpdate(double progress) { - xMov.stepAnimation(progress); - yMov.stepAnimation(progress); - } - - @Override - public double interpolate(double progress) { - return easingOutCirc(progress); - }; - - @Override - public void onComplete() { - touching = false; - escalator.body.domSorter.reschedule(); - }; - - @Override - public void run(int duration) { - if (xMov.run || yMov.run) { - super.run(duration); - } else { - onComplete(); - } - }; - }; - - public void touchStart(final CustomTouchEvent event) { - if (event.getNativeEvent().getTouches().length() == 1) { - if (yMov == null) { - yMov = new Movement(true); - xMov = new Movement(false); - } - if (animation.isRunning()) { - acceleration += F_ACC; - event.getNativeEvent().preventDefault(); - animation.cancel(); - } else { - acceleration = 1; - } - xMov.startTouch(event); - yMov.startTouch(event); - touching = true; - } else { - touching = false; - animation.cancel(); - acceleration = 1; - } - } - - public void touchMove(final CustomTouchEvent event) { - if (touching) { - xMov.moveTouch(event); - yMov.moveTouch(event); - xMov.validate(yMov); - yMov.validate(xMov); - event.getNativeEvent().preventDefault(); - moveScrollFromEvent(escalator, xMov.delta, yMov.delta, - event.getNativeEvent()); - } - } - - public void touchEnd(final CustomTouchEvent event) { - if (touching) { - xMov.endTouch(event); - yMov.endTouch(event); - xMov.validate(yMov); - yMov.validate(xMov); - // Adjust duration so as longer movements take more duration - boolean vert = !xMov.run || yMov.run - && Math.abs(yMov.offset) > Math.abs(xMov.offset); - double delta = Math.abs((vert ? yMov : xMov).offset); - animation.run((int) (3 * DURATION * easingOutExp(delta))); - } - } - - private double easingInOutCos(double val, double max) { - return 0.5 - 0.5 * Math.cos(Math.PI * Math.signum(val) - * Math.min(Math.abs(val), max) / max); - } - - private double easingOutExp(double delta) { - return (1 - Math.pow(2, -delta / 1000)); - } - - private double easingOutCirc(double progress) { - return Math.sqrt(1 - (progress - 1) * (progress - 1)); - } - } - - public static void moveScrollFromEvent(final Escalator escalator, - final double deltaX, final double deltaY, - final NativeEvent event) { - - if (!Double.isNaN(deltaX)) { - escalator.horizontalScrollbar.setScrollPosByDelta(deltaX); - } - - if (!Double.isNaN(deltaY)) { - escalator.verticalScrollbar.setScrollPosByDelta(deltaY); - } - - /* - * TODO: only prevent if not scrolled to end/bottom. Or no? UX team - * needs to decide. - */ - final boolean warrantedYScroll = deltaY != 0 - && escalator.verticalScrollbar.showsScrollHandle(); - final boolean warrantedXScroll = deltaX != 0 - && escalator.horizontalScrollbar.showsScrollHandle(); - if (warrantedYScroll || warrantedXScroll) { - event.preventDefault(); - } - } - } - - /** - * ScrollDestination case-specific handling logic. - */ - private static double getScrollPos(final ScrollDestination destination, - final double targetStartPx, final double targetEndPx, - final double viewportStartPx, final double viewportEndPx, - final double padding) { - - final double viewportLength = viewportEndPx - viewportStartPx; - - switch (destination) { - - /* - * Scroll as little as possible to show the target element. If the - * element fits into view, this works as START or END depending on the - * current scroll position. If the element does not fit into view, this - * works as START. - */ - case ANY: { - final double startScrollPos = targetStartPx - padding; - final double endScrollPos = targetEndPx + padding - viewportLength; - - if (startScrollPos < viewportStartPx) { - return startScrollPos; - } else if (targetEndPx + padding > viewportEndPx) { - return endScrollPos; - } else { - // NOOP, it's already visible - return viewportStartPx; - } - } - - /* - * Scrolls so that the element is shown at the end of the viewport. The - * viewport will, however, not scroll before its first element. - */ - case END: { - return targetEndPx + padding - viewportLength; - } - - /* - * Scrolls so that the element is shown in the middle of the viewport. - * The viewport will, however, not scroll beyond its contents, given - * more elements than what the viewport is able to show at once. Under - * no circumstances will the viewport scroll before its first element. - */ - case MIDDLE: { - final double targetMiddle = targetStartPx - + (targetEndPx - targetStartPx) / 2; - return targetMiddle - viewportLength / 2; - } - - /* - * Scrolls so that the element is shown at the start of the viewport. - * The viewport will, however, not scroll beyond its contents. - */ - case START: { - return targetStartPx - padding; - } - - /* - * Throw an error if we're here. This can only mean that - * ScrollDestination has been carelessly amended.. - */ - default: { - throw new IllegalArgumentException( - "Internal: ScrollDestination has been modified, " - + "but Escalator.getScrollPos has not been updated " - + "to match new values."); - } - } - - } - - /** An inner class that handles all logic related to scrolling. */ - private class Scroller extends JsniWorkaround { - private double lastScrollTop = 0; - private double lastScrollLeft = 0; - - public Scroller() { - super(Escalator.this); - } - - @Override - protected native JavaScriptObject createScrollListenerFunction( - Escalator esc) - /*-{ - var vScroll = esc.@com.vaadin.client.widgets.Escalator::verticalScrollbar; - var vScrollElem = vScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::getElement()(); - - var hScroll = esc.@com.vaadin.client.widgets.Escalator::horizontalScrollbar; - var hScrollElem = hScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::getElement()(); - - return $entry(function(e) { - var target = e.target || e.srcElement; // IE8 uses e.scrElement - - // in case the scroll event was native (i.e. scrollbars were dragged, or - // the scrollTop/Left was manually modified), the bundles have old cache - // values. We need to make sure that the caches are kept up to date. - if (target === vScrollElem) { - vScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::updateScrollPosFromDom()(); - } else if (target === hScrollElem) { - hScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::updateScrollPosFromDom()(); - } else { - $wnd.console.error("unexpected scroll target: "+target); - } - }); - }-*/; - - @Override - protected native JavaScriptObject createMousewheelListenerFunction( - Escalator esc) - /*-{ - return $entry(function(e) { - var deltaX = e.deltaX ? e.deltaX : -0.5*e.wheelDeltaX; - var deltaY = e.deltaY ? e.deltaY : -0.5*e.wheelDeltaY; - - // Delta mode 0 is in pixels; we don't need to do anything... - - // A delta mode of 1 means we're scrolling by lines instead of pixels - // We need to scale the number of lines by the default line height - if(e.deltaMode === 1) { - var brc = esc.@com.vaadin.client.widgets.Escalator::body; - deltaY *= brc.@com.vaadin.client.widgets.Escalator.AbstractRowContainer::getDefaultRowHeight()(); - } - - // Other delta modes aren't supported - if((e.deltaMode !== undefined) && (e.deltaMode >= 2 || e.deltaMode < 0)) { - var msg = "Unsupported wheel delta mode \"" + e.deltaMode + "\""; - - // Print warning message - esc.@com.vaadin.client.widgets.Escalator::logWarning(*)(msg); - } - - // IE8 has only delta y - if (isNaN(deltaY)) { - deltaY = -0.5*e.wheelDelta; - } - - @com.vaadin.client.widgets.Escalator.JsniUtil::moveScrollFromEvent(*)(esc, deltaX, deltaY, e); - }); - }-*/; - - /** - * Recalculates the virtual viewport represented by the scrollbars, so - * that the sizes of the scroll handles appear correct in the browser - */ - public void recalculateScrollbarsForVirtualViewport() { - double scrollContentHeight = body.calculateTotalRowHeight() - + body.spacerContainer.getSpacerHeightsSum(); - double scrollContentWidth = columnConfiguration.calculateRowWidth(); - double tableWrapperHeight = heightOfEscalator; - double tableWrapperWidth = widthOfEscalator; - - boolean verticalScrollNeeded = scrollContentHeight > tableWrapperHeight - + WidgetUtil.PIXEL_EPSILON - - header.getHeightOfSection() - - footer.getHeightOfSection(); - boolean horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth - + WidgetUtil.PIXEL_EPSILON; - - // One dimension got scrollbars, but not the other. Recheck time! - if (verticalScrollNeeded != horizontalScrollNeeded) { - if (!verticalScrollNeeded && horizontalScrollNeeded) { - verticalScrollNeeded = scrollContentHeight > tableWrapperHeight - + WidgetUtil.PIXEL_EPSILON - - header.getHeightOfSection() - - footer.getHeightOfSection() - - horizontalScrollbar.getScrollbarThickness(); - } else { - horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth - + WidgetUtil.PIXEL_EPSILON - - verticalScrollbar.getScrollbarThickness(); - } - } - - // let's fix the table wrapper size, since it's now stable. - if (verticalScrollNeeded) { - tableWrapperWidth -= verticalScrollbar.getScrollbarThickness(); - tableWrapperWidth = Math.max(0, tableWrapperWidth); - } - if (horizontalScrollNeeded) { - tableWrapperHeight -= horizontalScrollbar - .getScrollbarThickness(); - tableWrapperHeight = Math.max(0, tableWrapperHeight); - } - tableWrapper.getStyle().setHeight(tableWrapperHeight, Unit.PX); - tableWrapper.getStyle().setWidth(tableWrapperWidth, Unit.PX); - - double footerHeight = footer.getHeightOfSection(); - double headerHeight = header.getHeightOfSection(); - double vScrollbarHeight = Math.max(0, tableWrapperHeight - - footerHeight - headerHeight); - verticalScrollbar.setOffsetSize(vScrollbarHeight); - verticalScrollbar.setScrollSize(scrollContentHeight); - - /* - * If decreasing the amount of frozen columns, and scrolled to the - * right, the scroll position might reset. So we need to remember - * the scroll position, and re-apply it once the scrollbar size has - * been adjusted. - */ - double prevScrollPos = horizontalScrollbar.getScrollPos(); - - double unfrozenPixels = columnConfiguration - .getCalculatedColumnsWidth(Range.between( - columnConfiguration.getFrozenColumnCount(), - columnConfiguration.getColumnCount())); - double frozenPixels = scrollContentWidth - unfrozenPixels; - double hScrollOffsetWidth = tableWrapperWidth - frozenPixels; - horizontalScrollbar.setOffsetSize(hScrollOffsetWidth); - horizontalScrollbar.setScrollSize(unfrozenPixels); - horizontalScrollbar.getElement().getStyle() - .setLeft(frozenPixels, Unit.PX); - horizontalScrollbar.setScrollPos(prevScrollPos); - - /* - * only show the scrollbar wrapper if the scrollbar itself is - * visible. - */ - if (horizontalScrollbar.showsScrollHandle()) { - horizontalScrollbarDeco.getStyle().clearDisplay(); - } else { - horizontalScrollbarDeco.getStyle().setDisplay(Display.NONE); - } - - /* - * only show corner background divs if the vertical scrollbar is - * visible. - */ - Style hCornerStyle = headerDeco.getStyle(); - Style fCornerStyle = footerDeco.getStyle(); - if (verticalScrollbar.showsScrollHandle()) { - hCornerStyle.clearDisplay(); - fCornerStyle.clearDisplay(); - - if (horizontalScrollbar.showsScrollHandle()) { - double offset = horizontalScrollbar.getScrollbarThickness(); - fCornerStyle.setBottom(offset, Unit.PX); - } else { - fCornerStyle.clearBottom(); - } - } else { - hCornerStyle.setDisplay(Display.NONE); - fCornerStyle.setDisplay(Display.NONE); - } - } - - /** - * Logical scrolling event handler for the entire widget. - */ - public void onScroll() { - - final double scrollTop = verticalScrollbar.getScrollPos(); - final double scrollLeft = horizontalScrollbar.getScrollPos(); - if (lastScrollLeft != scrollLeft) { - for (int i = 0; i < columnConfiguration.frozenColumns; i++) { - header.updateFreezePosition(i, scrollLeft); - body.updateFreezePosition(i, scrollLeft); - footer.updateFreezePosition(i, scrollLeft); - } - - position.set(headElem, -scrollLeft, 0); - - /* - * TODO [[optimize]]: cache this value in case the instanceof - * check has undesirable overhead. This could also be a - * candidate for some deferred binding magic so that e.g. - * AbsolutePosition is not even considered in permutations that - * we know support something better. That would let the compiler - * completely remove the entire condition since it knows that - * the if will never be true. - */ - if (position instanceof AbsolutePosition) { - /* - * we don't want to put "top: 0" on the footer, since it'll - * render wrong, as we already have - * "bottom: $footer-height". - */ - footElem.getStyle().setLeft(-scrollLeft, Unit.PX); - } else { - position.set(footElem, -scrollLeft, 0); - } - - lastScrollLeft = scrollLeft; - } - - body.setBodyScrollPosition(scrollLeft, scrollTop); - - lastScrollTop = scrollTop; - body.updateEscalatorRowsOnScroll(); - body.spacerContainer.updateSpacerDecosVisibility(); - /* - * TODO [[optimize]]: Might avoid a reflow by first calculating new - * scrolltop and scrolleft, then doing the escalator magic based on - * those numbers and only updating the positions after that. - */ - } - - public native void attachScrollListener(Element element) - /* - * Attaching events with JSNI instead of the GWT event mechanism because - * GWT didn't provide enough details in events, or triggering the event - * handlers with GWT bindings was unsuccessful. Maybe, with more time - * and skill, it could be done with better success. JavaScript overlay - * types might work. This might also get rid of the JsniWorkaround - * class. - */ - /*-{ - if (element.addEventListener) { - element.addEventListener("scroll", this.@com.vaadin.client.widgets.JsniWorkaround::scrollListenerFunction); - } else { - element.attachEvent("onscroll", this.@com.vaadin.client.widgets.JsniWorkaround::scrollListenerFunction); - } - }-*/; - - public native void detachScrollListener(Element element) - /* - * Attaching events with JSNI instead of the GWT event mechanism because - * GWT didn't provide enough details in events, or triggering the event - * handlers with GWT bindings was unsuccessful. Maybe, with more time - * and skill, it could be done with better success. JavaScript overlay - * types might work. This might also get rid of the JsniWorkaround - * class. - */ - /*-{ - if (element.addEventListener) { - element.removeEventListener("scroll", this.@com.vaadin.client.widgets.JsniWorkaround::scrollListenerFunction); - } else { - element.detachEvent("onscroll", this.@com.vaadin.client.widgets.JsniWorkaround::scrollListenerFunction); - } - }-*/; - - public native void attachMousewheelListener(Element element) - /* - * Attaching events with JSNI instead of the GWT event mechanism because - * GWT didn't provide enough details in events, or triggering the event - * handlers with GWT bindings was unsuccessful. Maybe, with more time - * and skill, it could be done with better success. JavaScript overlay - * types might work. This might also get rid of the JsniWorkaround - * class. - */ - /*-{ - if (element.addEventListener) { - // firefox likes "wheel", while others use "mousewheel" - var eventName = 'onmousewheel' in element ? 'mousewheel' : 'wheel'; - element.addEventListener(eventName, this.@com.vaadin.client.widgets.JsniWorkaround::mousewheelListenerFunction); - } else { - // IE8 - element.attachEvent("onmousewheel", this.@com.vaadin.client.widgets.JsniWorkaround::mousewheelListenerFunction); - } - }-*/; - - public native void detachMousewheelListener(Element element) - /* - * Detaching events with JSNI instead of the GWT event mechanism because - * GWT didn't provide enough details in events, or triggering the event - * handlers with GWT bindings was unsuccessful. Maybe, with more time - * and skill, it could be done with better success. JavaScript overlay - * types might work. This might also get rid of the JsniWorkaround - * class. - */ - /*-{ - if (element.addEventListener) { - // firefox likes "wheel", while others use "mousewheel" - var eventName = element.onwheel===undefined?"mousewheel":"wheel"; - element.removeEventListener(eventName, this.@com.vaadin.client.widgets.JsniWorkaround::mousewheelListenerFunction); - } else { - // IE8 - element.detachEvent("onmousewheel", this.@com.vaadin.client.widgets.JsniWorkaround::mousewheelListenerFunction); - } - }-*/; - - public native void attachTouchListeners(Element element) - /* - * Detaching events with JSNI instead of the GWT event mechanism because - * GWT didn't provide enough details in events, or triggering the event - * handlers with GWT bindings was unsuccessful. Maybe, with more time - * and skill, it could be done with better success. JavaScript overlay - * types might work. This might also get rid of the JsniWorkaround - * class. - */ - /*-{ - if (element.addEventListener) { - element.addEventListener("touchstart", this.@com.vaadin.client.widgets.JsniWorkaround::touchStartFunction); - element.addEventListener("touchmove", this.@com.vaadin.client.widgets.JsniWorkaround::touchMoveFunction); - element.addEventListener("touchend", this.@com.vaadin.client.widgets.JsniWorkaround::touchEndFunction); - element.addEventListener("touchcancel", this.@com.vaadin.client.widgets.JsniWorkaround::touchEndFunction); - } else { - // this would be IE8, but we don't support it with touch - } - }-*/; - - public native void detachTouchListeners(Element element) - /* - * Detaching events with JSNI instead of the GWT event mechanism because - * GWT didn't provide enough details in events, or triggering the event - * handlers with GWT bindings was unsuccessful. Maybe, with more time - * and skill, it could be done with better success. JavaScript overlay - * types might work. This might also get rid of the JsniWorkaround - * class. - */ - /*-{ - if (element.removeEventListener) { - element.removeEventListener("touchstart", this.@com.vaadin.client.widgets.JsniWorkaround::touchStartFunction); - element.removeEventListener("touchmove", this.@com.vaadin.client.widgets.JsniWorkaround::touchMoveFunction); - element.removeEventListener("touchend", this.@com.vaadin.client.widgets.JsniWorkaround::touchEndFunction); - element.removeEventListener("touchcancel", this.@com.vaadin.client.widgets.JsniWorkaround::touchEndFunction); - } else { - // this would be IE8, but we don't support it with touch - } - }-*/; - - public void scrollToColumn(final int columnIndex, - final ScrollDestination destination, final int padding) { - assert columnIndex >= columnConfiguration.frozenColumns : "Can't scroll to a frozen column"; - - /* - * To cope with frozen columns, we just pretend those columns are - * not there at all when calculating the position of the target - * column and the boundaries of the viewport. The resulting - * scrollLeft will be correct without compensation since the DOM - * structure effectively means that scrollLeft also ignores the - * frozen columns. - */ - final double frozenPixels = columnConfiguration - .getCalculatedColumnsWidth(Range.withLength(0, - columnConfiguration.frozenColumns)); - - final double targetStartPx = columnConfiguration - .getCalculatedColumnsWidth(Range.withLength(0, columnIndex)) - - frozenPixels; - final double targetEndPx = targetStartPx - + columnConfiguration.getColumnWidthActual(columnIndex); - - final double viewportStartPx = getScrollLeft(); - double viewportEndPx = viewportStartPx - + WidgetUtil - .getRequiredWidthBoundingClientRectDouble(getElement()) - - frozenPixels; - if (verticalScrollbar.showsScrollHandle()) { - viewportEndPx -= WidgetUtil.getNativeScrollbarSize(); - } - - final double scrollLeft = getScrollPos(destination, targetStartPx, - targetEndPx, viewportStartPx, viewportEndPx, padding); - - /* - * note that it doesn't matter if the scroll would go beyond the - * content, since the browser will adjust for that, and everything - * fall into line accordingly. - */ - setScrollLeft(scrollLeft); - } - - public void scrollToRow(final int rowIndex, - final ScrollDestination destination, final double padding) { - - final double targetStartPx = (body.getDefaultRowHeight() * rowIndex) - + body.spacerContainer - .getSpacerHeightsSumUntilIndex(rowIndex); - final double targetEndPx = targetStartPx - + body.getDefaultRowHeight(); - - final double viewportStartPx = getScrollTop(); - final double viewportEndPx = viewportStartPx - + body.getHeightOfSection(); - - final double scrollTop = getScrollPos(destination, targetStartPx, - targetEndPx, viewportStartPx, viewportEndPx, padding); - - /* - * note that it doesn't matter if the scroll would go beyond the - * content, since the browser will adjust for that, and everything - * falls into line accordingly. - */ - setScrollTop(scrollTop); - } - } - - protected abstract class AbstractRowContainer implements RowContainer { - private EscalatorUpdater updater = EscalatorUpdater.NULL; - - private int rows; - - /** - * The table section element ({@code }, {@code } or - * {@code }) the rows (i.e. {@code } tags) are contained in. - */ - protected final TableSectionElement root; - - /** - * The primary style name of the escalator. Most commonly provided by - * Escalator as "v-escalator". - */ - private String primaryStyleName = null; - - private boolean defaultRowHeightShouldBeAutodetected = true; - - private double defaultRowHeight = INITIAL_DEFAULT_ROW_HEIGHT; - - public AbstractRowContainer( - final TableSectionElement rowContainerElement) { - root = rowContainerElement; - } - - @Override - public TableSectionElement getElement() { - return root; - } - - /** - * Gets the tag name of an element to represent a cell in a row. - *

- * Usually {@code "th"} or {@code "td"}. - *

- * Note: To actually create such an element, use - * {@link #createCellElement(int, int)} instead. - * - * @return the tag name for the element to represent cells as - * @see #createCellElement(int, int) - */ - protected abstract String getCellElementTagName(); - - @Override - public EscalatorUpdater getEscalatorUpdater() { - return updater; - } - - /** - * {@inheritDoc} - *

- * Implementation detail: This method does no DOM modifications - * (i.e. is very cheap to call) if there is no data for rows or columns - * when this method is called. - * - * @see #hasColumnAndRowData() - */ - @Override - public void setEscalatorUpdater(final EscalatorUpdater escalatorUpdater) { - if (escalatorUpdater == null) { - throw new IllegalArgumentException( - "escalator updater cannot be null"); - } - - updater = escalatorUpdater; - - if (hasColumnAndRowData() && getRowCount() > 0) { - refreshRows(0, getRowCount()); - } - } - - /** - * {@inheritDoc} - *

- * Implementation detail: This method does no DOM modifications - * (i.e. is very cheap to call) if there are no rows in the DOM when - * this method is called. - * - * @see #hasSomethingInDom() - */ - @Override - public void removeRows(final int index, final int numberOfRows) { - assertArgumentsAreValidAndWithinRange(index, numberOfRows); - - rows -= numberOfRows; - - if (!isAttached()) { - return; - } - - if (hasSomethingInDom()) { - paintRemoveRows(index, numberOfRows); - } - } - - /** - * Removes those row elements from the DOM that correspond to the given - * range of logical indices. This may be fewer than {@code numberOfRows} - * , even zero, if not all the removed rows are actually visible. - *

- * The implementation must call {@link #paintRemoveRow(Element, int)} - * for each row that is removed from the DOM. - * - * @param index - * the logical index of the first removed row - * @param numberOfRows - * number of logical rows to remove - */ - protected abstract void paintRemoveRows(final int index, - final int numberOfRows); - - /** - * Removes a row element from the DOM, invoking - * {@link #getEscalatorUpdater()} - * {@link EscalatorUpdater#preDetach(Row, Iterable) preDetach} and - * {@link EscalatorUpdater#postDetach(Row, Iterable) postDetach} before - * and after removing the row, respectively. - *

- * This method must be called for each removed DOM row by any - * {@link #paintRemoveRows(int, int)} implementation. - * - * @param tr - * the row element to remove. - */ - protected void paintRemoveRow(final TableRowElement tr, - final int logicalRowIndex) { - - flyweightRow.setup(tr, logicalRowIndex, - columnConfiguration.getCalculatedColumnWidths()); - - getEscalatorUpdater().preDetach(flyweightRow, - flyweightRow.getCells()); - - tr.removeFromParent(); - - getEscalatorUpdater().postDetach(flyweightRow, - flyweightRow.getCells()); - - /* - * the "assert" guarantees that this code is run only during - * development/debugging. - */ - assert flyweightRow.teardown(); - - } - - protected void assertArgumentsAreValidAndWithinRange(final int index, - final int numberOfRows) throws IllegalArgumentException, - IndexOutOfBoundsException { - if (numberOfRows < 1) { - throw new IllegalArgumentException( - "Number of rows must be 1 or greater (was " - + numberOfRows + ")"); - } - - if (index < 0 || index + numberOfRows > getRowCount()) { - throw new IndexOutOfBoundsException("The given " - + "row range (" + index + ".." + (index + numberOfRows) - + ") was outside of the current number of rows (" - + getRowCount() + ")"); - } - } - - @Override - public int getRowCount() { - return rows; - } - - /** - * This method calculates the current row count directly from the DOM. - *

- * While Escalator is stable, this value should equal to - * {@link #getRowCount()}, but while row counts are being updated, these - * two values might differ for a short while. - *

- * Any extra content, such as spacers for the body, should not be - * included in this count. - * - * @since 7.5.0 - * - * @return the actual DOM count of rows - */ - public abstract int getDomRowCount(); - - /** - * {@inheritDoc} - *

- * Implementation detail: This method does no DOM modifications - * (i.e. is very cheap to call) if there is no data for columns when - * this method is called. - * - * @see #hasColumnAndRowData() - */ - @Override - public void insertRows(final int index, final int numberOfRows) { - if (index < 0 || index > getRowCount()) { - throw new IndexOutOfBoundsException("The given index (" + index - + ") was outside of the current number of rows (0.." - + getRowCount() + ")"); - } - - if (numberOfRows < 1) { - throw new IllegalArgumentException( - "Number of rows must be 1 or greater (was " - + numberOfRows + ")"); - } - - rows += numberOfRows; - - /* - * only add items in the DOM if the widget itself is attached to the - * DOM. We can't calculate sizes otherwise. - */ - if (isAttached()) { - paintInsertRows(index, numberOfRows); - - if (rows == numberOfRows) { - /* - * We are inserting the first rows in this container. We - * potentially need to set the widths for the cells for the - * first time. - */ - Map colWidths = new HashMap(); - for (int i = 0; i < getColumnConfiguration() - .getColumnCount(); i++) { - Double width = Double.valueOf(getColumnConfiguration() - .getColumnWidth(i)); - Integer col = Integer.valueOf(i); - colWidths.put(col, width); - } - getColumnConfiguration().setColumnWidths(colWidths); - } - } - } - - /** - * Actually add rows into the DOM, now that everything can be - * calculated. - * - * @param visualIndex - * the DOM index to add rows into - * @param numberOfRows - * the number of rows to insert - * @return a list of the added row elements - */ - protected abstract void paintInsertRows(final int visualIndex, - final int numberOfRows); - - protected List paintInsertStaticRows( - final int visualIndex, final int numberOfRows) { - assert isAttached() : "Can't paint rows if Escalator is not attached"; - - final List addedRows = new ArrayList(); - - if (numberOfRows < 1) { - return addedRows; - } - - Node referenceRow; - if (root.getChildCount() != 0 && visualIndex != 0) { - // get the row node we're inserting stuff after - referenceRow = root.getChild(visualIndex - 1); - } else { - // index is 0, so just prepend. - referenceRow = null; - } - - for (int row = visualIndex; row < visualIndex + numberOfRows; row++) { - final TableRowElement tr = TableRowElement.as(DOM.createTR()); - addedRows.add(tr); - tr.addClassName(getStylePrimaryName() + "-row"); - - for (int col = 0; col < columnConfiguration.getColumnCount(); col++) { - final double colWidth = columnConfiguration - .getColumnWidthActual(col); - final TableCellElement cellElem = createCellElement(colWidth); - tr.appendChild(cellElem); - - // Set stylename and position if new cell is frozen - if (col < columnConfiguration.frozenColumns) { - cellElem.addClassName("frozen"); - position.set(cellElem, scroller.lastScrollLeft, 0); - } - if (columnConfiguration.frozenColumns > 0 - && col == columnConfiguration.frozenColumns - 1) { - cellElem.addClassName("last-frozen"); - } - } - - referenceRow = paintInsertRow(referenceRow, tr, row); - } - reapplyRowWidths(); - - recalculateSectionHeight(); - - return addedRows; - } - - /** - * Inserts a single row into the DOM, invoking - * {@link #getEscalatorUpdater()} - * {@link EscalatorUpdater#preAttach(Row, Iterable) preAttach} and - * {@link EscalatorUpdater#postAttach(Row, Iterable) postAttach} before - * and after inserting the row, respectively. The row should have its - * cells already inserted. - * - * @param referenceRow - * the row after which to insert or null if insert as first - * @param tr - * the row to be inserted - * @param logicalRowIndex - * the logical index of the inserted row - * @return the inserted row to be used as the new reference - */ - protected Node paintInsertRow(Node referenceRow, - final TableRowElement tr, int logicalRowIndex) { - flyweightRow.setup(tr, logicalRowIndex, - columnConfiguration.getCalculatedColumnWidths()); - - getEscalatorUpdater().preAttach(flyweightRow, - flyweightRow.getCells()); - - referenceRow = insertAfterReferenceAndUpdateIt(root, tr, - referenceRow); - - getEscalatorUpdater().postAttach(flyweightRow, - flyweightRow.getCells()); - updater.update(flyweightRow, flyweightRow.getCells()); - - /* - * the "assert" guarantees that this code is run only during - * development/debugging. - */ - assert flyweightRow.teardown(); - return referenceRow; - } - - private Node insertAfterReferenceAndUpdateIt(final Element parent, - final Element elem, final Node referenceNode) { - if (referenceNode != null) { - parent.insertAfter(elem, referenceNode); - } else { - /* - * referencenode being null means we have offset 0, i.e. make it - * the first row - */ - /* - * TODO [[optimize]]: Is insertFirst or append faster for an - * empty root? - */ - parent.insertFirst(elem); - } - return elem; - } - - abstract protected void recalculateSectionHeight(); - - /** - * Returns the height of all rows in the row container. - */ - protected double calculateTotalRowHeight() { - return getDefaultRowHeight() * getRowCount(); - } - - /** - * {@inheritDoc} - *

- * Implementation detail: This method does no DOM modifications - * (i.e. is very cheap to call) if there is no data for columns when - * this method is called. - * - * @see #hasColumnAndRowData() - */ - @Override - // overridden because of JavaDoc - public void refreshRows(final int index, final int numberOfRows) { - Range rowRange = Range.withLength(index, numberOfRows); - Range colRange = Range.withLength(0, getColumnConfiguration() - .getColumnCount()); - refreshCells(rowRange, colRange); - } - - protected abstract void refreshCells(Range logicalRowRange, - Range colRange); - - void refreshRow(TableRowElement tr, int logicalRowIndex) { - refreshRow(tr, logicalRowIndex, Range.withLength(0, - getColumnConfiguration().getColumnCount())); - } - - void refreshRow(final TableRowElement tr, final int logicalRowIndex, - Range colRange) { - flyweightRow.setup(tr, logicalRowIndex, - columnConfiguration.getCalculatedColumnWidths()); - Iterable cellsToUpdate = flyweightRow.getCells( - colRange.getStart(), colRange.length()); - updater.update(flyweightRow, cellsToUpdate); - - /* - * the "assert" guarantees that this code is run only during - * development/debugging. - */ - assert flyweightRow.teardown(); - } - - /** - * Create and setup an empty cell element. - * - * @param width - * the width of the cell, in pixels - * - * @return a set-up empty cell element - */ - public TableCellElement createCellElement(final double width) { - final TableCellElement cellElem = TableCellElement.as(DOM - .createElement(getCellElementTagName())); - - final double height = getDefaultRowHeight(); - assert height >= 0 : "defaultRowHeight was negative. There's a setter leak somewhere."; - cellElem.getStyle().setHeight(height, Unit.PX); - - if (width >= 0) { - cellElem.getStyle().setWidth(width, Unit.PX); - } - cellElem.addClassName(getStylePrimaryName() + "-cell"); - return cellElem; - } - - @Override - public TableRowElement getRowElement(int index) { - return getTrByVisualIndex(index); - } - - /** - * Gets the child element that is visually at a certain index - * - * @param index - * the index of the element to retrieve - * @return the element at position {@code index} - * @throws IndexOutOfBoundsException - * if {@code index} is not valid within {@link #root} - */ - protected abstract TableRowElement getTrByVisualIndex(int index) - throws IndexOutOfBoundsException; - - protected void paintRemoveColumns(final int offset, - final int numberOfColumns) { - for (int i = 0; i < getDomRowCount(); i++) { - TableRowElement row = getTrByVisualIndex(i); - flyweightRow.setup(row, i, - columnConfiguration.getCalculatedColumnWidths()); - - Iterable attachedCells = flyweightRow.getCells( - offset, numberOfColumns); - getEscalatorUpdater().preDetach(flyweightRow, attachedCells); - - for (int j = 0; j < numberOfColumns; j++) { - row.getCells().getItem(offset).removeFromParent(); - } - - Iterable detachedCells = flyweightRow - .getUnattachedCells(offset, numberOfColumns); - getEscalatorUpdater().postDetach(flyweightRow, detachedCells); - - assert flyweightRow.teardown(); - } - } - - protected void paintInsertColumns(final int offset, - final int numberOfColumns, boolean frozen) { - - for (int row = 0; row < getDomRowCount(); row++) { - final TableRowElement tr = getTrByVisualIndex(row); - int logicalRowIndex = getLogicalRowIndex(tr); - paintInsertCells(tr, logicalRowIndex, offset, numberOfColumns); - } - reapplyRowWidths(); - - if (frozen) { - for (int col = offset; col < offset + numberOfColumns; col++) { - setColumnFrozen(col, true); - } - } - } - - /** - * Inserts new cell elements into a single row element, invoking - * {@link #getEscalatorUpdater()} - * {@link EscalatorUpdater#preAttach(Row, Iterable) preAttach} and - * {@link EscalatorUpdater#postAttach(Row, Iterable) postAttach} before - * and after inserting the cells, respectively. - *

- * Precondition: The row must be already attached to the DOM and the - * FlyweightCell instances corresponding to the new columns added to - * {@code flyweightRow}. - * - * @param tr - * the row in which to insert the cells - * @param logicalRowIndex - * the index of the row - * @param offset - * the index of the first cell - * @param numberOfCells - * the number of cells to insert - */ - private void paintInsertCells(final TableRowElement tr, - int logicalRowIndex, final int offset, final int numberOfCells) { - - assert root.isOrHasChild(tr) : "The row must be attached to the document"; - - flyweightRow.setup(tr, logicalRowIndex, - columnConfiguration.getCalculatedColumnWidths()); - - Iterable cells = flyweightRow.getUnattachedCells( - offset, numberOfCells); - - for (FlyweightCell cell : cells) { - final double colWidth = columnConfiguration - .getColumnWidthActual(cell.getColumn()); - final TableCellElement cellElem = createCellElement(colWidth); - cell.setElement(cellElem); - } - - getEscalatorUpdater().preAttach(flyweightRow, cells); - - Node referenceCell; - if (offset != 0) { - referenceCell = tr.getChild(offset - 1); - } else { - referenceCell = null; - } - - for (FlyweightCell cell : cells) { - referenceCell = insertAfterReferenceAndUpdateIt(tr, - cell.getElement(), referenceCell); - } - - getEscalatorUpdater().postAttach(flyweightRow, cells); - getEscalatorUpdater().update(flyweightRow, cells); - - assert flyweightRow.teardown(); - } - - public void setColumnFrozen(int column, boolean frozen) { - toggleFrozenColumnClass(column, frozen, "frozen"); - - if (frozen) { - updateFreezePosition(column, scroller.lastScrollLeft); - } - } - - private void toggleFrozenColumnClass(int column, boolean frozen, - String className) { - final NodeList childRows = root.getRows(); - - for (int row = 0; row < childRows.getLength(); row++) { - final TableRowElement tr = childRows.getItem(row); - if (!rowCanBeFrozen(tr)) { - continue; - } - - TableCellElement cell = tr.getCells().getItem(column); - if (frozen) { - cell.addClassName(className); - } else { - cell.removeClassName(className); - position.reset(cell); - } - } - } - - public void setColumnLastFrozen(int column, boolean lastFrozen) { - toggleFrozenColumnClass(column, lastFrozen, "last-frozen"); - } - - public void updateFreezePosition(int column, double scrollLeft) { - final NodeList childRows = root.getRows(); - - for (int row = 0; row < childRows.getLength(); row++) { - final TableRowElement tr = childRows.getItem(row); - - if (rowCanBeFrozen(tr)) { - TableCellElement cell = tr.getCells().getItem(column); - position.set(cell, scrollLeft, 0); - } - } - } - - /** - * Checks whether a row is an element, or contains such elements, that - * can be frozen. - *

- * In practice, this applies for all header and footer rows. For body - * rows, it applies for all rows except spacer rows. - * - * @since 7.5.0 - * - * @param tr - * the row element to check for if it is or has elements that - * can be frozen - * @return true iff this the given element, or any of its - * descendants, can be frozen - */ - abstract protected boolean rowCanBeFrozen(TableRowElement tr); - - /** - * Iterates through all the cells in a column and returns the width of - * the widest element in this RowContainer. - * - * @param index - * the index of the column to inspect - * @return the pixel width of the widest element in the indicated column - */ - public double calculateMaxColWidth(int index) { - TableRowElement row = TableRowElement.as(root - .getFirstChildElement()); - double maxWidth = 0; - while (row != null) { - final TableCellElement cell = row.getCells().getItem(index); - final boolean isVisible = !cell.getStyle().getDisplay() - .equals(Display.NONE.getCssName()); - if (isVisible) { - maxWidth = Math.max(maxWidth, WidgetUtil - .getRequiredWidthBoundingClientRectDouble(cell)); - } - row = TableRowElement.as(row.getNextSiblingElement()); - } - return maxWidth; - } - - /** - * Reapplies all the cells' widths according to the calculated widths in - * the column configuration. - */ - public void reapplyColumnWidths() { - Element row = root.getFirstChildElement(); - while (row != null) { - // Only handle non-spacer rows - if (!body.spacerContainer.isSpacer(row)) { - Element cell = row.getFirstChildElement(); - int columnIndex = 0; - while (cell != null) { - final double width = getCalculatedColumnWidthWithColspan( - cell, columnIndex); - - /* - * TODO Should Escalator implement ProvidesResize at - * some point, this is where we need to do that. - */ - cell.getStyle().setWidth(width, Unit.PX); - - cell = cell.getNextSiblingElement(); - columnIndex++; - } - } - row = row.getNextSiblingElement(); - } - - reapplyRowWidths(); - } - - private double getCalculatedColumnWidthWithColspan(final Element cell, - final int columnIndex) { - final int colspan = cell.getPropertyInt(FlyweightCell.COLSPAN_ATTR); - Range spannedColumns = Range.withLength(columnIndex, colspan); - - /* - * Since browsers don't explode with overflowing colspans, escalator - * shouldn't either. - */ - if (spannedColumns.getEnd() > columnConfiguration.getColumnCount()) { - spannedColumns = Range.between(columnIndex, - columnConfiguration.getColumnCount()); - } - return columnConfiguration - .getCalculatedColumnsWidth(spannedColumns); - } - - /** - * Applies the total length of the columns to each row element. - *

- * Note: In contrast to {@link #reapplyColumnWidths()}, this - * method only modifies the width of the {@code } element, not the - * cells within. - */ - protected void reapplyRowWidths() { - double rowWidth = columnConfiguration.calculateRowWidth(); - if (rowWidth < 0) { - return; - } - - Element row = root.getFirstChildElement(); - while (row != null) { - // IF there is a rounding error when summing the columns, we - // need to round the tr width up to ensure that columns fit and - // do not wrap - // E.g.122.95+123.25+103.75+209.25+83.52+88.57+263.45+131.21+126.85+113.13=1365.9299999999998 - // For this we must set 1365.93 or the last column will wrap - row.getStyle().setWidth(WidgetUtil.roundSizeUp(rowWidth), - Unit.PX); - row = row.getNextSiblingElement(); - } - } - - /** - * The primary style name for the container. - * - * @param primaryStyleName - * the style name to use as prefix for all row and cell style - * names. - */ - protected void setStylePrimaryName(String primaryStyleName) { - String oldStyle = getStylePrimaryName(); - if (SharedUtil.equals(oldStyle, primaryStyleName)) { - return; - } - - this.primaryStyleName = primaryStyleName; - - // Update already rendered rows and cells - Element row = root.getRows().getItem(0); - while (row != null) { - UIObject.setStylePrimaryName(row, primaryStyleName + "-row"); - Element cell = TableRowElement.as(row).getCells().getItem(0); - while (cell != null) { - assert TableCellElement.is(cell); - UIObject.setStylePrimaryName(cell, primaryStyleName - + "-cell"); - cell = cell.getNextSiblingElement(); - } - row = row.getNextSiblingElement(); - } - } - - /** - * Returns the primary style name of the container. - * - * @return The primary style name or null if not set. - */ - protected String getStylePrimaryName() { - return primaryStyleName; - } - - @Override - public void setDefaultRowHeight(double px) - throws IllegalArgumentException { - if (px < 1) { - throw new IllegalArgumentException("Height must be positive. " - + px + " was given."); - } - - defaultRowHeightShouldBeAutodetected = false; - defaultRowHeight = px; - reapplyDefaultRowHeights(); - } - - @Override - public double getDefaultRowHeight() { - return defaultRowHeight; - } - - /** - * The default height of rows has (most probably) changed. - *

- * Make sure that the displayed rows with a default height are updated - * in height and top position. - *

- * Note:This implementation should not call - * {@link Escalator#recalculateElementSizes()} - it is done by the - * discretion of the caller of this method. - */ - protected abstract void reapplyDefaultRowHeights(); - - protected void reapplyRowHeight(final TableRowElement tr, - final double heightPx) { - assert heightPx >= 0 : "Height must not be negative"; - - Element cellElem = tr.getFirstChildElement(); - while (cellElem != null) { - cellElem.getStyle().setHeight(heightPx, Unit.PX); - cellElem = cellElem.getNextSiblingElement(); - } - - /* - * no need to apply height to tr-element, it'll be resized - * implicitly. - */ - } - - protected void setRowPosition(final TableRowElement tr, final int x, - final double y) { - positions.set(tr, x, y); - } - - /** - * Returns the assigned top position for the given element. - *

- * Note: This method does not calculate what a row's top - * position should be. It just returns an assigned value, correct or - * not. - * - * @param tr - * the table row element to measure - * @return the current top position for {@code tr} - * @see BodyRowContainerImpl#getRowTop(int) - */ - protected double getRowTop(final TableRowElement tr) { - return positions.getTop(tr); - } - - protected void removeRowPosition(TableRowElement tr) { - positions.remove(tr); - } - - public void autodetectRowHeightLater() { - Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() { - @Override - public void execute() { - if (defaultRowHeightShouldBeAutodetected && isAttached()) { - autodetectRowHeightNow(); - defaultRowHeightShouldBeAutodetected = false; - } - } - }); - } - - public void autodetectRowHeightNow() { - if (!isAttached()) { - // Run again when attached - defaultRowHeightShouldBeAutodetected = true; - return; - } - - final Element detectionTr = DOM.createTR(); - detectionTr.setClassName(getStylePrimaryName() + "-row"); - - final Element cellElem = DOM.createElement(getCellElementTagName()); - cellElem.setClassName(getStylePrimaryName() + "-cell"); - cellElem.setInnerText("Ij"); - - detectionTr.appendChild(cellElem); - root.appendChild(detectionTr); - double boundingHeight = WidgetUtil - .getRequiredHeightBoundingClientRectDouble(cellElem); - defaultRowHeight = Math.max(1.0d, boundingHeight); - root.removeChild(detectionTr); - - if (root.hasChildNodes()) { - reapplyDefaultRowHeights(); - applyHeightByRows(); - } - } - - @Override - public Cell getCell(final Element element) { - if (element == null) { - throw new IllegalArgumentException("Element cannot be null"); - } - - /* - * Ensure that element is not root nor the direct descendant of root - * (a row) and ensure the element is inside the dom hierarchy of the - * root element. If not, return. - */ - if (root == element || element.getParentElement() == root - || !root.isOrHasChild(element)) { - return null; - } - - /* - * Ensure element is the cell element by iterating up the DOM - * hierarchy until reaching cell element. - */ - Element cellElementCandidate = element; - while (cellElementCandidate.getParentElement().getParentElement() != root) { - cellElementCandidate = cellElementCandidate.getParentElement(); - } - final TableCellElement cellElement = TableCellElement - .as(cellElementCandidate); - - // Find dom column - int domColumnIndex = -1; - for (Element e = cellElement; e != null; e = e - .getPreviousSiblingElement()) { - domColumnIndex++; - } - - // Find dom row - int domRowIndex = -1; - for (Element e = cellElement.getParentElement(); e != null; e = e - .getPreviousSiblingElement()) { - domRowIndex++; - } - - return new Cell(domRowIndex, domColumnIndex, cellElement); - } - - double measureCellWidth(TableCellElement cell, boolean withContent) { - /* - * To get the actual width of the contents, we need to get the cell - * content without any hardcoded height or width. - * - * But we don't want to modify the existing column, because that - * might trigger some unnecessary listeners and whatnot. So, - * instead, we make a deep clone of that cell, but without any - * explicit dimensions, and measure that instead. - */ - - TableCellElement cellClone = TableCellElement.as((Element) cell - .cloneNode(withContent)); - cellClone.getStyle().clearHeight(); - cellClone.getStyle().clearWidth(); - - cell.getParentElement().insertBefore(cellClone, cell); - double requiredWidth = WidgetUtil - .getRequiredWidthBoundingClientRectDouble(cellClone); - if (BrowserInfo.get().isIE()) { - /* - * IE browsers have some issues with subpixels. Occasionally - * content is overflown even if not necessary. Increase the - * counted required size by 0.01 just to be on the safe side. - */ - requiredWidth += 0.01; - } - - cellClone.removeFromParent(); - - return requiredWidth; - } - - /** - * Gets the minimum width needed to display the cell properly. - * - * @param colIndex - * index of column to measure - * @param withContent - * true if content is taken into account, - * false if not - * @return cell width needed for displaying correctly - */ - double measureMinCellWidth(int colIndex, boolean withContent) { - assert isAttached() : "Can't measure max width of cell, since Escalator is not attached to the DOM."; - - double minCellWidth = -1; - NodeList rows = root.getRows(); - - for (int row = 0; row < rows.getLength(); row++) { - - TableCellElement cell = rows.getItem(row).getCells() - .getItem(colIndex); - - if (cell != null && !cellIsPartOfSpan(cell)) { - double cellWidth = measureCellWidth(cell, withContent); - minCellWidth = Math.max(minCellWidth, cellWidth); - } - } - - return minCellWidth; - } - - private boolean cellIsPartOfSpan(TableCellElement cell) { - boolean cellHasColspan = cell.getColSpan() > 1; - boolean cellIsHidden = Display.NONE.getCssName().equals( - cell.getStyle().getDisplay()); - return cellHasColspan || cellIsHidden; - } - - void refreshColumns(int index, int numberOfColumns) { - if (getRowCount() > 0) { - Range rowRange = Range.withLength(0, getRowCount()); - Range colRange = Range.withLength(index, numberOfColumns); - refreshCells(rowRange, colRange); - } - } - - /** - * The height of this table section. - *

- * Note that {@link Escalator#getBody() the body} will calculate its - * height, while the others will return a precomputed value. - * - * @since 7.5.0 - * - * @return the height of this table section - */ - protected abstract double getHeightOfSection(); - - protected int getLogicalRowIndex(final TableRowElement tr) { - return tr.getSectionRowIndex(); - }; - - } - - private abstract class AbstractStaticRowContainer extends - AbstractRowContainer { - - /** The height of the combined rows in the DOM. Never negative. */ - private double heightOfSection = 0; - - public AbstractStaticRowContainer(final TableSectionElement headElement) { - super(headElement); - } - - @Override - public int getDomRowCount() { - return root.getChildCount(); - } - - @Override - protected void paintRemoveRows(final int index, final int numberOfRows) { - for (int i = index; i < index + numberOfRows; i++) { - final TableRowElement tr = root.getRows().getItem(index); - paintRemoveRow(tr, index); - } - recalculateSectionHeight(); - } - - @Override - protected TableRowElement getTrByVisualIndex(final int index) - throws IndexOutOfBoundsException { - if (index >= 0 && index < root.getChildCount()) { - return root.getRows().getItem(index); - } else { - throw new IndexOutOfBoundsException("No such visual index: " - + index); - } - } - - @Override - public void insertRows(int index, int numberOfRows) { - super.insertRows(index, numberOfRows); - recalculateElementSizes(); - applyHeightByRows(); - } - - @Override - public void removeRows(int index, int numberOfRows) { - - /* - * While the rows in a static section are removed, the scrollbar is - * temporarily shrunk and then re-expanded. This leads to the fact - * that the scroll position is scooted up a bit. This means that we - * need to reset the position here. - * - * If Escalator, at some point, gets a JIT evaluation functionality, - * this re-setting is a strong candidate for removal. - */ - double oldScrollPos = verticalScrollbar.getScrollPos(); - - super.removeRows(index, numberOfRows); - recalculateElementSizes(); - applyHeightByRows(); - - verticalScrollbar.setScrollPos(oldScrollPos); - } - - @Override - protected void reapplyDefaultRowHeights() { - if (root.getChildCount() == 0) { - return; - } - - Profiler.enter("Escalator.AbstractStaticRowContainer.reapplyDefaultRowHeights"); - - Element tr = root.getRows().getItem(0); - while (tr != null) { - reapplyRowHeight(TableRowElement.as(tr), getDefaultRowHeight()); - tr = tr.getNextSiblingElement(); - } - - /* - * Because all rows are immediately displayed in the static row - * containers, the section's overall height has most probably - * changed. - */ - recalculateSectionHeight(); - - Profiler.leave("Escalator.AbstractStaticRowContainer.reapplyDefaultRowHeights"); - } - - @Override - protected void recalculateSectionHeight() { - Profiler.enter("Escalator.AbstractStaticRowContainer.recalculateSectionHeight"); - - double newHeight = calculateTotalRowHeight(); - if (newHeight != heightOfSection) { - heightOfSection = newHeight; - sectionHeightCalculated(); - - /* - * We need to update the scrollbar dimension at this point. If - * we are scrolled too far down and the static section shrinks, - * the body will try to render rows that don't exist during - * body.verifyEscalatorCount. This is because the logical row - * indices are calculated from the scrollbar position. - */ - verticalScrollbar.setOffsetSize(heightOfEscalator - - header.getHeightOfSection() - - footer.getHeightOfSection()); - - body.verifyEscalatorCount(); - body.spacerContainer.updateSpacerDecosVisibility(); - } - - Profiler.leave("Escalator.AbstractStaticRowContainer.recalculateSectionHeight"); - } - - /** - * Informs the row container that the height of its respective table - * section has changed. - *

- * These calculations might affect some layouting logic, such as the - * body is being offset by the footer, the footer needs to be readjusted - * according to its height, and so on. - *

- * A table section is either header, body or footer. - */ - protected abstract void sectionHeightCalculated(); - - @Override - protected void refreshCells(Range logicalRowRange, Range colRange) { - Profiler.enter("Escalator.AbstractStaticRowContainer.refreshRows"); - - assertArgumentsAreValidAndWithinRange(logicalRowRange.getStart(), - logicalRowRange.length()); - - if (!isAttached()) { - return; - } - - if (hasColumnAndRowData()) { - for (int row = logicalRowRange.getStart(); row < logicalRowRange - .getEnd(); row++) { - final TableRowElement tr = getTrByVisualIndex(row); - refreshRow(tr, row, colRange); - } - } - - Profiler.leave("Escalator.AbstractStaticRowContainer.refreshRows"); - } - - @Override - protected void paintInsertRows(int visualIndex, int numberOfRows) { - paintInsertStaticRows(visualIndex, numberOfRows); - } - - @Override - protected boolean rowCanBeFrozen(TableRowElement tr) { - assert root.isOrHasChild(tr) : "Row does not belong to this table section"; - return true; - } - - @Override - protected double getHeightOfSection() { - return Math.max(0, heightOfSection); - } - } - - private class HeaderRowContainer extends AbstractStaticRowContainer { - public HeaderRowContainer(final TableSectionElement headElement) { - super(headElement); - } - - @Override - protected void sectionHeightCalculated() { - double heightOfSection = getHeightOfSection(); - bodyElem.getStyle().setMarginTop(heightOfSection, Unit.PX); - spacerDecoContainer.getStyle().setMarginTop(heightOfSection, - Unit.PX); - verticalScrollbar.getElement().getStyle() - .setTop(heightOfSection, Unit.PX); - headerDeco.getStyle().setHeight(heightOfSection, Unit.PX); - } - - @Override - protected String getCellElementTagName() { - return "th"; - } - - @Override - public void setStylePrimaryName(String primaryStyleName) { - super.setStylePrimaryName(primaryStyleName); - UIObject.setStylePrimaryName(root, primaryStyleName + "-header"); - } - } - - private class FooterRowContainer extends AbstractStaticRowContainer { - public FooterRowContainer(final TableSectionElement footElement) { - super(footElement); - } - - @Override - public void setStylePrimaryName(String primaryStyleName) { - super.setStylePrimaryName(primaryStyleName); - UIObject.setStylePrimaryName(root, primaryStyleName + "-footer"); - } - - @Override - protected String getCellElementTagName() { - return "td"; - } - - @Override - protected void sectionHeightCalculated() { - double headerHeight = header.getHeightOfSection(); - double footerHeight = footer.getHeightOfSection(); - int vscrollHeight = (int) Math.floor(heightOfEscalator - - headerHeight - footerHeight); - - final boolean horizontalScrollbarNeeded = columnConfiguration - .calculateRowWidth() > widthOfEscalator; - if (horizontalScrollbarNeeded) { - vscrollHeight -= horizontalScrollbar.getScrollbarThickness(); - } - - footerDeco.getStyle().setHeight(footer.getHeightOfSection(), - Unit.PX); - - verticalScrollbar.setOffsetSize(vscrollHeight); - } - } - - private class BodyRowContainerImpl extends AbstractRowContainer implements - BodyRowContainer { - /* - * TODO [[optimize]]: check whether a native JsArray might be faster - * than LinkedList - */ - /** - * The order in which row elements are rendered visually in the browser, - * with the help of CSS tricks. Usually has nothing to do with the DOM - * order. - * - * @see #sortDomElements() - */ - private final LinkedList visualRowOrder = new LinkedList(); - - /** - * The logical index of the topmost row. - * - * @deprecated Use the accessors {@link #setTopRowLogicalIndex(int)}, - * {@link #updateTopRowLogicalIndex(int)} and - * {@link #getTopRowLogicalIndex()} instead - */ - @Deprecated - private int topRowLogicalIndex = 0; - - private void setTopRowLogicalIndex(int topRowLogicalIndex) { - if (LogConfiguration.loggingIsEnabled(Level.INFO)) { - Logger.getLogger("Escalator.BodyRowContainer").fine( - "topRowLogicalIndex: " + this.topRowLogicalIndex - + " -> " + topRowLogicalIndex); - } - assert topRowLogicalIndex >= 0 : "topRowLogicalIndex became negative (top left cell contents: " - + visualRowOrder.getFirst().getCells().getItem(0) - .getInnerText() + ") "; - /* - * if there's a smart way of evaluating and asserting the max index, - * this would be a nice place to put it. I haven't found out an - * effective and generic solution. - */ - - this.topRowLogicalIndex = topRowLogicalIndex; - } - - public int getTopRowLogicalIndex() { - return topRowLogicalIndex; - } - - private void updateTopRowLogicalIndex(int diff) { - setTopRowLogicalIndex(topRowLogicalIndex + diff); - } - - private class DeferredDomSorter { - private static final int SORT_DELAY_MILLIS = 50; - - // as it happens, 3 frames = 50ms @ 60fps. - private static final int REQUIRED_FRAMES_PASSED = 3; - - private final AnimationCallback frameCounter = new AnimationCallback() { - @Override - public void execute(double timestamp) { - framesPassed++; - boolean domWasSorted = sortIfConditionsMet(); - if (!domWasSorted) { - animationHandle = AnimationScheduler.get() - .requestAnimationFrame(this); - } else { - waiting = false; - } - } - }; - - private int framesPassed; - private double startTime; - private AnimationHandle animationHandle; - - /** true if a sort is scheduled */ - public boolean waiting = false; - - public void reschedule() { - waiting = true; - resetConditions(); - animationHandle = AnimationScheduler.get() - .requestAnimationFrame(frameCounter); - } - - private boolean sortIfConditionsMet() { - boolean enoughFramesHavePassed = framesPassed >= REQUIRED_FRAMES_PASSED; - boolean enoughTimeHasPassed = (Duration.currentTimeMillis() - startTime) >= SORT_DELAY_MILLIS; - boolean notTouchActivity = !scroller.touchHandlerBundle.touching; - boolean conditionsMet = enoughFramesHavePassed - && enoughTimeHasPassed && notTouchActivity; - - if (conditionsMet) { - resetConditions(); - sortDomElements(); - } - - return conditionsMet; - } - - private void resetConditions() { - if (animationHandle != null) { - animationHandle.cancel(); - animationHandle = null; - } - startTime = Duration.currentTimeMillis(); - framesPassed = 0; - } - } - - private DeferredDomSorter domSorter = new DeferredDomSorter(); - - private final SpacerContainer spacerContainer = new SpacerContainer(); - - public BodyRowContainerImpl(final TableSectionElement bodyElement) { - super(bodyElement); - } - - @Override - public void setStylePrimaryName(String primaryStyleName) { - super.setStylePrimaryName(primaryStyleName); - UIObject.setStylePrimaryName(root, primaryStyleName + "-body"); - spacerContainer.setStylePrimaryName(primaryStyleName); - } - - public void updateEscalatorRowsOnScroll() { - if (visualRowOrder.isEmpty()) { - return; - } - - boolean rowsWereMoved = false; - - final double topElementPosition; - final double nextRowBottomOffset; - SpacerContainer.SpacerImpl topSpacer = spacerContainer - .getSpacer(getTopRowLogicalIndex() - 1); - - if (topSpacer != null) { - topElementPosition = topSpacer.getTop(); - nextRowBottomOffset = topSpacer.getHeight() - + getDefaultRowHeight(); - } else { - topElementPosition = getRowTop(visualRowOrder.getFirst()); - nextRowBottomOffset = getDefaultRowHeight(); - } - - // TODO [[mpixscroll]] - final double scrollTop = tBodyScrollTop; - final double viewportOffset = topElementPosition - scrollTop; - - /* - * TODO [[optimize]] this if-else can most probably be refactored - * into a neater block of code - */ - - if (viewportOffset > 0) { - // there's empty room on top - - double rowPx = getRowHeightsSumBetweenPx(scrollTop, - topElementPosition); - int originalRowsToMove = (int) Math.ceil(rowPx - / getDefaultRowHeight()); - int rowsToMove = Math.min(originalRowsToMove, - visualRowOrder.size()); - - final int end = visualRowOrder.size(); - final int start = end - rowsToMove; - final int logicalRowIndex = getLogicalRowIndex(scrollTop); - - moveAndUpdateEscalatorRows(Range.between(start, end), 0, - logicalRowIndex); - - setTopRowLogicalIndex(logicalRowIndex); - - rowsWereMoved = true; - } - - else if (viewportOffset + nextRowBottomOffset <= 0) { - /* - * the viewport has been scrolled more than the topmost visual - * row. - */ - - double rowPx = getRowHeightsSumBetweenPx(topElementPosition, - scrollTop); - - int originalRowsToMove = (int) (rowPx / getDefaultRowHeight()); - int rowsToMove = Math.min(originalRowsToMove, - visualRowOrder.size()); - - int logicalRowIndex; - if (rowsToMove < visualRowOrder.size()) { - /* - * We scroll so little that we can just keep adding the rows - * below the current escalator - */ - logicalRowIndex = getLogicalRowIndex(visualRowOrder - .getLast()) + 1; - } else { - /* - * Since we're moving all escalator rows, we need to - * calculate the first logical row index from the scroll - * position. - */ - logicalRowIndex = getLogicalRowIndex(scrollTop); - } - - /* - * Since we're moving the viewport downwards, the visual index - * is always at the bottom. Note: Due to how - * moveAndUpdateEscalatorRows works, this will work out even if - * we move all the rows, and try to place them "at the end". - */ - final int targetVisualIndex = visualRowOrder.size(); - - // make sure that we don't move rows over the data boundary - boolean aRowWasLeftBehind = false; - if (logicalRowIndex + rowsToMove > getRowCount()) { - /* - * TODO [[spacer]]: with constant row heights, there's - * always exactly one row that will be moved beyond the data - * source, when viewport is scrolled to the end. This, - * however, isn't guaranteed anymore once row heights start - * varying. - */ - rowsToMove--; - aRowWasLeftBehind = true; - } - - /* - * Make sure we don't scroll beyond the row content. This can - * happen if we have spacers for the last rows. - */ - rowsToMove = Math.max(0, - Math.min(rowsToMove, getRowCount() - logicalRowIndex)); - - moveAndUpdateEscalatorRows(Range.between(0, rowsToMove), - targetVisualIndex, logicalRowIndex); - - if (aRowWasLeftBehind) { - /* - * To keep visualRowOrder as a spatially contiguous block of - * rows, let's make sure that the one row we didn't move - * visually still stays with the pack. - */ - final Range strayRow = Range.withOnly(0); - - /* - * We cannot trust getLogicalRowIndex, because it hasn't yet - * been updated. But since we're leaving rows behind, it - * means we've scrolled to the bottom. So, instead, we - * simply count backwards from the end. - */ - final int topLogicalIndex = getRowCount() - - visualRowOrder.size(); - moveAndUpdateEscalatorRows(strayRow, 0, topLogicalIndex); - } - - final int naiveNewLogicalIndex = getTopRowLogicalIndex() - + originalRowsToMove; - final int maxLogicalIndex = getRowCount() - - visualRowOrder.size(); - setTopRowLogicalIndex(Math.min(naiveNewLogicalIndex, - maxLogicalIndex)); - - rowsWereMoved = true; - } - - if (rowsWereMoved) { - fireRowVisibilityChangeEvent(); - domSorter.reschedule(); - } - } - - private double getRowHeightsSumBetweenPx(double y1, double y2) { - assert y1 < y2 : "y1 must be smaller than y2"; - - double viewportPx = y2 - y1; - double spacerPx = spacerContainer.getSpacerHeightsSumBetweenPx(y1, - SpacerInclusionStrategy.PARTIAL, y2, - SpacerInclusionStrategy.PARTIAL); - - return viewportPx - spacerPx; - } - - private int getLogicalRowIndex(final double px) { - double rowPx = px - spacerContainer.getSpacerHeightsSumUntilPx(px); - return (int) (rowPx / getDefaultRowHeight()); - } - - @Override - protected void paintInsertRows(final int index, final int numberOfRows) { - if (numberOfRows == 0) { - return; - } - - spacerContainer.shiftSpacersByRows(index, numberOfRows); - - /* - * TODO: this method should probably only add physical rows, and not - * populate them - let everything be populated as appropriate by the - * logic that follows. - * - * This also would lead to the fact that paintInsertRows wouldn't - * need to return anything. - */ - final List addedRows = fillAndPopulateEscalatorRowsIfNeeded( - index, numberOfRows); - - /* - * insertRows will always change the number of rows - update the - * scrollbar sizes. - */ - scroller.recalculateScrollbarsForVirtualViewport(); - - final boolean addedRowsAboveCurrentViewport = index - * getDefaultRowHeight() < getScrollTop(); - final boolean addedRowsBelowCurrentViewport = index - * getDefaultRowHeight() > getScrollTop() - + getHeightOfSection(); - - if (addedRowsAboveCurrentViewport) { - /* - * We need to tweak the virtual viewport (scroll handle - * positions, table "scroll position" and row locations), but - * without re-evaluating any rows. - */ - - final double yDelta = numberOfRows * getDefaultRowHeight(); - moveViewportAndContent(yDelta); - updateTopRowLogicalIndex(numberOfRows); - } - - else if (addedRowsBelowCurrentViewport) { - // NOOP, we already recalculated scrollbars. - } - - else { // some rows were added inside the current viewport - - final int unupdatedLogicalStart = index + addedRows.size(); - final int visualOffset = getLogicalRowIndex(visualRowOrder - .getFirst()); - - /* - * At this point, we have added new escalator rows, if so - * needed. - * - * If more rows were added than the new escalator rows can - * account for, we need to start to spin the escalator to update - * the remaining rows aswell. - */ - final int rowsStillNeeded = numberOfRows - addedRows.size(); - - if (rowsStillNeeded > 0) { - final Range unupdatedVisual = convertToVisual(Range - .withLength(unupdatedLogicalStart, rowsStillNeeded)); - final int end = getDomRowCount(); - final int start = end - unupdatedVisual.length(); - final int visualTargetIndex = unupdatedLogicalStart - - visualOffset; - moveAndUpdateEscalatorRows(Range.between(start, end), - visualTargetIndex, unupdatedLogicalStart); - - // move the surrounding rows to their correct places. - double rowTop = (unupdatedLogicalStart + (end - start)) - * getDefaultRowHeight(); - - // TODO: Get rid of this try/catch block by fixing the - // underlying issue. The reason for this erroneous behavior - // might be that Escalator actually works 'by mistake', and - // the order of operations is, in fact, wrong. - try { - final ListIterator i = visualRowOrder - .listIterator(visualTargetIndex + (end - start)); - - int logicalRowIndexCursor = unupdatedLogicalStart; - while (i.hasNext()) { - rowTop += spacerContainer - .getSpacerHeight(logicalRowIndexCursor++); - - final TableRowElement tr = i.next(); - setRowPosition(tr, 0, rowTop); - rowTop += getDefaultRowHeight(); - } - } catch (Exception e) { - Logger logger = getLogger(); - logger.warning("Ignored out-of-bounds row element access"); - logger.warning("Escalator state: start=" + start - + ", end=" + end + ", visualTargetIndex=" - + visualTargetIndex - + ", visualRowOrder.size()=" - + visualRowOrder.size()); - logger.warning(e.toString()); - } - } - - fireRowVisibilityChangeEvent(); - sortDomElements(); - } - } - - /** - * Move escalator rows around, and make sure everything gets - * appropriately repositioned and repainted. - * - * @param visualSourceRange - * the range of rows to move to a new place - * @param visualTargetIndex - * the visual index where the rows will be placed to - * @param logicalTargetIndex - * the logical index to be assigned to the first moved row - */ - private void moveAndUpdateEscalatorRows(final Range visualSourceRange, - final int visualTargetIndex, final int logicalTargetIndex) - throws IllegalArgumentException { - - if (visualSourceRange.isEmpty()) { - return; - } - - assert visualSourceRange.getStart() >= 0 : "Visual source start " - + "must be 0 or greater (was " - + visualSourceRange.getStart() + ")"; - - assert logicalTargetIndex >= 0 : "Logical target must be 0 or " - + "greater (was " + logicalTargetIndex + ")"; - - assert visualTargetIndex >= 0 : "Visual target must be 0 or greater (was " - + visualTargetIndex + ")"; - - assert visualTargetIndex <= getDomRowCount() : "Visual target " - + "must not be greater than the number of escalator rows (was " - + visualTargetIndex + ", escalator rows " - + getDomRowCount() + ")"; - - assert logicalTargetIndex + visualSourceRange.length() <= getRowCount() : "Logical " - + "target leads to rows outside of the data range (" - + Range.withLength(logicalTargetIndex, - visualSourceRange.length()) - + " goes beyond " - + Range.withLength(0, getRowCount()) + ")"; - - /* - * Since we move a range into another range, the indices might move - * about. Having 10 rows, if we move 0..1 to index 10 (to the end of - * the collection), the target range will end up being 8..9, instead - * of 10..11. - * - * This applies only if we move elements forward in the collection, - * not backward. - */ - final int adjustedVisualTargetIndex; - if (visualSourceRange.getStart() < visualTargetIndex) { - adjustedVisualTargetIndex = visualTargetIndex - - visualSourceRange.length(); - } else { - adjustedVisualTargetIndex = visualTargetIndex; - } - - if (visualSourceRange.getStart() != adjustedVisualTargetIndex) { - - /* - * Reorder the rows to their correct places within - * visualRowOrder (unless rows are moved back to their original - * places) - */ - - /* - * TODO [[optimize]]: move whichever set is smaller: the ones - * explicitly moved, or the others. So, with 10 escalator rows, - * if we are asked to move idx[0..8] to the end of the list, - * it's faster to just move idx[9] to the beginning. - */ - - final List removedRows = new ArrayList( - visualSourceRange.length()); - for (int i = 0; i < visualSourceRange.length(); i++) { - final TableRowElement tr = visualRowOrder - .remove(visualSourceRange.getStart()); - removedRows.add(tr); - } - visualRowOrder.addAll(adjustedVisualTargetIndex, removedRows); - } - - { // Refresh the contents of the affected rows - final ListIterator iter = visualRowOrder - .listIterator(adjustedVisualTargetIndex); - for (int logicalIndex = logicalTargetIndex; logicalIndex < logicalTargetIndex - + visualSourceRange.length(); logicalIndex++) { - final TableRowElement tr = iter.next(); - refreshRow(tr, logicalIndex); - } - } - - { // Reposition the rows that were moved - double newRowTop = getRowTop(logicalTargetIndex); - - final ListIterator iter = visualRowOrder - .listIterator(adjustedVisualTargetIndex); - for (int i = 0; i < visualSourceRange.length(); i++) { - final TableRowElement tr = iter.next(); - setRowPosition(tr, 0, newRowTop); - - newRowTop += getDefaultRowHeight(); - newRowTop += spacerContainer - .getSpacerHeight(logicalTargetIndex + i); - } - } - } - - /** - * Adjust the scroll position and move the contained rows. - *

- * The difference between using this method and simply scrolling is that - * this method "takes the rows and spacers with it" and renders them - * appropriately. The viewport may be scrolled any arbitrary amount, and - * the contents are moved appropriately, but always snapped into a - * plausible place. - *

- *

- *
Example 1
- *
An Escalator with default row height 20px. Adjusting the scroll - * position with 7.5px will move the viewport 7.5px down, but leave the - * row where it is.
- *
Example 2
- *
An Escalator with default row height 20px. Adjusting the scroll - * position with 27.5px will move the viewport 27.5px down, and place - * the row at 20px.
- *
- * - * @param yDelta - * the delta of pixels by which to move the viewport and - * content. A positive value moves everything downwards, - * while a negative value moves everything upwards - */ - public void moveViewportAndContent(final double yDelta) { - - if (yDelta == 0) { - return; - } - - double newTop = tBodyScrollTop + yDelta; - verticalScrollbar.setScrollPos(newTop); - - final double defaultRowHeight = getDefaultRowHeight(); - double rowPxDelta = yDelta - (yDelta % defaultRowHeight); - int rowIndexDelta = (int) (yDelta / defaultRowHeight); - if (!WidgetUtil.pixelValuesEqual(rowPxDelta, 0)) { - - Collection spacers = spacerContainer - .getSpacersAfterPx(tBodyScrollTop, - SpacerInclusionStrategy.PARTIAL); - for (SpacerContainer.SpacerImpl spacer : spacers) { - spacer.setPositionDiff(0, rowPxDelta); - spacer.setRowIndex(spacer.getRow() + rowIndexDelta); - } - - for (TableRowElement tr : visualRowOrder) { - setRowPosition(tr, 0, getRowTop(tr) + rowPxDelta); - } - } - - setBodyScrollPosition(tBodyScrollLeft, newTop); - } - - /** - * Adds new physical escalator rows to the DOM at the given index if - * there's still a need for more escalator rows. - *

- * If Escalator already is at (or beyond) max capacity, this method does - * nothing to the DOM. - * - * @param index - * the index at which to add new escalator rows. - * Note:It is assumed that the index is both the - * visual index and the logical index. - * @param numberOfRows - * the number of rows to add at index - * @return a list of the added rows - */ - private List fillAndPopulateEscalatorRowsIfNeeded( - final int index, final int numberOfRows) { - - final int escalatorRowsStillFit = getMaxEscalatorRowCapacity() - - getDomRowCount(); - final int escalatorRowsNeeded = Math.min(numberOfRows, - escalatorRowsStillFit); - - if (escalatorRowsNeeded > 0) { - - final List addedRows = paintInsertStaticRows( - index, escalatorRowsNeeded); - visualRowOrder.addAll(index, addedRows); - - double y = index * getDefaultRowHeight() - + spacerContainer.getSpacerHeightsSumUntilIndex(index); - for (int i = index; i < visualRowOrder.size(); i++) { - - final TableRowElement tr; - if (i - index < addedRows.size()) { - tr = addedRows.get(i - index); - } else { - tr = visualRowOrder.get(i); - } - - setRowPosition(tr, 0, y); - y += getDefaultRowHeight(); - y += spacerContainer.getSpacerHeight(i); - } - - return addedRows; - } else { - return Collections.emptyList(); - } - } - - private int getMaxEscalatorRowCapacity() { - final int maxEscalatorRowCapacity = (int) Math - .ceil(getHeightOfSection() / getDefaultRowHeight()) + 1; - - /* - * maxEscalatorRowCapacity can become negative if the headers and - * footers start to overlap. This is a crazy situation, but Vaadin - * blinks the components a lot, so it's feasible. - */ - return Math.max(0, maxEscalatorRowCapacity); - } - - @Override - protected void paintRemoveRows(final int index, final int numberOfRows) { - if (numberOfRows == 0) { - return; - } - - final Range viewportRange = getVisibleRowRange(); - final Range removedRowsRange = Range - .withLength(index, numberOfRows); - - /* - * Removing spacers as the very first step will correct the - * scrollbars and row offsets right away. - * - * TODO: actually, it kinda sounds like a Grid feature that a spacer - * would be associated with a particular row. Maybe it would be - * better to have a spacer separate from rows, and simply collapse - * them if they happen to end up on top of each other. This would - * probably make supporting the -1 row pretty easy, too. - */ - spacerContainer.paintRemoveSpacers(removedRowsRange); - - final Range[] partitions = removedRowsRange - .partitionWith(viewportRange); - final Range removedAbove = partitions[0]; - final Range removedLogicalInside = partitions[1]; - final Range removedVisualInside = convertToVisual(removedLogicalInside); - - /* - * TODO: extract the following if-block to a separate method. I'll - * leave this be inlined for now, to make linediff-based code - * reviewing easier. Probably will be moved in the following patch - * set. - */ - - /* - * Adjust scroll position in one of two scenarios: - * - * 1) Rows were removed above. Then we just need to adjust the - * scrollbar by the height of the removed rows. - * - * 2) There are no logical rows above, and at least the first (if - * not more) visual row is removed. Then we need to snap the scroll - * position to the first visible row (i.e. reset scroll position to - * absolute 0) - * - * The logic is optimized in such a way that the - * moveViewportAndContent is called only once, to avoid extra - * reflows, and thus the code might seem a bit obscure. - */ - final boolean firstVisualRowIsRemoved = !removedVisualInside - .isEmpty() && removedVisualInside.getStart() == 0; - - if (!removedAbove.isEmpty() || firstVisualRowIsRemoved) { - final double yDelta = removedAbove.length() - * getDefaultRowHeight(); - final double firstLogicalRowHeight = getDefaultRowHeight(); - final boolean removalScrollsToShowFirstLogicalRow = verticalScrollbar - .getScrollPos() - yDelta < firstLogicalRowHeight; - - if (removedVisualInside.isEmpty() - && (!removalScrollsToShowFirstLogicalRow || !firstVisualRowIsRemoved)) { - /* - * rows were removed from above the viewport, so all we need - * to do is to adjust the scroll position to account for the - * removed rows - */ - moveViewportAndContent(-yDelta); - } else if (removalScrollsToShowFirstLogicalRow) { - /* - * It seems like we've removed all rows from above, and also - * into the current viewport. This means we'll need to even - * out the scroll position to exactly 0 (i.e. adjust by the - * current negative scrolltop, presto!), so that it isn't - * aligned funnily - */ - moveViewportAndContent(-verticalScrollbar.getScrollPos()); - } - } - - // ranges evaluated, let's do things. - if (!removedVisualInside.isEmpty()) { - int escalatorRowCount = body.getDomRowCount(); - - /* - * remember: the rows have already been subtracted from the row - * count at this point - */ - int rowsLeft = getRowCount(); - if (rowsLeft < escalatorRowCount) { - int escalatorRowsToRemove = escalatorRowCount - rowsLeft; - for (int i = 0; i < escalatorRowsToRemove; i++) { - final TableRowElement tr = visualRowOrder - .remove(removedVisualInside.getStart()); - - paintRemoveRow(tr, index); - removeRowPosition(tr); - } - escalatorRowCount -= escalatorRowsToRemove; - - /* - * Because we're removing escalator rows, we don't have - * anything to scroll by. Let's make sure the viewport is - * scrolled to top, to render any rows possibly left above. - */ - body.setBodyScrollPosition(tBodyScrollLeft, 0); - - /* - * We might have removed some rows from the middle, so let's - * make sure we're not left with any holes. Also remember: - * visualIndex == logicalIndex applies now. - */ - final int dirtyRowsStart = removedLogicalInside.getStart(); - double y = getRowTop(dirtyRowsStart); - for (int i = dirtyRowsStart; i < escalatorRowCount; i++) { - final TableRowElement tr = visualRowOrder.get(i); - setRowPosition(tr, 0, y); - y += getDefaultRowHeight(); - y += spacerContainer.getSpacerHeight(i); - } - - /* - * this is how many rows appeared into the viewport from - * below - */ - final int rowsToUpdateDataOn = numberOfRows - - escalatorRowsToRemove; - final int start = Math.max(0, escalatorRowCount - - rowsToUpdateDataOn); - final int end = escalatorRowCount; - for (int i = start; i < end; i++) { - final TableRowElement tr = visualRowOrder.get(i); - refreshRow(tr, i); - } - } - - else { - // No escalator rows need to be removed. - - /* - * Two things (or a combination thereof) can happen: - * - * 1) We're scrolled to the bottom, the last rows are - * removed. SOLUTION: moveAndUpdateEscalatorRows the - * bottommost rows, and place them at the top to be - * refreshed. - * - * 2) We're scrolled somewhere in the middle, arbitrary rows - * are removed. SOLUTION: moveAndUpdateEscalatorRows the - * removed rows, and place them at the bottom to be - * refreshed. - * - * Since a combination can also happen, we need to handle - * this in a smart way, all while avoiding - * double-refreshing. - */ - - final double contentBottom = getRowCount() - * getDefaultRowHeight(); - final double viewportBottom = tBodyScrollTop - + getHeightOfSection(); - if (viewportBottom <= contentBottom) { - /* - * We're in the middle of the row container, everything - * is added to the bottom - */ - paintRemoveRowsAtMiddle(removedLogicalInside, - removedVisualInside, 0); - } - - else if (removedVisualInside.contains(0) - && numberOfRows >= visualRowOrder.size()) { - /* - * We're removing so many rows that the viewport is - * pushed up more than a screenful. This means we can - * simply scroll up and everything will work without a - * sweat. - */ - - double left = horizontalScrollbar.getScrollPos(); - double top = contentBottom - visualRowOrder.size() - * getDefaultRowHeight(); - setBodyScrollPosition(left, top); - - Range allEscalatorRows = Range.withLength(0, - visualRowOrder.size()); - int logicalTargetIndex = getRowCount() - - allEscalatorRows.length(); - moveAndUpdateEscalatorRows(allEscalatorRows, 0, - logicalTargetIndex); - - /* - * Scrolling the body to the correct location will be - * fixed automatically. Because the amount of rows is - * decreased, the viewport is pushed up as the scrollbar - * shrinks. So no need to do anything there. - * - * TODO [[optimize]]: This might lead to a double body - * refresh. Needs investigation. - */ - } - - else if (contentBottom - + (numberOfRows * getDefaultRowHeight()) - - viewportBottom < getDefaultRowHeight()) { - /* - * We're at the end of the row container, everything is - * added to the top. - */ - - /* - * FIXME [[spacer]]: above if-clause is coded to only - * work with default row heights - will not work with - * variable row heights - */ - - paintRemoveRowsAtBottom(removedLogicalInside, - removedVisualInside); - updateTopRowLogicalIndex(-removedLogicalInside.length()); - } - - else { - /* - * We're in a combination, where we need to both scroll - * up AND show new rows at the bottom. - * - * Example: Scrolled down to show the second to last - * row. Remove two. Viewport scrolls up, revealing the - * row above row. The last element collapses up and into - * view. - * - * Reminder: this use case handles only the case when - * there are enough escalator rows to still render a - * full view. I.e. all escalator rows will _always_ be - * populated - */ - /*- - * 1 1 |1| <- newly rendered - * |2| |2| |2| - * |3| ==> |*| ==> |5| <- newly rendered - * |4| |*| - * 5 5 - * - * 1 1 |1| <- newly rendered - * |2| |*| |4| - * |3| ==> |*| ==> |5| <- newly rendered - * |4| |4| - * 5 5 - */ - - /* - * STEP 1: - * - * reorganize deprecated escalator rows to bottom, but - * don't re-render anything yet - */ - /*- - * 1 1 1 - * |2| |*| |4| - * |3| ==> |*| ==> |*| - * |4| |4| |*| - * 5 5 5 - */ - double newTop = getRowTop(visualRowOrder - .get(removedVisualInside.getStart())); - for (int i = 0; i < removedVisualInside.length(); i++) { - final TableRowElement tr = visualRowOrder - .remove(removedVisualInside.getStart()); - visualRowOrder.addLast(tr); - } - - for (int i = removedVisualInside.getStart(); i < escalatorRowCount; i++) { - final TableRowElement tr = visualRowOrder.get(i); - setRowPosition(tr, 0, (int) newTop); - newTop += getDefaultRowHeight(); - newTop += spacerContainer.getSpacerHeight(i - + removedLogicalInside.getStart()); - } - - /* - * STEP 2: - * - * manually scroll - */ - /*- - * 1 |1| <-- newly rendered (by scrolling) - * |4| |4| - * |*| ==> |*| - * |*| - * 5 5 - */ - final double newScrollTop = contentBottom - - getHeightOfSection(); - setScrollTop(newScrollTop); - /* - * Manually call the scroll handler, so we get immediate - * effects in the escalator. - */ - scroller.onScroll(); - - /* - * Move the bottommost (n+1:th) escalator row to top, - * because scrolling up doesn't handle that for us - * automatically - */ - moveAndUpdateEscalatorRows( - Range.withOnly(escalatorRowCount - 1), - 0, - getLogicalRowIndex(visualRowOrder.getFirst()) - 1); - updateTopRowLogicalIndex(-1); - - /* - * STEP 3: - * - * update remaining escalator rows - */ - /*- - * |1| |1| - * |4| ==> |4| - * |*| |5| <-- newly rendered - * - * 5 - */ - - final int rowsScrolled = (int) (Math - .ceil((viewportBottom - contentBottom) - / getDefaultRowHeight())); - final int start = escalatorRowCount - - (removedVisualInside.length() - rowsScrolled); - final Range visualRefreshRange = Range.between(start, - escalatorRowCount); - final int logicalTargetIndex = getLogicalRowIndex(visualRowOrder - .getFirst()) + start; - // in-place move simply re-renders the rows. - moveAndUpdateEscalatorRows(visualRefreshRange, start, - logicalTargetIndex); - } - } - - fireRowVisibilityChangeEvent(); - sortDomElements(); - } - - updateTopRowLogicalIndex(-removedAbove.length()); - - /* - * this needs to be done after the escalator has been shrunk down, - * or it won't work correctly (due to setScrollTop invocation) - */ - scroller.recalculateScrollbarsForVirtualViewport(); - } - - private void paintRemoveRowsAtMiddle(final Range removedLogicalInside, - final Range removedVisualInside, final int logicalOffset) { - /*- - * : : : - * |2| |2| |2| - * |3| ==> |*| ==> |4| - * |4| |4| |6| <- newly rendered - * : : : - */ - - final int escalatorRowCount = visualRowOrder.size(); - - final int logicalTargetIndex = getLogicalRowIndex(visualRowOrder - .getLast()) - - (removedVisualInside.length() - 1) - + logicalOffset; - moveAndUpdateEscalatorRows(removedVisualInside, escalatorRowCount, - logicalTargetIndex); - - // move the surrounding rows to their correct places. - final ListIterator iterator = visualRowOrder - .listIterator(removedVisualInside.getStart()); - - double rowTop = getRowTop(removedLogicalInside.getStart() - + logicalOffset); - for (int i = removedVisualInside.getStart(); i < escalatorRowCount - - removedVisualInside.length(); i++) { - final TableRowElement tr = iterator.next(); - setRowPosition(tr, 0, rowTop); - rowTop += getDefaultRowHeight(); - rowTop += spacerContainer.getSpacerHeight(i - + removedLogicalInside.getStart()); - } - } - - private void paintRemoveRowsAtBottom(final Range removedLogicalInside, - final Range removedVisualInside) { - /*- - * : - * : : |4| <- newly rendered - * |5| |5| |5| - * |6| ==> |*| ==> |7| - * |7| |7| - */ - - final int logicalTargetIndex = getLogicalRowIndex(visualRowOrder - .getFirst()) - removedVisualInside.length(); - moveAndUpdateEscalatorRows(removedVisualInside, 0, - logicalTargetIndex); - - // move the surrounding rows to their correct places. - int firstUpdatedIndex = removedVisualInside.getEnd(); - final ListIterator iterator = visualRowOrder - .listIterator(firstUpdatedIndex); - - double rowTop = getRowTop(removedLogicalInside.getStart()); - int i = 0; - while (iterator.hasNext()) { - final TableRowElement tr = iterator.next(); - setRowPosition(tr, 0, rowTop); - rowTop += getDefaultRowHeight(); - rowTop += spacerContainer.getSpacerHeight(firstUpdatedIndex - + i++); - } - } - - @Override - protected int getLogicalRowIndex(final TableRowElement tr) { - assert tr.getParentNode() == root : "The given element isn't a row element in the body"; - int internalIndex = visualRowOrder.indexOf(tr); - return getTopRowLogicalIndex() + internalIndex; - } - - @Override - protected void recalculateSectionHeight() { - // NOOP for body, since it doesn't make any sense. - } - - /** - * Adjusts the row index and number to be relevant for the current - * virtual viewport. - *

- * It converts a logical range of rows index to the matching visual - * range, truncating the resulting range with the viewport. - *

- *

    - *
  • Escalator contains logical rows 0..100 - *
  • Current viewport showing logical rows 20..29 - *
  • convertToVisual([20..29]) → [0..9] - *
  • convertToVisual([15..24]) → [0..4] - *
  • convertToVisual([25..29]) → [5..9] - *
  • convertToVisual([26..39]) → [6..9] - *
  • convertToVisual([0..5]) → [0..-1] (empty) - *
  • convertToVisual([35..1]) → [0..-1] (empty) - *
  • convertToVisual([0..100]) → [0..9] - *
- * - * @return a logical range converted to a visual range, truncated to the - * current viewport. The first visual row has the index 0. - */ - private Range convertToVisual(final Range logicalRange) { - - if (logicalRange.isEmpty()) { - return logicalRange; - } else if (visualRowOrder.isEmpty()) { - // empty range - return Range.withLength(0, 0); - } - - /* - * TODO [[spacer]]: these assumptions will be totally broken with - * spacers. - */ - final int maxEscalatorRows = getMaxEscalatorRowCapacity(); - final int currentTopRowIndex = getLogicalRowIndex(visualRowOrder - .getFirst()); - - final Range[] partitions = logicalRange.partitionWith(Range - .withLength(currentTopRowIndex, maxEscalatorRows)); - final Range insideRange = partitions[1]; - return insideRange.offsetBy(-currentTopRowIndex); - } - - @Override - protected String getCellElementTagName() { - return "td"; - } - - @Override - protected double getHeightOfSection() { - final int tableHeight = tableWrapper.getOffsetHeight(); - final double footerHeight = footer.getHeightOfSection(); - final double headerHeight = header.getHeightOfSection(); - - double heightOfSection = tableHeight - footerHeight - headerHeight; - return Math.max(0, heightOfSection); - } - - @Override - protected void refreshCells(Range logicalRowRange, Range colRange) { - Profiler.enter("Escalator.BodyRowContainer.refreshRows"); - - final Range visualRange = convertToVisual(logicalRowRange); - - if (!visualRange.isEmpty()) { - final int firstLogicalRowIndex = getLogicalRowIndex(visualRowOrder - .getFirst()); - for (int rowNumber = visualRange.getStart(); rowNumber < visualRange - .getEnd(); rowNumber++) { - refreshRow(visualRowOrder.get(rowNumber), - firstLogicalRowIndex + rowNumber, colRange); - } - } - - Profiler.leave("Escalator.BodyRowContainer.refreshRows"); - } - - @Override - protected TableRowElement getTrByVisualIndex(final int index) - throws IndexOutOfBoundsException { - if (index >= 0 && index < visualRowOrder.size()) { - return visualRowOrder.get(index); - } else { - throw new IndexOutOfBoundsException("No such visual index: " - + index); - } - } - - @Override - public TableRowElement getRowElement(int index) { - if (index < 0 || index >= getRowCount()) { - throw new IndexOutOfBoundsException("No such logical index: " - + index); - } - int visualIndex = index - - getLogicalRowIndex(visualRowOrder.getFirst()); - if (visualIndex >= 0 && visualIndex < visualRowOrder.size()) { - return super.getRowElement(visualIndex); - } else { - throw new IllegalStateException("Row with logical index " - + index + " is currently not available in the DOM"); - } - } - - private void setBodyScrollPosition(final double scrollLeft, - final double scrollTop) { - tBodyScrollLeft = scrollLeft; - tBodyScrollTop = scrollTop; - position.set(bodyElem, -tBodyScrollLeft, -tBodyScrollTop); - position.set(spacerDecoContainer, 0, -tBodyScrollTop); - } - - /** - * Make sure that there is a correct amount of escalator rows: Add more - * if needed, or remove any superfluous ones. - *

- * This method should be called when e.g. the height of the Escalator - * changes. - *

- * Note: This method will make sure that the escalator rows are - * placed in the proper places. By default new rows are added below, but - * if the content is scrolled down, the rows are populated on top - * instead. - */ - public void verifyEscalatorCount() { - /* - * This method indeed has a smell very similar to paintRemoveRows - * and paintInsertRows. - * - * Unfortunately, those the code can't trivially be shared, since - * there are some slight differences in the respective - * responsibilities. The "paint" methods fake the addition and - * removal of rows, and make sure to either push existing data out - * of view, or draw new data into view. Only in some special cases - * will the DOM element count change. - * - * This method, however, has the explicit responsibility to verify - * that when "something" happens, we still have the correct amount - * of escalator rows in the DOM, and if not, we make sure to modify - * that count. Only in some special cases do we need to take into - * account other things than simply modifying the DOM element count. - */ - - Profiler.enter("Escalator.BodyRowContainer.verifyEscalatorCount"); - - if (!isAttached()) { - return; - } - - final int maxEscalatorRows = getMaxEscalatorRowCapacity(); - final int neededEscalatorRows = Math.min(maxEscalatorRows, - body.getRowCount()); - final int neededEscalatorRowsDiff = neededEscalatorRows - - visualRowOrder.size(); - - if (neededEscalatorRowsDiff > 0) { - // needs more - - /* - * This is a workaround for the issue where we might be scrolled - * to the bottom, and the widget expands beyond the content - * range - */ - - final int index = visualRowOrder.size(); - final int nextLastLogicalIndex; - if (!visualRowOrder.isEmpty()) { - nextLastLogicalIndex = getLogicalRowIndex(visualRowOrder - .getLast()) + 1; - } else { - nextLastLogicalIndex = 0; - } - - final boolean contentWillFit = nextLastLogicalIndex < getRowCount() - - neededEscalatorRowsDiff; - if (contentWillFit) { - final List addedRows = fillAndPopulateEscalatorRowsIfNeeded( - index, neededEscalatorRowsDiff); - - /* - * Since fillAndPopulateEscalatorRowsIfNeeded operates on - * the assumption that index == visual index == logical - * index, we thank for the added escalator rows, but since - * they're painted in the wrong CSS position, we need to - * move them to their actual locations. - * - * Note: this is the second (see body.paintInsertRows) - * occasion where fillAndPopulateEscalatorRowsIfNeeded would - * behave "more correctly" if it only would add escalator - * rows to the DOM and appropriate bookkeping, and not - * actually populate them :/ - */ - moveAndUpdateEscalatorRows( - Range.withLength(index, addedRows.size()), index, - nextLastLogicalIndex); - } else { - /* - * TODO [[optimize]] - * - * We're scrolled so far down that all rows can't be simply - * appended at the end, since we might start displaying - * escalator rows that don't exist. To avoid the mess that - * is body.paintRemoveRows, this is a dirty hack that dumbs - * the problem down to a more basic and already-solved - * problem: - * - * 1) scroll all the way up 2) add the missing escalator - * rows 3) scroll back to the original position. - * - * Letting the browser scroll back to our original position - * will automatically solve any possible overflow problems, - * since the browser will not allow us to scroll beyond the - * actual content. - */ - - final double oldScrollTop = getScrollTop(); - setScrollTop(0); - scroller.onScroll(); - fillAndPopulateEscalatorRowsIfNeeded(index, - neededEscalatorRowsDiff); - setScrollTop(oldScrollTop); - scroller.onScroll(); - } - } - - else if (neededEscalatorRowsDiff < 0) { - // needs less - - final ListIterator iter = visualRowOrder - .listIterator(visualRowOrder.size()); - for (int i = 0; i < -neededEscalatorRowsDiff; i++) { - final Element last = iter.previous(); - last.removeFromParent(); - iter.remove(); - } - - /* - * If we were scrolled to the bottom so that we didn't have an - * extra escalator row at the bottom, we'll probably end up with - * blank space at the bottom of the escalator, and one extra row - * above the header. - * - * Experimentation idea #1: calculate "scrollbottom" vs content - * bottom and remove one row from top, rest from bottom. This - * FAILED, since setHeight has already happened, thus we never - * will detect ourselves having been scrolled all the way to the - * bottom. - */ - - if (!visualRowOrder.isEmpty()) { - final double firstRowTop = getRowTop(visualRowOrder - .getFirst()); - final double firstRowMinTop = tBodyScrollTop - - getDefaultRowHeight(); - if (firstRowTop < firstRowMinTop) { - final int newLogicalIndex = getLogicalRowIndex(visualRowOrder - .getLast()) + 1; - moveAndUpdateEscalatorRows(Range.withOnly(0), - visualRowOrder.size(), newLogicalIndex); - } - } - } - - if (neededEscalatorRowsDiff != 0) { - fireRowVisibilityChangeEvent(); - } - - Profiler.leave("Escalator.BodyRowContainer.verifyEscalatorCount"); - } - - @Override - protected void reapplyDefaultRowHeights() { - if (visualRowOrder.isEmpty()) { - return; - } - - Profiler.enter("Escalator.BodyRowContainer.reapplyDefaultRowHeights"); - - /* step 1: resize and reposition rows */ - for (int i = 0; i < visualRowOrder.size(); i++) { - TableRowElement tr = visualRowOrder.get(i); - reapplyRowHeight(tr, getDefaultRowHeight()); - - final int logicalIndex = getTopRowLogicalIndex() + i; - setRowPosition(tr, 0, logicalIndex * getDefaultRowHeight()); - } - - /* - * step 2: move scrollbar so that it corresponds to its previous - * place - */ - - /* - * This ratio needs to be calculated with the scrollsize (not max - * scroll position) in order to align the top row with the new - * scroll position. - */ - double scrollRatio = verticalScrollbar.getScrollPos() - / verticalScrollbar.getScrollSize(); - scroller.recalculateScrollbarsForVirtualViewport(); - verticalScrollbar.setScrollPos((int) (getDefaultRowHeight() - * getRowCount() * scrollRatio)); - setBodyScrollPosition(horizontalScrollbar.getScrollPos(), - verticalScrollbar.getScrollPos()); - scroller.onScroll(); - - /* step 3: make sure we have the correct amount of escalator rows. */ - verifyEscalatorCount(); - - int logicalLogical = (int) (getRowTop(visualRowOrder.getFirst()) / getDefaultRowHeight()); - setTopRowLogicalIndex(logicalLogical); - - Profiler.leave("Escalator.BodyRowContainer.reapplyDefaultRowHeights"); - } - - /** - * Sorts the rows in the DOM to correspond to the visual order. - * - * @see #visualRowOrder - */ - private void sortDomElements() { - final String profilingName = "Escalator.BodyRowContainer.sortDomElements"; - Profiler.enter(profilingName); - - /* - * Focus is lost from an element if that DOM element is (or any of - * its parents are) removed from the document. Therefore, we sort - * everything around that row instead. - */ - final TableRowElement focusedRow = getRowWithFocus(); - - if (focusedRow != null) { - assert focusedRow.getParentElement() == root : "Trying to sort around a row that doesn't exist in body"; - assert visualRowOrder.contains(focusedRow) - || body.spacerContainer.isSpacer(focusedRow) : "Trying to sort around a row that doesn't exist in visualRowOrder or is not a spacer."; - } - - /* - * Two cases handled simultaneously: - * - * 1) No focus on rows. We iterate visualRowOrder backwards, and - * take the respective element in the DOM, and place it as the first - * child in the body element. Then we take the next-to-last from - * visualRowOrder, and put that first, pushing the previous row as - * the second child. And so on... - * - * 2) Focus on some row within Escalator body. Again, we iterate - * visualRowOrder backwards. This time, we use the focused row as a - * pivot: Instead of placing rows from the bottom of visualRowOrder - * and placing it first, we place it underneath the focused row. - * Once we hit the focused row, we don't move it (to not reset - * focus) but change sorting mode. After that, we place all rows as - * the first child. - */ - - List orderedBodyRows = new ArrayList( - visualRowOrder); - Map spacers = body.spacerContainer - .getSpacers(); - - /* - * Start at -1 to include a spacer that is rendered above the - * viewport, but its parent row is still not shown - */ - for (int i = -1; i < visualRowOrder.size(); i++) { - SpacerContainer.SpacerImpl spacer = spacers.remove(Integer - .valueOf(getTopRowLogicalIndex() + i)); - - if (spacer != null) { - orderedBodyRows.add(i + 1, spacer.getRootElement()); - spacer.show(); - } - } - /* - * At this point, invisible spacers aren't reordered, so their - * position in the DOM will remain undefined. - */ - - // If a spacer was not reordered, it means that it's out of view. - for (SpacerContainer.SpacerImpl unmovedSpacer : spacers.values()) { - unmovedSpacer.hide(); - } - - /* - * If we have a focused row, start in the mode where we put - * everything underneath that row. Otherwise, all rows are placed as - * first child. - */ - boolean insertFirst = (focusedRow == null); - - final ListIterator i = orderedBodyRows - .listIterator(orderedBodyRows.size()); - while (i.hasPrevious()) { - TableRowElement tr = i.previous(); - - if (tr == focusedRow) { - insertFirst = true; - } else if (insertFirst) { - root.insertFirst(tr); - } else { - root.insertAfter(tr, focusedRow); - } - } - - Profiler.leave(profilingName); - } - - /** - * Get the {@literal } row that contains (or has) focus. - * - * @return The {@literal } row that contains a focused DOM - * element, or null if focus is outside of a body - * row. - */ - private TableRowElement getRowWithFocus() { - TableRowElement rowContainingFocus = null; - - final Element focusedElement = WidgetUtil.getFocusedElement(); - - if (focusedElement != null && root.isOrHasChild(focusedElement)) { - Element e = focusedElement; - - while (e != null && e != root) { - /* - * You never know if there's several tables embedded in a - * cell... We'll take the deepest one. - */ - if (TableRowElement.is(e)) { - rowContainingFocus = TableRowElement.as(e); - } - e = e.getParentElement(); - } - } - - return rowContainingFocus; - } - - @Override - public Cell getCell(Element element) { - Cell cell = super.getCell(element); - if (cell == null) { - return null; - } - - // Convert DOM coordinates to logical coordinates for rows - TableRowElement rowElement = (TableRowElement) cell.getElement() - .getParentElement(); - return new Cell(getLogicalRowIndex(rowElement), cell.getColumn(), - cell.getElement()); - } - - @Override - public void setSpacer(int rowIndex, double height) - throws IllegalArgumentException { - spacerContainer.setSpacer(rowIndex, height); - } - - @Override - public void setSpacerUpdater(SpacerUpdater spacerUpdater) - throws IllegalArgumentException { - spacerContainer.setSpacerUpdater(spacerUpdater); - } - - @Override - public SpacerUpdater getSpacerUpdater() { - return spacerContainer.getSpacerUpdater(); - } - - /** - * Calculates the correct top position of a row at a logical - * index, regardless if there is one there or not. - *

- * A correct result requires that both {@link #getDefaultRowHeight()} is - * consistent, and the placement and height of all spacers above the - * given logical index are consistent. - * - * @param logicalIndex - * the logical index of the row for which to calculate the - * top position - * @return the position at which to place a row in {@code logicalIndex} - * @see #getRowTop(TableRowElement) - */ - private double getRowTop(int logicalIndex) { - double top = spacerContainer - .getSpacerHeightsSumUntilIndex(logicalIndex); - return top + (logicalIndex * getDefaultRowHeight()); - } - - public void shiftRowPositions(int row, double diff) { - for (TableRowElement tr : getVisibleRowsAfter(row)) { - setRowPosition(tr, 0, getRowTop(tr) + diff); - } - } - - private List getVisibleRowsAfter(int logicalRow) { - Range visibleRowLogicalRange = getVisibleRowRange(); - - boolean allRowsAreInView = logicalRow < visibleRowLogicalRange - .getStart(); - boolean noRowsAreInView = logicalRow >= visibleRowLogicalRange - .getEnd() - 1; - - if (allRowsAreInView) { - return Collections.unmodifiableList(visualRowOrder); - } else if (noRowsAreInView) { - return Collections.emptyList(); - } else { - int fromIndex = (logicalRow - visibleRowLogicalRange.getStart()) + 1; - int toIndex = visibleRowLogicalRange.length(); - List sublist = visualRowOrder.subList( - fromIndex, toIndex); - return Collections.unmodifiableList(sublist); - } - } - - @Override - public int getDomRowCount() { - return root.getChildCount() - - spacerContainer.getSpacersInDom().size(); - } - - @Override - protected boolean rowCanBeFrozen(TableRowElement tr) { - return visualRowOrder.contains(tr); - } - - void reapplySpacerWidths() { - spacerContainer.reapplySpacerWidths(); - } - - void scrollToSpacer(int spacerIndex, ScrollDestination destination, - int padding) { - spacerContainer.scrollToSpacer(spacerIndex, destination, padding); - } - } - - private class ColumnConfigurationImpl implements ColumnConfiguration { - public class Column { - public static final double DEFAULT_COLUMN_WIDTH_PX = 100; - - private double definedWidth = -1; - private double calculatedWidth = DEFAULT_COLUMN_WIDTH_PX; - private boolean measuringRequested = false; - - public void setWidth(double px) { - definedWidth = px; - - if (px < 0) { - if (isAttached()) { - calculateWidth(); - } else { - /* - * the column's width is calculated at Escalator.onLoad - * via measureAndSetWidthIfNeeded! - */ - measuringRequested = true; - } - } else { - calculatedWidth = px; - } - } - - public double getDefinedWidth() { - return definedWidth; - } - - /** - * Returns the actual width in the DOM. - * - * @return the width in pixels in the DOM. Returns -1 if the column - * needs measuring, but has not been yet measured - */ - public double getCalculatedWidth() { - /* - * This might return an untrue value (e.g. during init/onload), - * since we haven't had a proper chance to actually calculate - * widths yet. - * - * This is fixed during Escalator.onLoad, by the call to - * "measureAndSetWidthIfNeeded", which fixes "everything". - */ - if (!measuringRequested) { - return calculatedWidth; - } else { - return -1; - } - } - - /** - * Checks if the column needs measuring, and then measures it. - *

- * Called by {@link Escalator#onLoad()}. - */ - public boolean measureAndSetWidthIfNeeded() { - assert isAttached() : "Column.measureAndSetWidthIfNeeded() was called even though Escalator was not attached!"; - - if (measuringRequested) { - measuringRequested = false; - setWidth(definedWidth); - return true; - } - return false; - } - - private void calculateWidth() { - calculatedWidth = getMaxCellWidth(columns.indexOf(this)); - } - } - - private final List columns = new ArrayList(); - private int frozenColumns = 0; - - /* - * TODO: this is a bit of a duplicate functionality with the - * Column.calculatedWidth caching. Probably should use one or the other, - * not both - */ - /** - * A cached array of all the calculated column widths. - * - * @see #getCalculatedColumnWidths() - */ - private double[] widthsArray = null; - - /** - * {@inheritDoc} - *

- * Implementation detail: This method does no DOM modifications - * (i.e. is very cheap to call) if there are no rows in the DOM when - * this method is called. - * - * @see #hasSomethingInDom() - */ - @Override - public void removeColumns(final int index, final int numberOfColumns) { - // Validate - assertArgumentsAreValidAndWithinRange(index, numberOfColumns); - - // Move the horizontal scrollbar to the left, if removed columns are - // to the left of the viewport - removeColumnsAdjustScrollbar(index, numberOfColumns); - - // Remove from DOM - header.paintRemoveColumns(index, numberOfColumns); - body.paintRemoveColumns(index, numberOfColumns); - footer.paintRemoveColumns(index, numberOfColumns); - - // Remove from bookkeeping - flyweightRow.removeCells(index, numberOfColumns); - columns.subList(index, index + numberOfColumns).clear(); - - // Adjust frozen columns - if (index < getFrozenColumnCount()) { - if (index + numberOfColumns < frozenColumns) { - /* - * Last removed column was frozen, meaning that all removed - * columns were frozen. Just decrement the number of frozen - * columns accordingly. - */ - frozenColumns -= numberOfColumns; - } else { - /* - * If last removed column was not frozen, we have removed - * columns beyond the frozen range, so all remaining frozen - * columns are to the left of the removed columns. - */ - frozenColumns = index; - } - } - - scroller.recalculateScrollbarsForVirtualViewport(); - body.verifyEscalatorCount(); - - if (getColumnConfiguration().getColumnCount() > 0) { - reapplyRowWidths(header); - reapplyRowWidths(body); - reapplyRowWidths(footer); - } - - /* - * Colspans make any kind of automatic clever content re-rendering - * impossible: As soon as anything has colspans, removing one might - * reveal further colspans, modifying the DOM structure once again, - * ending in a cascade of updates. Because we don't know how the - * data is updated. - * - * So, instead, we don't do anything. The client code is responsible - * for re-rendering the content (if so desired). Everything Just - * Works (TM) if colspans aren't used. - */ - } - - private void reapplyRowWidths(AbstractRowContainer container) { - if (container.getRowCount() > 0) { - container.reapplyRowWidths(); - } - } - - private void removeColumnsAdjustScrollbar(int index, int numberOfColumns) { - if (horizontalScrollbar.getOffsetSize() >= horizontalScrollbar - .getScrollSize()) { - return; - } - - double leftPosOfFirstColumnToRemove = getCalculatedColumnsWidth(Range - .between(0, index)); - double widthOfColumnsToRemove = getCalculatedColumnsWidth(Range - .withLength(index, numberOfColumns)); - - double scrollLeft = horizontalScrollbar.getScrollPos(); - - if (scrollLeft <= leftPosOfFirstColumnToRemove) { - /* - * viewport is scrolled to the left of the first removed column, - * so there's no need to adjust anything - */ - return; - } - - double adjustedScrollLeft = Math.max(leftPosOfFirstColumnToRemove, - scrollLeft - widthOfColumnsToRemove); - horizontalScrollbar.setScrollPos(adjustedScrollLeft); - } - - /** - * Calculate the width of a row, as the sum of columns' widths. - * - * @return the width of a row, in pixels - */ - public double calculateRowWidth() { - return getCalculatedColumnsWidth(Range.between(0, getColumnCount())); - } - - private void assertArgumentsAreValidAndWithinRange(final int index, - final int numberOfColumns) { - if (numberOfColumns < 1) { - throw new IllegalArgumentException( - "Number of columns can't be less than 1 (was " - + numberOfColumns + ")"); - } - - if (index < 0 || index + numberOfColumns > getColumnCount()) { - throw new IndexOutOfBoundsException("The given " - + "column range (" + index + ".." - + (index + numberOfColumns) - + ") was outside of the current " - + "number of columns (" + getColumnCount() + ")"); - } - } - - /** - * {@inheritDoc} - *

- * Implementation detail: This method does no DOM modifications - * (i.e. is very cheap to call) if there is no data for rows when this - * method is called. - * - * @see #hasColumnAndRowData() - */ - @Override - public void insertColumns(final int index, final int numberOfColumns) { - // Validate - if (index < 0 || index > getColumnCount()) { - throw new IndexOutOfBoundsException("The given index(" + index - + ") was outside of the current number of columns (0.." - + getColumnCount() + ")"); - } - - if (numberOfColumns < 1) { - throw new IllegalArgumentException( - "Number of columns must be 1 or greater (was " - + numberOfColumns); - } - - // Add to bookkeeping - flyweightRow.addCells(index, numberOfColumns); - for (int i = 0; i < numberOfColumns; i++) { - columns.add(index, new Column()); - } - - // Adjust frozen columns - boolean frozen = index < frozenColumns; - if (frozen) { - frozenColumns += numberOfColumns; - } - - // this needs to be before the scrollbar adjustment. - boolean scrollbarWasNeeded = horizontalScrollbar.getOffsetSize() < horizontalScrollbar - .getScrollSize(); - scroller.recalculateScrollbarsForVirtualViewport(); - boolean scrollbarIsNowNeeded = horizontalScrollbar.getOffsetSize() < horizontalScrollbar - .getScrollSize(); - if (!scrollbarWasNeeded && scrollbarIsNowNeeded) { - body.verifyEscalatorCount(); - } - - // Add to DOM - header.paintInsertColumns(index, numberOfColumns, frozen); - body.paintInsertColumns(index, numberOfColumns, frozen); - footer.paintInsertColumns(index, numberOfColumns, frozen); - - // fix initial width - if (header.getRowCount() > 0 || body.getRowCount() > 0 - || footer.getRowCount() > 0) { - - Map colWidths = new HashMap(); - Double width = Double.valueOf(Column.DEFAULT_COLUMN_WIDTH_PX); - for (int i = index; i < index + numberOfColumns; i++) { - Integer col = Integer.valueOf(i); - colWidths.put(col, width); - } - getColumnConfiguration().setColumnWidths(colWidths); - } - - // Adjust scrollbar - double pixelsToInsertedColumn = columnConfiguration - .getCalculatedColumnsWidth(Range.withLength(0, index)); - final boolean columnsWereAddedToTheLeftOfViewport = scroller.lastScrollLeft > pixelsToInsertedColumn; - - if (columnsWereAddedToTheLeftOfViewport) { - double insertedColumnsWidth = columnConfiguration - .getCalculatedColumnsWidth(Range.withLength(index, - numberOfColumns)); - horizontalScrollbar.setScrollPos(scroller.lastScrollLeft - + insertedColumnsWidth); - } - - /* - * Colspans make any kind of automatic clever content re-rendering - * impossible: As soon as anything has colspans, adding one might - * affect surrounding colspans, modifying the DOM structure once - * again, ending in a cascade of updates. Because we don't know how - * the data is updated. - * - * So, instead, we don't do anything. The client code is responsible - * for re-rendering the content (if so desired). Everything Just - * Works (TM) if colspans aren't used. - */ - } - - @Override - public int getColumnCount() { - return columns.size(); - } - - @Override - public void setFrozenColumnCount(int count) - throws IllegalArgumentException { - if (count < 0 || count > getColumnCount()) { - throw new IllegalArgumentException( - "count must be between 0 and the current number of columns (" - + getColumnCount() + ")"); - } - int oldCount = frozenColumns; - if (count == oldCount) { - return; - } - - frozenColumns = count; - - if (hasSomethingInDom()) { - // Are we freezing or unfreezing? - boolean frozen = count > oldCount; - - int firstAffectedCol; - int firstUnaffectedCol; - - if (frozen) { - firstAffectedCol = oldCount; - firstUnaffectedCol = count; - } else { - firstAffectedCol = count; - firstUnaffectedCol = oldCount; - } - - if (oldCount > 0) { - header.setColumnLastFrozen(oldCount - 1, false); - body.setColumnLastFrozen(oldCount - 1, false); - footer.setColumnLastFrozen(oldCount - 1, false); - } - if (count > 0) { - header.setColumnLastFrozen(count - 1, true); - body.setColumnLastFrozen(count - 1, true); - footer.setColumnLastFrozen(count - 1, true); - } - - for (int col = firstAffectedCol; col < firstUnaffectedCol; col++) { - header.setColumnFrozen(col, frozen); - body.setColumnFrozen(col, frozen); - footer.setColumnFrozen(col, frozen); - } - } - - scroller.recalculateScrollbarsForVirtualViewport(); - } - - @Override - public int getFrozenColumnCount() { - return frozenColumns; - } - - @Override - public void setColumnWidth(int index, double px) - throws IllegalArgumentException { - setColumnWidths(Collections.singletonMap(Integer.valueOf(index), - Double.valueOf(px))); - } - - @Override - public void setColumnWidths(Map indexWidthMap) - throws IllegalArgumentException { - - if (indexWidthMap == null) { - throw new IllegalArgumentException("indexWidthMap was null"); - } - - if (indexWidthMap.isEmpty()) { - return; - } - - for (Entry entry : indexWidthMap.entrySet()) { - int index = entry.getKey().intValue(); - double width = entry.getValue().doubleValue(); - - checkValidColumnIndex(index); - - // Not all browsers will accept any fractional size.. - width = WidgetUtil.roundSizeDown(width); - columns.get(index).setWidth(width); - - } - - widthsArray = null; - header.reapplyColumnWidths(); - body.reapplyColumnWidths(); - footer.reapplyColumnWidths(); - - recalculateElementSizes(); - } - - private void checkValidColumnIndex(int index) - throws IllegalArgumentException { - if (!Range.withLength(0, getColumnCount()).contains(index)) { - throw new IllegalArgumentException("The given column index (" - + index + ") does not exist"); - } - } - - @Override - public double getColumnWidth(int index) throws IllegalArgumentException { - checkValidColumnIndex(index); - return columns.get(index).getDefinedWidth(); - } - - @Override - public double getColumnWidthActual(int index) { - return columns.get(index).getCalculatedWidth(); - } - - private double getMaxCellWidth(int colIndex) - throws IllegalArgumentException { - double headerWidth = header.measureMinCellWidth(colIndex, true); - double bodyWidth = body.measureMinCellWidth(colIndex, true); - double footerWidth = footer.measureMinCellWidth(colIndex, true); - - double maxWidth = Math.max(headerWidth, - Math.max(bodyWidth, footerWidth)); - assert maxWidth >= 0 : "Got a negative max width for a column, which should be impossible."; - return maxWidth; - } - - private double getMinCellWidth(int colIndex) - throws IllegalArgumentException { - double headerWidth = header.measureMinCellWidth(colIndex, false); - double bodyWidth = body.measureMinCellWidth(colIndex, false); - double footerWidth = footer.measureMinCellWidth(colIndex, false); - - double minWidth = Math.max(headerWidth, - Math.max(bodyWidth, footerWidth)); - assert minWidth >= 0 : "Got a negative max width for a column, which should be impossible."; - return minWidth; - } - - /** - * Calculates the width of the columns in a given range. - * - * @param columns - * the columns to calculate - * @return the total width of the columns in the given - * columns - */ - double getCalculatedColumnsWidth(final Range columns) { - /* - * This is an assert instead of an exception, since this is an - * internal method. - */ - assert columns.isSubsetOf(Range.between(0, getColumnCount())) : "Range " - + "was outside of current column range (i.e.: " - + Range.between(0, getColumnCount()) - + ", but was given :" - + columns; - - double sum = 0; - for (int i = columns.getStart(); i < columns.getEnd(); i++) { - double columnWidthActual = getColumnWidthActual(i); - sum += columnWidthActual; - } - return sum; - } - - double[] getCalculatedColumnWidths() { - if (widthsArray == null || widthsArray.length != getColumnCount()) { - widthsArray = new double[getColumnCount()]; - for (int i = 0; i < columns.size(); i++) { - widthsArray[i] = columns.get(i).getCalculatedWidth(); - } - } - return widthsArray; - } - - @Override - public void refreshColumns(int index, int numberOfColumns) - throws IndexOutOfBoundsException, IllegalArgumentException { - if (numberOfColumns < 1) { - throw new IllegalArgumentException( - "Number of columns must be 1 or greater (was " - + numberOfColumns + ")"); - } - - if (index < 0 || index + numberOfColumns > getColumnCount()) { - throw new IndexOutOfBoundsException("The given " - + "column range (" + index + ".." - + (index + numberOfColumns) - + ") was outside of the current number of columns (" - + getColumnCount() + ")"); - } - - header.refreshColumns(index, numberOfColumns); - body.refreshColumns(index, numberOfColumns); - footer.refreshColumns(index, numberOfColumns); - } - } - - /** - * A decision on how to measure a spacer when it is partially within a - * designated range. - *

- * The meaning of each value may differ depending on the context it is being - * used in. Check that particular method's JavaDoc. - */ - private enum SpacerInclusionStrategy { - /** A representation of "the entire spacer". */ - COMPLETE, - - /** A representation of "a partial spacer". */ - PARTIAL, - - /** A representation of "no spacer at all". */ - NONE - } - - private class SpacerContainer { - - /** This is used mainly for testing purposes */ - private static final String SPACER_LOGICAL_ROW_PROPERTY = "vLogicalRow"; - - private final class SpacerImpl implements Spacer { - private TableCellElement spacerElement; - private TableRowElement root; - private DivElement deco; - private int rowIndex; - private double height = -1; - private boolean domHasBeenSetup = false; - private double decoHeight; - private double defaultCellBorderBottomSize = -1; - - public SpacerImpl(int rowIndex) { - this.rowIndex = rowIndex; - - root = TableRowElement.as(DOM.createTR()); - spacerElement = TableCellElement.as(DOM.createTD()); - root.appendChild(spacerElement); - root.setPropertyInt(SPACER_LOGICAL_ROW_PROPERTY, rowIndex); - deco = DivElement.as(DOM.createDiv()); - } - - public void setPositionDiff(double x, double y) { - setPosition(getLeft() + x, getTop() + y); - } - - public void setupDom(double height) { - assert !domHasBeenSetup : "DOM can't be set up twice."; - assert RootPanel.get().getElement().isOrHasChild(root) : "Root element should've been attached to the DOM by now."; - domHasBeenSetup = true; - - getRootElement().getStyle().setWidth(getInnerWidth(), Unit.PX); - setHeight(height); - - spacerElement.setColSpan(getColumnConfiguration() - .getColumnCount()); - - setStylePrimaryName(getStylePrimaryName()); - } - - public TableRowElement getRootElement() { - return root; - } - - @Override - public Element getDecoElement() { - return deco; - } - - public void setPosition(double x, double y) { - positions.set(getRootElement(), x, y); - positions - .set(getDecoElement(), 0, y - getSpacerDecoTopOffset()); - } - - private double getSpacerDecoTopOffset() { - return getBody().getDefaultRowHeight(); - } - - public void setStylePrimaryName(String style) { - UIObject.setStylePrimaryName(root, style + "-spacer"); - UIObject.setStylePrimaryName(deco, style + "-spacer-deco"); - } - - public void setHeight(double height) { - - assert height >= 0 : "Height must be more >= 0 (was " + height - + ")"; - - final double heightDiff = height - Math.max(0, this.height); - final double oldHeight = this.height; - - this.height = height; - - // since the spacer might be rendered on top of the previous - // rows border (done with css), need to increase height the - // amount of the border thickness - if (defaultCellBorderBottomSize < 0) { - defaultCellBorderBottomSize = WidgetUtil - .getBorderBottomThickness(body.getRowElement( - getVisibleRowRange().getStart()) - .getFirstChildElement()); - } - root.getStyle().setHeight(height + defaultCellBorderBottomSize, - Unit.PX); - - // move the visible spacers getRow row onwards. - shiftSpacerPositionsAfterRow(getRow(), heightDiff); - - /* - * If we're growing, we'll adjust the scroll size first, then - * adjust scrolling. If we're shrinking, we do it after the - * second if-clause. - */ - boolean spacerIsGrowing = heightDiff > 0; - if (spacerIsGrowing) { - verticalScrollbar.setScrollSize(verticalScrollbar - .getScrollSize() + heightDiff); - } - - /* - * Don't modify the scrollbars if we're expanding the -1 spacer - * while we're scrolled to the top. - */ - boolean minusOneSpacerException = spacerIsGrowing - && getRow() == -1 && body.getTopRowLogicalIndex() == 0; - - boolean viewportNeedsScrolling = getRow() < body - .getTopRowLogicalIndex() && !minusOneSpacerException; - if (viewportNeedsScrolling) { - - /* - * We can't use adjustScrollPos here, probably because of a - * bookkeeping-related race condition. - * - * This particular situation is easier, however, since we - * know exactly how many pixels we need to move (heightDiff) - * and all elements below the spacer always need to move - * that pixel amount. - */ - - for (TableRowElement row : body.visualRowOrder) { - body.setRowPosition(row, 0, body.getRowTop(row) - + heightDiff); - } - - double top = getTop(); - double bottom = top + oldHeight; - double scrollTop = verticalScrollbar.getScrollPos(); - - boolean viewportTopIsAtMidSpacer = top < scrollTop - && scrollTop < bottom; - - final double moveDiff; - if (viewportTopIsAtMidSpacer && !spacerIsGrowing) { - - /* - * If the scroll top is in the middle of the modified - * spacer, we want to scroll the viewport up as usual, - * but we don't want to scroll past the top of it. - * - * Math.max ensures this (remember: the result is going - * to be negative). - */ - - moveDiff = Math.max(heightDiff, top - scrollTop); - } else { - moveDiff = heightDiff; - } - body.setBodyScrollPosition(tBodyScrollLeft, tBodyScrollTop - + moveDiff); - verticalScrollbar.setScrollPosByDelta(moveDiff); - - } else { - body.shiftRowPositions(getRow(), heightDiff); - } - - if (!spacerIsGrowing) { - verticalScrollbar.setScrollSize(verticalScrollbar - .getScrollSize() + heightDiff); - } - - updateDecoratorGeometry(height); - } - - /** Resizes and places the decorator. */ - private void updateDecoratorGeometry(double detailsHeight) { - Style style = deco.getStyle(); - decoHeight = detailsHeight + getBody().getDefaultRowHeight(); - style.setHeight(decoHeight, Unit.PX); - } - - @Override - public Element getElement() { - return spacerElement; - } - - @Override - public int getRow() { - return rowIndex; - } - - public double getHeight() { - assert height >= 0 : "Height was not previously set by setHeight."; - return height; - } - - public double getTop() { - return positions.getTop(getRootElement()); - } - - public double getLeft() { - return positions.getLeft(getRootElement()); - } - - /** - * Sets a new row index for this spacer. Also updates the bookeeping - * at {@link SpacerContainer#rowIndexToSpacer}. - */ - @SuppressWarnings("boxing") - public void setRowIndex(int rowIndex) { - SpacerImpl spacer = rowIndexToSpacer.remove(this.rowIndex); - assert this == spacer : "trying to move an unexpected spacer."; - this.rowIndex = rowIndex; - root.setPropertyInt(SPACER_LOGICAL_ROW_PROPERTY, rowIndex); - rowIndexToSpacer.put(this.rowIndex, this); - } - - /** - * Updates the spacer's visibility parameters, based on whether it - * is being currently visible or not. - */ - public void updateVisibility() { - if (isInViewport()) { - show(); - } else { - hide(); - } - } - - private boolean isInViewport() { - int top = (int) Math.ceil(getTop()); - int height = (int) Math.floor(getHeight()); - Range location = Range.withLength(top, height); - return getViewportPixels().intersects(location); - } - - public void show() { - getRootElement().getStyle().clearDisplay(); - getDecoElement().getStyle().clearDisplay(); - } - - public void hide() { - getRootElement().getStyle().setDisplay(Display.NONE); - getDecoElement().getStyle().setDisplay(Display.NONE); - } - - /** - * Crop the decorator element so that it doesn't overlap the header - * and footer sections. - * - * @param bodyTop - * the top cordinate of the escalator body - * @param bodyBottom - * the bottom cordinate of the escalator body - * @param decoWidth - * width of the deco - */ - private void updateDecoClip(final double bodyTop, - final double bodyBottom, final double decoWidth) { - final int top = deco.getAbsoluteTop(); - final int bottom = deco.getAbsoluteBottom(); - /* - * FIXME - * - * Height and its use is a workaround for the issue where - * coordinates of the deco are not calculated yet. This will - * prevent a deco from being displayed when it's added to DOM - */ - final int height = bottom - top; - if (top < bodyTop || bottom > bodyBottom) { - final double topClip = Math.max(0.0D, bodyTop - top); - final double bottomClip = height - - Math.max(0.0D, bottom - bodyBottom); - // TODO [optimize] not sure how GWT compiles this - final String clip = new StringBuilder("rect(") - .append(topClip).append("px,").append(decoWidth) - .append("px,").append(bottomClip).append("px,0)") - .toString(); - deco.getStyle().setProperty("clip", clip); - } else { - deco.getStyle().setProperty("clip", "auto"); - } - } - } - - private final TreeMap rowIndexToSpacer = new TreeMap(); - - private SpacerUpdater spacerUpdater = SpacerUpdater.NULL; - - private final ScrollHandler spacerScroller = new ScrollHandler() { - private double prevScrollX = 0; - - @Override - public void onScroll(ScrollEvent event) { - if (WidgetUtil.pixelValuesEqual(getScrollLeft(), prevScrollX)) { - return; - } - - prevScrollX = getScrollLeft(); - for (SpacerImpl spacer : rowIndexToSpacer.values()) { - spacer.setPosition(prevScrollX, spacer.getTop()); - } - } - }; - private HandlerRegistration spacerScrollerRegistration; - - /** Width of the spacers' decos. Calculated once then cached. */ - private double spacerDecoWidth = 0.0D; - - public void setSpacer(int rowIndex, double height) - throws IllegalArgumentException { - - if (rowIndex < -1 || rowIndex >= getBody().getRowCount()) { - throw new IllegalArgumentException("invalid row index: " - + rowIndex + ", while the body only has " - + getBody().getRowCount() + " rows."); - } - - if (height >= 0) { - if (!spacerExists(rowIndex)) { - insertNewSpacer(rowIndex, height); - } else { - updateExistingSpacer(rowIndex, height); - } - } else if (spacerExists(rowIndex)) { - removeSpacer(rowIndex); - } - - updateSpacerDecosVisibility(); - } - - /** Checks if a given element is a spacer element */ - public boolean isSpacer(Element row) { - - /* - * If this needs optimization, we could do a more heuristic check - * based on stylenames and stuff, instead of iterating through the - * map. - */ - - for (SpacerImpl spacer : rowIndexToSpacer.values()) { - if (spacer.getRootElement().equals(row)) { - return true; - } - } - - return false; - } - - @SuppressWarnings("boxing") - void scrollToSpacer(int spacerIndex, ScrollDestination destination, - int padding) { - - assert !destination.equals(ScrollDestination.MIDDLE) - || padding != 0 : "destination/padding check should be done before this method"; - - if (!rowIndexToSpacer.containsKey(spacerIndex)) { - throw new IllegalArgumentException("No spacer open at index " - + spacerIndex); - } - - SpacerImpl spacer = rowIndexToSpacer.get(spacerIndex); - double targetStartPx = spacer.getTop(); - double targetEndPx = targetStartPx + spacer.getHeight(); - - Range viewportPixels = getViewportPixels(); - double viewportStartPx = viewportPixels.getStart(); - double viewportEndPx = viewportPixels.getEnd(); - - double scrollTop = getScrollPos(destination, targetStartPx, - targetEndPx, viewportStartPx, viewportEndPx, padding); - - setScrollTop(scrollTop); - } - - public void reapplySpacerWidths() { - // FIXME #16266 , spacers get couple pixels too much because borders - final double width = getInnerWidth() - spacerDecoWidth; - for (SpacerImpl spacer : rowIndexToSpacer.values()) { - spacer.getRootElement().getStyle().setWidth(width, Unit.PX); - } - } - - public void paintRemoveSpacers(Range removedRowsRange) { - removeSpacers(removedRowsRange); - shiftSpacersByRows(removedRowsRange.getStart(), - -removedRowsRange.length()); - } - - @SuppressWarnings("boxing") - public void removeSpacers(Range removedRange) { - - Map removedSpacers = rowIndexToSpacer - .subMap(removedRange.getStart(), true, - removedRange.getEnd(), false); - - if (removedSpacers.isEmpty()) { - return; - } - - for (SpacerImpl spacer : removedSpacers.values()) { - /* - * [[optimization]] TODO: Each invocation of the setHeight - * method has a cascading effect in the DOM. if this proves to - * be slow, the DOM offset could be updated as a batch. - */ - - destroySpacerContent(spacer); - spacer.setHeight(0); // resets row offsets - spacer.getRootElement().removeFromParent(); - spacer.getDecoElement().removeFromParent(); - } - - removedSpacers.clear(); - - if (rowIndexToSpacer.isEmpty()) { - assert spacerScrollerRegistration != null : "Spacer scroller registration was null"; - spacerScrollerRegistration.removeHandler(); - spacerScrollerRegistration = null; - } - } - - public Map getSpacers() { - return new HashMap(rowIndexToSpacer); - } - - /** - * Calculates the sum of all spacers. - * - * @return sum of all spacers, or 0 if no spacers present - */ - public double getSpacerHeightsSum() { - return getHeights(rowIndexToSpacer.values()); - } - - /** - * Calculates the sum of all spacers from one row index onwards. - * - * @param logicalRowIndex - * the spacer to include as the first calculated spacer - * @return the sum of all spacers from {@code logicalRowIndex} and - * onwards, or 0 if no suitable spacers were found - */ - @SuppressWarnings("boxing") - public Collection getSpacersForRowAndAfter( - int logicalRowIndex) { - return new ArrayList(rowIndexToSpacer.tailMap( - logicalRowIndex, true).values()); - } - - /** - * Get all spacers from one pixel point onwards. - *

- * - * In this method, the {@link SpacerInclusionStrategy} has the following - * meaning when a spacer lies in the middle of either pixel argument: - *

- *
{@link SpacerInclusionStrategy#COMPLETE COMPLETE} - *
include the spacer - *
{@link SpacerInclusionStrategy#PARTIAL PARTIAL} - *
include the spacer - *
{@link SpacerInclusionStrategy#NONE NONE} - *
ignore the spacer - *
- * - * @param px - * the pixel point after which to return all spacers - * @param strategy - * the inclusion strategy regarding the {@code px} - * @return a collection of the spacers that exist after {@code px} - */ - public Collection getSpacersAfterPx(final double px, - final SpacerInclusionStrategy strategy) { - - ArrayList spacers = new ArrayList( - rowIndexToSpacer.values()); - - for (int i = 0; i < spacers.size(); i++) { - SpacerImpl spacer = spacers.get(i); - - double top = spacer.getTop(); - double bottom = top + spacer.getHeight(); - - if (top > px) { - return spacers.subList(i, spacers.size()); - } else if (bottom > px) { - if (strategy == SpacerInclusionStrategy.NONE) { - return spacers.subList(i + 1, spacers.size()); - } else { - return spacers.subList(i, spacers.size()); - } - } - } - - return Collections.emptySet(); - } - - /** - * Gets the spacers currently rendered in the DOM. - * - * @return an unmodifiable (but live) collection of the spacers - * currently in the DOM - */ - public Collection getSpacersInDom() { - return Collections - .unmodifiableCollection(rowIndexToSpacer.values()); - } - - /** - * Gets the amount of pixels occupied by spacers between two pixel - * points. - *

- * In this method, the {@link SpacerInclusionStrategy} has the following - * meaning when a spacer lies in the middle of either pixel argument: - *

- *
{@link SpacerInclusionStrategy#COMPLETE COMPLETE} - *
take the entire spacer into account - *
{@link SpacerInclusionStrategy#PARTIAL PARTIAL} - *
take only the visible area into account - *
{@link SpacerInclusionStrategy#NONE NONE} - *
ignore that spacer - *
- * - * @param rangeTop - * the top pixel point - * @param topInclusion - * the inclusion strategy regarding {@code rangeTop}. - * @param rangeBottom - * the bottom pixel point - * @param bottomInclusion - * the inclusion strategy regarding {@code rangeBottom}. - * @return the pixels occupied by spacers between {@code rangeTop} and - * {@code rangeBottom} - */ - public double getSpacerHeightsSumBetweenPx(double rangeTop, - SpacerInclusionStrategy topInclusion, double rangeBottom, - SpacerInclusionStrategy bottomInclusion) { - - assert rangeTop <= rangeBottom : "rangeTop must be less than rangeBottom"; - - double heights = 0; - - /* - * TODO [[optimize]]: this might be somewhat inefficient (due to - * iterator-based scanning, instead of using the treemap's search - * functionalities). But it should be easy to write, read, verify - * and maintain. - */ - for (SpacerImpl spacer : rowIndexToSpacer.values()) { - double top = spacer.getTop(); - double height = spacer.getHeight(); - double bottom = top + height; - - /* - * If we happen to implement a DoubleRange (in addition to the - * int-based Range) at some point, the following logic should - * probably be converted into using the - * Range.partitionWith-equivalent. - */ - - boolean topIsAboveRange = top < rangeTop; - boolean topIsInRange = rangeTop <= top && top <= rangeBottom; - boolean topIsBelowRange = rangeBottom < top; - - boolean bottomIsAboveRange = bottom < rangeTop; - boolean bottomIsInRange = rangeTop <= bottom - && bottom <= rangeBottom; - boolean bottomIsBelowRange = rangeBottom < bottom; - - assert topIsAboveRange ^ topIsBelowRange ^ topIsInRange : "Bad top logic"; - assert bottomIsAboveRange ^ bottomIsBelowRange - ^ bottomIsInRange : "Bad bottom logic"; - - if (bottomIsAboveRange) { - continue; - } else if (topIsBelowRange) { - return heights; - } - - else if (topIsAboveRange && bottomIsInRange) { - switch (topInclusion) { - case PARTIAL: - heights += bottom - rangeTop; - break; - case COMPLETE: - heights += height; - break; - default: - break; - } - } - - else if (topIsAboveRange && bottomIsBelowRange) { - - /* - * Here we arbitrarily decide that the top inclusion will - * have the honor of overriding the bottom inclusion if - * happens to be a conflict of interests. - */ - switch (topInclusion) { - case NONE: - return 0; - case COMPLETE: - return height; - case PARTIAL: - return rangeBottom - rangeTop; - default: - throw new IllegalArgumentException( - "Unexpected inclusion state :" + topInclusion); - } - - } else if (topIsInRange && bottomIsInRange) { - heights += height; - } - - else if (topIsInRange && bottomIsBelowRange) { - switch (bottomInclusion) { - case PARTIAL: - heights += rangeBottom - top; - break; - case COMPLETE: - heights += height; - break; - default: - break; - } - - return heights; - } - - else { - assert false : "Unnaccounted-for situation"; - } - } - - return heights; - } - - /** - * Gets the amount of pixels occupied by spacers from the top until a - * certain spot from the top of the body. - * - * @param px - * pixels counted from the top - * @return the pixels occupied by spacers up until {@code px} - */ - public double getSpacerHeightsSumUntilPx(double px) { - return getSpacerHeightsSumBetweenPx(0, - SpacerInclusionStrategy.PARTIAL, px, - SpacerInclusionStrategy.PARTIAL); - } - - /** - * Gets the amount of pixels occupied by spacers until a logical row - * index. - * - * @param logicalIndex - * a logical row index - * @return the pixels occupied by spacers up until {@code logicalIndex} - */ - @SuppressWarnings("boxing") - public double getSpacerHeightsSumUntilIndex(int logicalIndex) { - return getHeights(rowIndexToSpacer.headMap(logicalIndex, false) - .values()); - } - - private double getHeights(Collection spacers) { - double heights = 0; - for (SpacerImpl spacer : spacers) { - heights += spacer.getHeight(); - } - return heights; - } - - /** - * Gets the height of the spacer for a row index. - * - * @param rowIndex - * the index of the row where the spacer should be - * @return the height of the spacer at index {@code rowIndex}, or 0 if - * there is no spacer there - */ - public double getSpacerHeight(int rowIndex) { - SpacerImpl spacer = getSpacer(rowIndex); - if (spacer != null) { - return spacer.getHeight(); - } else { - return 0; - } - } - - private boolean spacerExists(int rowIndex) { - return rowIndexToSpacer.containsKey(Integer.valueOf(rowIndex)); - } - - @SuppressWarnings("boxing") - private void insertNewSpacer(int rowIndex, double height) { - - if (spacerScrollerRegistration == null) { - spacerScrollerRegistration = addScrollHandler(spacerScroller); - } - - final SpacerImpl spacer = new SpacerImpl(rowIndex); - - rowIndexToSpacer.put(rowIndex, spacer); - // set the position before adding it to DOM - positions.set(spacer.getRootElement(), getScrollLeft(), - calculateSpacerTop(rowIndex)); - - TableRowElement spacerRoot = spacer.getRootElement(); - spacerRoot.getStyle().setWidth( - columnConfiguration.calculateRowWidth(), Unit.PX); - body.getElement().appendChild(spacerRoot); - spacer.setupDom(height); - // set the deco position, requires that spacer is in the DOM - positions.set(spacer.getDecoElement(), 0, - spacer.getTop() - spacer.getSpacerDecoTopOffset()); - - spacerDecoContainer.appendChild(spacer.getDecoElement()); - if (spacerDecoContainer.getParentElement() == null) { - getElement().appendChild(spacerDecoContainer); - // calculate the spacer deco width, it won't change - spacerDecoWidth = WidgetUtil - .getRequiredWidthBoundingClientRectDouble(spacer - .getDecoElement()); - } - - initSpacerContent(spacer); - - body.sortDomElements(); - } - - private void updateExistingSpacer(int rowIndex, double newHeight) { - getSpacer(rowIndex).setHeight(newHeight); - } - - public SpacerImpl getSpacer(int rowIndex) { - return rowIndexToSpacer.get(Integer.valueOf(rowIndex)); - } - - private void removeSpacer(int rowIndex) { - removeSpacers(Range.withOnly(rowIndex)); - } - - public void setStylePrimaryName(String style) { - for (SpacerImpl spacer : rowIndexToSpacer.values()) { - spacer.setStylePrimaryName(style); - } - } - - public void setSpacerUpdater(SpacerUpdater spacerUpdater) - throws IllegalArgumentException { - if (spacerUpdater == null) { - throw new IllegalArgumentException( - "spacer updater cannot be null"); - } - - destroySpacerContent(rowIndexToSpacer.values()); - this.spacerUpdater = spacerUpdater; - initSpacerContent(rowIndexToSpacer.values()); - } - - public SpacerUpdater getSpacerUpdater() { - return spacerUpdater; - } - - private void destroySpacerContent(Iterable spacers) { - for (SpacerImpl spacer : spacers) { - destroySpacerContent(spacer); - } - } - - private void destroySpacerContent(SpacerImpl spacer) { - assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator before detaching"; - assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator before detaching"; - spacerUpdater.destroy(spacer); - assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator before detaching"; - assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator before detaching"; - } - - private void initSpacerContent(Iterable spacers) { - for (SpacerImpl spacer : spacers) { - initSpacerContent(spacer); - } - } - - private void initSpacerContent(SpacerImpl spacer) { - assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator before attaching"; - assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator before attaching"; - spacerUpdater.init(spacer); - assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator during attaching"; - assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator during attaching"; - - spacer.updateVisibility(); - } - - public String getSubPartName(Element subElement) { - for (SpacerImpl spacer : rowIndexToSpacer.values()) { - if (spacer.getRootElement().isOrHasChild(subElement)) { - return "spacer[" + spacer.getRow() + "]"; - } - } - return null; - } - - public Element getSubPartElement(int index) { - SpacerImpl spacer = rowIndexToSpacer.get(Integer.valueOf(index)); - if (spacer != null) { - return spacer.getElement(); - } else { - return null; - } - } - - private double calculateSpacerTop(int logicalIndex) { - return body.getRowTop(logicalIndex) + body.getDefaultRowHeight(); - } - - @SuppressWarnings("boxing") - private void shiftSpacerPositionsAfterRow(int changedRowIndex, - double diffPx) { - for (SpacerImpl spacer : rowIndexToSpacer.tailMap(changedRowIndex, - false).values()) { - spacer.setPositionDiff(0, diffPx); - } - } - - /** - * Shifts spacers at and after a specific row by an amount of rows. - *

- * This moves both their associated row index and also their visual - * placement. - *

- * Note: This method does not check for the validity of any - * arguments. - * - * @param index - * the index of first row to move - * @param numberOfRows - * the number of rows to shift the spacers with. A positive - * value is downwards, a negative value is upwards. - */ - public void shiftSpacersByRows(int index, int numberOfRows) { - final double pxDiff = numberOfRows * body.getDefaultRowHeight(); - for (SpacerContainer.SpacerImpl spacer : getSpacersForRowAndAfter(index)) { - spacer.setPositionDiff(0, pxDiff); - spacer.setRowIndex(spacer.getRow() + numberOfRows); - } - } - - private void updateSpacerDecosVisibility() { - final Range visibleRowRange = getVisibleRowRange(); - Collection visibleSpacers = rowIndexToSpacer.subMap( - visibleRowRange.getStart() - 1, - visibleRowRange.getEnd() + 1).values(); - if (!visibleSpacers.isEmpty()) { - final double top = tableWrapper.getAbsoluteTop() - + header.getHeightOfSection(); - final double bottom = tableWrapper.getAbsoluteBottom() - - footer.getHeightOfSection(); - for (SpacerImpl spacer : visibleSpacers) { - spacer.updateDecoClip(top, bottom, spacerDecoWidth); - } - } - } - } - - private class ElementPositionBookkeeper { - /** - * A map containing cached values of an element's current top position. - */ - private final Map elementTopPositionMap = new HashMap(); - private final Map elementLeftPositionMap = new HashMap(); - - public void set(final Element e, final double x, final double y) { - assert e != null : "Element was null"; - position.set(e, x, y); - elementTopPositionMap.put(e, Double.valueOf(y)); - elementLeftPositionMap.put(e, Double.valueOf(x)); - } - - public double getTop(final Element e) { - Double top = elementTopPositionMap.get(e); - if (top == null) { - throw new IllegalArgumentException("Element " + e - + " was not found in the position bookkeeping"); - } - return top.doubleValue(); - } - - public double getLeft(final Element e) { - Double left = elementLeftPositionMap.get(e); - if (left == null) { - throw new IllegalArgumentException("Element " + e - + " was not found in the position bookkeeping"); - } - return left.doubleValue(); - } - - public void remove(Element e) { - elementTopPositionMap.remove(e); - elementLeftPositionMap.remove(e); - } - } - - /** - * Utility class for parsing and storing SubPart request string attributes - * for Grid and Escalator. - * - * @since 7.5.0 - */ - public static class SubPartArguments { - private String type; - private int[] indices; - - private SubPartArguments(String type, int[] indices) { - /* - * The constructor is private so that no third party would by - * mistake start using this parsing scheme, since it's not official - * by TestBench (yet?). - */ - - this.type = type; - this.indices = indices; - } - - public String getType() { - return type; - } - - public int getIndicesLength() { - return indices.length; - } - - public int getIndex(int i) { - return indices[i]; - } - - public int[] getIndices() { - return Arrays.copyOf(indices, indices.length); - } - - static SubPartArguments create(String subPart) { - String[] splitArgs = subPart.split("\\["); - String type = splitArgs[0]; - int[] indices = new int[splitArgs.length - 1]; - for (int i = 0; i < indices.length; ++i) { - String tmp = splitArgs[i + 1]; - indices[i] = Integer.parseInt(tmp.substring(0, - tmp.indexOf("]", 1))); - } - return new SubPartArguments(type, indices); - } - } - - // abs(atan(y/x))*(180/PI) = n deg, x = 1, solve y - /** - * The solution to - * |tan-1(x)|×(180/π) = 30 - * . - *

- * This constant is placed in the Escalator class, instead of an inner - * class, since even mathematical expressions aren't allowed in non-static - * inner classes for constants. - */ - private static final double RATIO_OF_30_DEGREES = 1 / Math.sqrt(3); - /** - * The solution to - * |tan-1(x)|×(180/π) = 40 - * . - *

- * This constant is placed in the Escalator class, instead of an inner - * class, since even mathematical expressions aren't allowed in non-static - * inner classes for constants. - */ - private static final double RATIO_OF_40_DEGREES = Math.tan(2 * Math.PI / 9); - - private static final String DEFAULT_WIDTH = "500.0px"; - private static final String DEFAULT_HEIGHT = "400.0px"; - - private FlyweightRow flyweightRow = new FlyweightRow(); - - /** The {@code } tag. */ - private final TableSectionElement headElem = TableSectionElement.as(DOM - .createTHead()); - /** The {@code } tag. */ - private final TableSectionElement bodyElem = TableSectionElement.as(DOM - .createTBody()); - /** The {@code } tag. */ - private final TableSectionElement footElem = TableSectionElement.as(DOM - .createTFoot()); - - /** - * TODO: investigate whether this field is now unnecessary, as - * {@link ScrollbarBundle} now caches its values. - * - * @deprecated maybe... - */ - @Deprecated - private double tBodyScrollTop = 0; - - /** - * TODO: investigate whether this field is now unnecessary, as - * {@link ScrollbarBundle} now caches its values. - * - * @deprecated maybe... - */ - @Deprecated - private double tBodyScrollLeft = 0; - - private final VerticalScrollbarBundle verticalScrollbar = new VerticalScrollbarBundle(); - private final HorizontalScrollbarBundle horizontalScrollbar = new HorizontalScrollbarBundle(); - - private final HeaderRowContainer header = new HeaderRowContainer(headElem); - private final BodyRowContainerImpl body = new BodyRowContainerImpl(bodyElem); - private final FooterRowContainer footer = new FooterRowContainer(footElem); - - private final Scroller scroller = new Scroller(); - - private final ColumnConfigurationImpl columnConfiguration = new ColumnConfigurationImpl(); - private final DivElement tableWrapper; - - private final DivElement horizontalScrollbarDeco = DivElement.as(DOM - .createDiv()); - private final DivElement headerDeco = DivElement.as(DOM.createDiv()); - private final DivElement footerDeco = DivElement.as(DOM.createDiv()); - private final DivElement spacerDecoContainer = DivElement.as(DOM - .createDiv()); - - private PositionFunction position; - - /** The cached width of the escalator, in pixels. */ - private double widthOfEscalator = 0; - /** The cached height of the escalator, in pixels. */ - private double heightOfEscalator = 0; - - /** The height of Escalator in terms of body rows. */ - private double heightByRows = 10.0d; - - /** The height of Escalator, as defined by {@link #setHeight(String)} */ - private String heightByCss = ""; - - private HeightMode heightMode = HeightMode.CSS; - - private boolean layoutIsScheduled = false; - private ScheduledCommand layoutCommand = new ScheduledCommand() { - @Override - public void execute() { - recalculateElementSizes(); - layoutIsScheduled = false; - } - }; - - private final ElementPositionBookkeeper positions = new ElementPositionBookkeeper(); - - /** - * Creates a new Escalator widget instance. - */ - public Escalator() { - - detectAndApplyPositionFunction(); - getLogger().info( - "Using " + position.getClass().getSimpleName() - + " for position"); - - final Element root = DOM.createDiv(); - setElement(root); - - setupScrollbars(root); - - tableWrapper = DivElement.as(DOM.createDiv()); - - root.appendChild(tableWrapper); - - final Element table = DOM.createTable(); - tableWrapper.appendChild(table); - - table.appendChild(headElem); - table.appendChild(bodyElem); - table.appendChild(footElem); - - Style hCornerStyle = headerDeco.getStyle(); - hCornerStyle.setWidth(verticalScrollbar.getScrollbarThickness(), - Unit.PX); - hCornerStyle.setDisplay(Display.NONE); - root.appendChild(headerDeco); - - Style fCornerStyle = footerDeco.getStyle(); - fCornerStyle.setWidth(verticalScrollbar.getScrollbarThickness(), - Unit.PX); - fCornerStyle.setDisplay(Display.NONE); - root.appendChild(footerDeco); - - Style hWrapperStyle = horizontalScrollbarDeco.getStyle(); - hWrapperStyle.setDisplay(Display.NONE); - hWrapperStyle.setHeight(horizontalScrollbar.getScrollbarThickness(), - Unit.PX); - root.appendChild(horizontalScrollbarDeco); - - setStylePrimaryName("v-escalator"); - - spacerDecoContainer.setAttribute("aria-hidden", "true"); - - // init default dimensions - setHeight(null); - setWidth(null); - } - - private void setupScrollbars(final Element root) { - - ScrollHandler scrollHandler = new ScrollHandler() { - @Override - public void onScroll(ScrollEvent event) { - scroller.onScroll(); - fireEvent(new ScrollEvent()); - } - }; - - int scrollbarThickness = WidgetUtil.getNativeScrollbarSize(); - if (BrowserInfo.get().isIE()) { - /* - * IE refuses to scroll properly if the DIV isn't at least one pixel - * larger than the scrollbar controls themselves. But, probably - * because of subpixel rendering, in Grid, one pixel isn't enough, - * so we'll add two instead. - */ - if (BrowserInfo.get().isIE9()) { - scrollbarThickness += 2; - } else { - scrollbarThickness += 1; - } - } - - root.appendChild(verticalScrollbar.getElement()); - verticalScrollbar.addScrollHandler(scrollHandler); - verticalScrollbar.setScrollbarThickness(scrollbarThickness); - - if (BrowserInfo.get().isIE8()) { - /* - * IE8 will have to compensate for a misalignment where it pops the - * scrollbar outside of its box. See Bug 3 in - * http://edskes.net/ie/ie8overflowandexpandingboxbugs.htm - */ - Style vScrollStyle = verticalScrollbar.getElement().getStyle(); - vScrollStyle.setRight( - verticalScrollbar.getScrollbarThickness() - 1, Unit.PX); - } - - root.appendChild(horizontalScrollbar.getElement()); - horizontalScrollbar.addScrollHandler(scrollHandler); - horizontalScrollbar.setScrollbarThickness(scrollbarThickness); - horizontalScrollbar - .addVisibilityHandler(new ScrollbarBundle.VisibilityHandler() { - - private boolean queued = false; - - @Override - public void visibilityChanged( - ScrollbarBundle.VisibilityChangeEvent event) { - if (queued) { - return; - } - queued = true; - - /* - * We either lost or gained a scrollbar. In any case, we - * need to change the height, if it's defined by rows. - */ - Scheduler.get().scheduleFinally(new ScheduledCommand() { - - @Override - public void execute() { - applyHeightByRows(); - queued = false; - } - }); - } - }); - - /* - * Because of all the IE hacks we've done above, we now have scrollbars - * hiding underneath a lot of DOM elements. - * - * This leads to problems with OSX (and many touch-only devices) when - * scrollbars are only shown when scrolling, as the scrollbar elements - * are hidden underneath everything. We trust that the scrollbars behave - * properly in these situations and simply pop them out with a bit of - * z-indexing. - */ - if (WidgetUtil.getNativeScrollbarSize() == 0) { - verticalScrollbar.getElement().getStyle().setZIndex(90); - horizontalScrollbar.getElement().getStyle().setZIndex(90); - } - } - - @Override - protected void onLoad() { - super.onLoad(); - - header.autodetectRowHeightLater(); - body.autodetectRowHeightLater(); - footer.autodetectRowHeightLater(); - - header.paintInsertRows(0, header.getRowCount()); - footer.paintInsertRows(0, footer.getRowCount()); - - // recalculateElementSizes(); - - Scheduler.get().scheduleDeferred(new Command() { - @Override - public void execute() { - /* - * Not a faintest idea why we have to defer this call, but - * unless it is deferred, the size of the escalator will be 0x0 - * after it is first detached and then reattached to the DOM. - * This only applies to a bare Escalator; inside a Grid - * everything works fine either way. - * - * The three autodetectRowHeightLater calls above seem obvious - * suspects at first. However, they don't seem to have anything - * to do with the issue, as they are no-ops in the - * detach-reattach case. - */ - recalculateElementSizes(); - } - }); - - /* - * Note: There's no need to explicitly insert rows into the body. - * - * recalculateElementSizes will recalculate the height of the body. This - * has the side-effect that as the body's size grows bigger (i.e. from 0 - * to its actual height), more escalator rows are populated. Those - * escalator rows are then immediately rendered. This, in effect, is the - * same thing as inserting those rows. - * - * In fact, having an extra paintInsertRows here would lead to duplicate - * rows. - */ - - boolean columnsChanged = false; - for (ColumnConfigurationImpl.Column column : columnConfiguration.columns) { - boolean columnChanged = column.measureAndSetWidthIfNeeded(); - if (columnChanged) { - columnsChanged = true; - } - } - if (columnsChanged) { - header.reapplyColumnWidths(); - body.reapplyColumnWidths(); - footer.reapplyColumnWidths(); - } - - verticalScrollbar.onLoad(); - horizontalScrollbar.onLoad(); - - scroller.attachScrollListener(verticalScrollbar.getElement()); - scroller.attachScrollListener(horizontalScrollbar.getElement()); - scroller.attachMousewheelListener(getElement()); - scroller.attachTouchListeners(getElement()); - } - - @Override - protected void onUnload() { - - scroller.detachScrollListener(verticalScrollbar.getElement()); - scroller.detachScrollListener(horizontalScrollbar.getElement()); - scroller.detachMousewheelListener(getElement()); - scroller.detachTouchListeners(getElement()); - - /* - * We can call paintRemoveRows here, because static ranges are simple to - * remove. - */ - header.paintRemoveRows(0, header.getRowCount()); - footer.paintRemoveRows(0, footer.getRowCount()); - - /* - * We can't call body.paintRemoveRows since it relies on rowCount to be - * updated correctly. Since it isn't, we'll simply and brutally rip out - * the DOM elements (in an elegant way, of course). - */ - int rowsToRemove = body.getDomRowCount(); - for (int i = 0; i < rowsToRemove; i++) { - int index = rowsToRemove - i - 1; - TableRowElement tr = bodyElem.getRows().getItem(index); - body.paintRemoveRow(tr, index); - positions.remove(tr); - } - body.visualRowOrder.clear(); - body.setTopRowLogicalIndex(0); - - super.onUnload(); - } - - private void detectAndApplyPositionFunction() { - /* - * firefox has a bug in its translate operation, showing white space - * when adjusting the scrollbar in BodyRowContainer.paintInsertRows - */ - if (Window.Navigator.getUserAgent().contains("Firefox")) { - position = new AbsolutePosition(); - return; - } - - final Style docStyle = Document.get().getBody().getStyle(); - if (hasProperty(docStyle, "transform")) { - if (hasProperty(docStyle, "transformStyle")) { - position = new Translate3DPosition(); - } else { - position = new TranslatePosition(); - } - } else if (hasProperty(docStyle, "webkitTransform")) { - position = new WebkitTranslate3DPosition(); - } else { - position = new AbsolutePosition(); - } - } - - private Logger getLogger() { - return Logger.getLogger(getClass().getName()); - } - - private static native boolean hasProperty(Style style, String name) - /*-{ - return style[name] !== undefined; - }-*/; - - /** - * Check whether there are both columns and any row data (for either - * headers, body or footer). - * - * @return true iff header, body or footer has rows && there - * are columns - */ - private boolean hasColumnAndRowData() { - return (header.getRowCount() > 0 || body.getRowCount() > 0 || footer - .getRowCount() > 0) && columnConfiguration.getColumnCount() > 0; - } - - /** - * Check whether there are any cells in the DOM. - * - * @return true iff header, body or footer has any child - * elements - */ - private boolean hasSomethingInDom() { - return headElem.hasChildNodes() || bodyElem.hasChildNodes() - || footElem.hasChildNodes(); - } - - /** - * Returns the row container for the header in this Escalator. - * - * @return the header. Never null - */ - public RowContainer getHeader() { - return header; - } - - /** - * Returns the row container for the body in this Escalator. - * - * @return the body. Never null - */ - public BodyRowContainer getBody() { - return body; - } - - /** - * Returns the row container for the footer in this Escalator. - * - * @return the footer. Never null - */ - public RowContainer getFooter() { - return footer; - } - - /** - * Returns the configuration object for the columns in this Escalator. - * - * @return the configuration object for the columns in this Escalator. Never - * null - */ - public ColumnConfiguration getColumnConfiguration() { - return columnConfiguration; - } - - @Override - public void setWidth(final String width) { - if (width != null && !width.isEmpty()) { - super.setWidth(width); - } else { - super.setWidth(DEFAULT_WIDTH); - } - - recalculateElementSizes(); - } - - /** - * {@inheritDoc} - *

- * If Escalator is currently not in {@link HeightMode#CSS}, the given value - * is remembered, and applied once the mode is applied. - * - * @see #setHeightMode(HeightMode) - */ - @Override - public void setHeight(String height) { - /* - * TODO remove method once RequiresResize and the Vaadin layoutmanager - * listening mechanisms are implemented - */ - - if (height != null && !height.isEmpty()) { - heightByCss = height; - } else { - heightByCss = DEFAULT_HEIGHT; - } - - if (getHeightMode() == HeightMode.CSS) { - setHeightInternal(height); - } - } - - private void setHeightInternal(final String height) { - final int escalatorRowsBefore = body.visualRowOrder.size(); - - if (height != null && !height.isEmpty()) { - super.setHeight(height); - } else { - super.setHeight(DEFAULT_HEIGHT); - } - - recalculateElementSizes(); - - if (escalatorRowsBefore != body.visualRowOrder.size()) { - fireRowVisibilityChangeEvent(); - } - } - - /** - * Returns the vertical scroll offset. Note that this is not necessarily the - * same as the {@code scrollTop} attribute in the DOM. - * - * @return the logical vertical scroll offset - */ - public double getScrollTop() { - return verticalScrollbar.getScrollPos(); - } - - /** - * Sets the vertical scroll offset. Note that this will not necessarily - * become the same as the {@code scrollTop} attribute in the DOM. - * - * @param scrollTop - * the number of pixels to scroll vertically - */ - public void setScrollTop(final double scrollTop) { - verticalScrollbar.setScrollPos(scrollTop); - } - - /** - * Returns the logical horizontal scroll offset. Note that this is not - * necessarily the same as the {@code scrollLeft} attribute in the DOM. - * - * @return the logical horizontal scroll offset - */ - public double getScrollLeft() { - return horizontalScrollbar.getScrollPos(); - } - - /** - * Sets the logical horizontal scroll offset. Note that will not necessarily - * become the same as the {@code scrollLeft} attribute in the DOM. - * - * @param scrollLeft - * the number of pixels to scroll horizontally - */ - public void setScrollLeft(final double scrollLeft) { - horizontalScrollbar.setScrollPos(scrollLeft); - } - - /** - * Returns the scroll width for the escalator. Note that this is not - * necessary the same as {@code Element.scrollWidth} in the DOM. - * - * @since 7.5.0 - * @return the scroll width in pixels - */ - public double getScrollWidth() { - return horizontalScrollbar.getScrollSize(); - } - - /** - * Returns the scroll height for the escalator. Note that this is not - * necessary the same as {@code Element.scrollHeight} in the DOM. - * - * @since 7.5.0 - * @return the scroll height in pixels - */ - public double getScrollHeight() { - return verticalScrollbar.getScrollSize(); - } - - /** - * Scrolls the body horizontally so that the column at the given index is - * visible and there is at least {@code padding} pixels in the direction of - * the given scroll destination. - * - * @param columnIndex - * the index of the column to scroll to - * @param destination - * where the column should be aligned visually after scrolling - * @param padding - * the number pixels to place between the scrolled-to column and - * the viewport edge. - * @throws IndexOutOfBoundsException - * if {@code columnIndex} is not a valid index for an existing - * column - * @throws IllegalArgumentException - * if {@code destination} is {@link ScrollDestination#MIDDLE} - * and padding is nonzero; or if the indicated column is frozen; - * or if {@code destination == null} - */ - public void scrollToColumn(final int columnIndex, - final ScrollDestination destination, final int padding) - throws IndexOutOfBoundsException, IllegalArgumentException { - validateScrollDestination(destination, padding); - verifyValidColumnIndex(columnIndex); - - if (columnIndex < columnConfiguration.frozenColumns) { - throw new IllegalArgumentException("The given column index " - + columnIndex + " is frozen."); - } - - scroller.scrollToColumn(columnIndex, destination, padding); - } - - private void verifyValidColumnIndex(final int columnIndex) - throws IndexOutOfBoundsException { - if (columnIndex < 0 - || columnIndex >= columnConfiguration.getColumnCount()) { - throw new IndexOutOfBoundsException("The given column index " - + columnIndex + " does not exist."); - } - } - - /** - * Scrolls the body vertically so that the row at the given index is visible - * and there is at least {@literal padding} pixels to the given scroll - * destination. - * - * @param rowIndex - * the index of the logical row to scroll to - * @param destination - * where the row should be aligned visually after scrolling - * @param padding - * the number pixels to place between the scrolled-to row and the - * viewport edge. - * @throws IndexOutOfBoundsException - * if {@code rowIndex} is not a valid index for an existing row - * @throws IllegalArgumentException - * if {@code destination} is {@link ScrollDestination#MIDDLE} - * and padding is nonzero; or if {@code destination == null} - * @see #scrollToRowAndSpacer(int, ScrollDestination, int) - * @see #scrollToSpacer(int, ScrollDestination, int) - */ - public void scrollToRow(final int rowIndex, - final ScrollDestination destination, final int padding) - throws IndexOutOfBoundsException, IllegalArgumentException { - Scheduler.get().scheduleFinally(new ScheduledCommand() { - @Override - public void execute() { - validateScrollDestination(destination, padding); - verifyValidRowIndex(rowIndex); - scroller.scrollToRow(rowIndex, destination, padding); - } - }); - } - - private void verifyValidRowIndex(final int rowIndex) { - if (rowIndex < 0 || rowIndex >= body.getRowCount()) { - throw new IndexOutOfBoundsException("The given row index " - + rowIndex + " does not exist."); - } - } - - /** - * Scrolls the body vertically so that the spacer at the given row index is - * visible and there is at least {@literal padding} pixesl to the given - * scroll destination. - * - * @since 7.5.0 - * @param spacerIndex - * the row index of the spacer to scroll to - * @param destination - * where the spacer should be aligned visually after scrolling - * @param padding - * the number of pixels to place between the scrolled-to spacer - * and the viewport edge - * @throws IllegalArgumentException - * if {@code spacerIndex} is not an opened spacer; or if - * {@code destination} is {@link ScrollDestination#MIDDLE} and - * padding is nonzero; or if {@code destination == null} - * @see #scrollToRow(int, ScrollDestination, int) - * @see #scrollToRowAndSpacer(int, ScrollDestination, int) - */ - public void scrollToSpacer(final int spacerIndex, - ScrollDestination destination, final int padding) - throws IllegalArgumentException { - validateScrollDestination(destination, padding); - body.scrollToSpacer(spacerIndex, destination, padding); - } - - /** - * Scrolls vertically to a row and the spacer below it. - *

- * If a spacer is not open at that index, this method behaves like - * {@link #scrollToRow(int, ScrollDestination, int)} - * - * @since 7.5.0 - * @param rowIndex - * the index of the logical row to scroll to. -1 takes the - * topmost spacer into account as well. - * @param destination - * where the row should be aligned visually after scrolling - * @param padding - * the number pixels to place between the scrolled-to row and the - * viewport edge. - * @see #scrollToRow(int, ScrollDestination, int) - * @see #scrollToSpacer(int, ScrollDestination, int) - * @throws IllegalArgumentException - * if {@code destination} is {@link ScrollDestination#MIDDLE} - * and {@code padding} is not zero; or if {@code rowIndex} is - * not a valid row index, or -1; or if - * {@code destination == null}; or if {@code rowIndex == -1} and - * there is no spacer open at that index. - */ - public void scrollToRowAndSpacer(final int rowIndex, - final ScrollDestination destination, final int padding) - throws IllegalArgumentException { - Scheduler.get().scheduleFinally(new ScheduledCommand() { - @Override - public void execute() { - validateScrollDestination(destination, padding); - if (rowIndex != -1) { - verifyValidRowIndex(rowIndex); - } - - // row range - final Range rowRange; - if (rowIndex != -1) { - int rowTop = (int) Math.floor(body.getRowTop(rowIndex)); - int rowHeight = (int) Math.ceil(body.getDefaultRowHeight()); - rowRange = Range.withLength(rowTop, rowHeight); - } else { - rowRange = Range.withLength(0, 0); - } - - // get spacer - final SpacerContainer.SpacerImpl spacer = body.spacerContainer - .getSpacer(rowIndex); - - if (rowIndex == -1 && spacer == null) { - throw new IllegalArgumentException( - "Cannot scroll to row index " - + "-1, as there is no spacer open at that index."); - } - - // make into target range - final Range targetRange; - if (spacer != null) { - final int spacerTop = (int) Math.floor(spacer.getTop()); - final int spacerHeight = (int) Math.ceil(spacer.getHeight()); - Range spacerRange = Range.withLength(spacerTop, - spacerHeight); - - targetRange = rowRange.combineWith(spacerRange); - } else { - targetRange = rowRange; - } - - // get params - int targetStart = targetRange.getStart(); - int targetEnd = targetRange.getEnd(); - double viewportStart = getScrollTop(); - double viewportEnd = viewportStart + body.getHeightOfSection(); - - double scrollPos = getScrollPos(destination, targetStart, - targetEnd, viewportStart, viewportEnd, padding); - - setScrollTop(scrollPos); - } - }); - } - - private static void validateScrollDestination( - final ScrollDestination destination, final int padding) { - if (destination == null) { - throw new IllegalArgumentException("Destination cannot be null"); - } - - if (destination == ScrollDestination.MIDDLE && padding != 0) { - throw new IllegalArgumentException( - "You cannot have a padding with a MIDDLE destination"); - } - } - - /** - * Recalculates the dimensions for all elements that require manual - * calculations. Also updates the dimension caches. - *

- * Note: This method has the side-effect - * automatically makes sure that an appropriate amount of escalator rows are - * present. So, if the body area grows, more escalator rows might be - * inserted. Conversely, if the body area shrinks, - * escalator rows might be removed. - */ - private void recalculateElementSizes() { - if (!isAttached()) { - return; - } - - Profiler.enter("Escalator.recalculateElementSizes"); - widthOfEscalator = Math.max(0, WidgetUtil - .getRequiredWidthBoundingClientRectDouble(getElement())); - heightOfEscalator = Math.max(0, WidgetUtil - .getRequiredHeightBoundingClientRectDouble(getElement())); - - header.recalculateSectionHeight(); - body.recalculateSectionHeight(); - footer.recalculateSectionHeight(); - - scroller.recalculateScrollbarsForVirtualViewport(); - body.verifyEscalatorCount(); - body.reapplySpacerWidths(); - Profiler.leave("Escalator.recalculateElementSizes"); - } - - /** - * Snap deltas of x and y to the major four axes (up, down, left, right) - * with a threshold of a number of degrees from those axes. - * - * @param deltaX - * the delta in the x axis - * @param deltaY - * the delta in the y axis - * @param thresholdRatio - * the threshold in ratio (0..1) between x and y for when to snap - * @return a two-element array: [snappedX, snappedY] - */ - private static double[] snapDeltas(final double deltaX, - final double deltaY, final double thresholdRatio) { - - final double[] array = new double[2]; - if (deltaX != 0 && deltaY != 0) { - final double aDeltaX = Math.abs(deltaX); - final double aDeltaY = Math.abs(deltaY); - final double yRatio = aDeltaY / aDeltaX; - final double xRatio = aDeltaX / aDeltaY; - - array[0] = (xRatio < thresholdRatio) ? 0 : deltaX; - array[1] = (yRatio < thresholdRatio) ? 0 : deltaY; - } else { - array[0] = deltaX; - array[1] = deltaY; - } - - return array; - } - - /** - * Adds an event handler that gets notified when the range of visible rows - * changes e.g. because of scrolling, row resizing or spacers - * appearing/disappearing. - * - * @param rowVisibilityChangeHandler - * the event handler - * @return a handler registration for the added handler - */ - public HandlerRegistration addRowVisibilityChangeHandler( - RowVisibilityChangeHandler rowVisibilityChangeHandler) { - return addHandler(rowVisibilityChangeHandler, - RowVisibilityChangeEvent.TYPE); - } - - private void fireRowVisibilityChangeEvent() { - if (!body.visualRowOrder.isEmpty()) { - int visibleRangeStart = body.getLogicalRowIndex(body.visualRowOrder - .getFirst()); - int visibleRangeEnd = body.getLogicalRowIndex(body.visualRowOrder - .getLast()) + 1; - - int visibleRowCount = visibleRangeEnd - visibleRangeStart; - fireEvent(new RowVisibilityChangeEvent(visibleRangeStart, - visibleRowCount)); - } else { - fireEvent(new RowVisibilityChangeEvent(0, 0)); - } - } - - /** - * Gets the logical index range of currently visible rows. - * - * @return logical index range of visible rows - */ - public Range getVisibleRowRange() { - if (!body.visualRowOrder.isEmpty()) { - return Range.withLength(body.getTopRowLogicalIndex(), - body.visualRowOrder.size()); - } else { - return Range.withLength(0, 0); - } - } - - /** - * Returns the widget from a cell node or null if there is no - * widget in the cell - * - * @param cellNode - * The cell node - */ - static Widget getWidgetFromCell(Node cellNode) { - Node possibleWidgetNode = cellNode.getFirstChild(); - if (possibleWidgetNode != null - && possibleWidgetNode.getNodeType() == Node.ELEMENT_NODE) { - @SuppressWarnings("deprecation") - com.google.gwt.user.client.Element castElement = (com.google.gwt.user.client.Element) possibleWidgetNode - .cast(); - Widget w = WidgetUtil.findWidget(castElement, null); - - // Ensure findWidget did not traverse past the cell element in the - // DOM hierarchy - if (cellNode.isOrHasChild(w.getElement())) { - return w; - } - } - return null; - } - - @Override - public void setStylePrimaryName(String style) { - super.setStylePrimaryName(style); - - verticalScrollbar.setStylePrimaryName(style); - horizontalScrollbar.setStylePrimaryName(style); - - UIObject.setStylePrimaryName(tableWrapper, style + "-tablewrapper"); - UIObject.setStylePrimaryName(headerDeco, style + "-header-deco"); - UIObject.setStylePrimaryName(footerDeco, style + "-footer-deco"); - UIObject.setStylePrimaryName(horizontalScrollbarDeco, style - + "-horizontal-scrollbar-deco"); - UIObject.setStylePrimaryName(spacerDecoContainer, style - + "-spacer-deco-container"); - - header.setStylePrimaryName(style); - body.setStylePrimaryName(style); - footer.setStylePrimaryName(style); - } - - /** - * Sets the number of rows that should be visible in Escalator's body, while - * {@link #getHeightMode()} is {@link HeightMode#ROW}. - *

- * If Escalator is currently not in {@link HeightMode#ROW}, the given value - * is remembered, and applied once the mode is applied. - * - * @param rows - * the number of rows that should be visible in Escalator's body - * @throws IllegalArgumentException - * if {@code rows} is ≤ 0, - * {@link Double#isInifinite(double) infinite} or - * {@link Double#isNaN(double) NaN}. - * @see #setHeightMode(HeightMode) - */ - public void setHeightByRows(double rows) throws IllegalArgumentException { - if (rows <= 0) { - throw new IllegalArgumentException( - "The number of rows must be a positive number."); - } else if (Double.isInfinite(rows)) { - throw new IllegalArgumentException( - "The number of rows must be finite."); - } else if (Double.isNaN(rows)) { - throw new IllegalArgumentException("The number must not be NaN."); - } - - heightByRows = rows; - applyHeightByRows(); - } - - /** - * Gets the amount of rows in Escalator's body that are shown, while - * {@link #getHeightMode()} is {@link HeightMode#ROW}. - *

- * By default, it is 10. - * - * @return the amount of rows that are being shown in Escalator's body - * @see #setHeightByRows(double) - */ - public double getHeightByRows() { - return heightByRows; - } - - /** - * Reapplies the row-based height of the Grid, if Grid currently should - * define its height that way. - */ - private void applyHeightByRows() { - if (heightMode != HeightMode.ROW) { - return; - } - - double headerHeight = header.getHeightOfSection(); - double footerHeight = footer.getHeightOfSection(); - double bodyHeight = body.getDefaultRowHeight() * heightByRows; - double scrollbar = horizontalScrollbar.showsScrollHandle() ? horizontalScrollbar - .getScrollbarThickness() : 0; - - double totalHeight = headerHeight + bodyHeight + scrollbar - + footerHeight; - setHeightInternal(totalHeight + "px"); - } - - /** - * Defines the mode in which the Escalator widget's height is calculated. - *

- * If {@link HeightMode#CSS} is given, Escalator will respect the values - * given via {@link #setHeight(String)}, and behave as a traditional Widget. - *

- * If {@link HeightMode#ROW} is given, Escalator will make sure that the - * {@link #getBody() body} will display as many rows as - * {@link #getHeightByRows()} defines. Note: If headers/footers are - * inserted or removed, the widget will resize itself to still display the - * required amount of rows in its body. It also takes the horizontal - * scrollbar into account. - * - * @param heightMode - * the mode in to which Escalator should be set - */ - public void setHeightMode(HeightMode heightMode) { - /* - * This method is a workaround for the fact that Vaadin re-applies - * widget dimensions (height/width) on each state change event. The - * original design was to have setHeight an setHeightByRow be equals, - * and whichever was called the latest was considered in effect. - * - * But, because of Vaadin always calling setHeight on the widget, this - * approach doesn't work. - */ - - if (heightMode != this.heightMode) { - this.heightMode = heightMode; - - switch (this.heightMode) { - case CSS: - setHeight(heightByCss); - break; - case ROW: - setHeightByRows(heightByRows); - break; - default: - throw new IllegalStateException("Unimplemented feature " - + "- unknown HeightMode: " + this.heightMode); - } - } - } - - /** - * Returns the current {@link HeightMode} the Escalator is in. - *

- * Defaults to {@link HeightMode#CSS}. - * - * @return the current HeightMode - */ - public HeightMode getHeightMode() { - return heightMode; - } - - /** - * Returns the {@link RowContainer} which contains the element. - * - * @param element - * the element to check for - * @return the container the element is in or null if element - * is not present in any container. - */ - public RowContainer findRowContainer(Element element) { - if (getHeader().getElement() != element - && getHeader().getElement().isOrHasChild(element)) { - return getHeader(); - } else if (getBody().getElement() != element - && getBody().getElement().isOrHasChild(element)) { - return getBody(); - } else if (getFooter().getElement() != element - && getFooter().getElement().isOrHasChild(element)) { - return getFooter(); - } - return null; - } - - /** - * Sets whether a scroll direction is locked or not. - *

- * If a direction is locked, the escalator will refuse to scroll in that - * direction. - * - * @param direction - * the orientation of the scroll to set the lock status - * @param locked - * true to lock, false to unlock - */ - public void setScrollLocked(ScrollbarBundle.Direction direction, - boolean locked) { - switch (direction) { - case HORIZONTAL: - horizontalScrollbar.setLocked(locked); - break; - case VERTICAL: - verticalScrollbar.setLocked(locked); - break; - default: - throw new UnsupportedOperationException("Unexpected value: " - + direction); - } - } - - /** - * Checks whether or not an direction is locked for scrolling. - * - * @param direction - * the direction of the scroll of which to check the lock status - * @return true iff the direction is locked - */ - public boolean isScrollLocked(ScrollbarBundle.Direction direction) { - switch (direction) { - case HORIZONTAL: - return horizontalScrollbar.isLocked(); - case VERTICAL: - return verticalScrollbar.isLocked(); - default: - throw new UnsupportedOperationException("Unexpected value: " - + direction); - } - } - - /** - * Adds a scroll handler to this escalator - * - * @param handler - * the scroll handler to add - * @return a handler registration for the registered scroll handler - */ - public HandlerRegistration addScrollHandler(ScrollHandler handler) { - return addHandler(handler, ScrollEvent.TYPE); - } - - @Override - public boolean isWorkPending() { - return body.domSorter.waiting || verticalScrollbar.isWorkPending() - || horizontalScrollbar.isWorkPending() || layoutIsScheduled; - } - - @Override - public void onResize() { - if (isAttached() && !layoutIsScheduled) { - layoutIsScheduled = true; - Scheduler.get().scheduleFinally(layoutCommand); - } - } - - /** - * Gets the maximum number of body rows that can be visible on the screen at - * once. - * - * @return the maximum capacity - */ - public int getMaxVisibleRowCount() { - return body.getMaxEscalatorRowCapacity(); - } - - /** - * Gets the escalator's inner width. This is the entire width in pixels, - * without the vertical scrollbar. - * - * @return escalator's inner width - */ - public double getInnerWidth() { - return WidgetUtil - .getRequiredWidthBoundingClientRectDouble(tableWrapper); - } - - /** - * Resets all cached pixel sizes and reads new values from the DOM. This - * methods should be used e.g. when styles affecting the dimensions of - * elements in this escalator have been changed. - */ - public void resetSizesFromDom() { - header.autodetectRowHeightNow(); - body.autodetectRowHeightNow(); - footer.autodetectRowHeightNow(); - - for (int i = 0; i < columnConfiguration.getColumnCount(); i++) { - columnConfiguration.setColumnWidth(i, - columnConfiguration.getColumnWidth(i)); - } - } - - private Range getViewportPixels() { - int from = (int) Math.floor(verticalScrollbar.getScrollPos()); - int to = (int) body.getHeightOfSection(); - return Range.withLength(from, to); - } - - @Override - @SuppressWarnings("deprecation") - public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - SubPartArguments args = SubPartArguments.create(subPart); - - Element tableStructureElement = getSubPartElementTableStructure(args); - if (tableStructureElement != null) { - return DOM.asOld(tableStructureElement); - } - - Element spacerElement = getSubPartElementSpacer(args); - if (spacerElement != null) { - return DOM.asOld(spacerElement); - } - - return null; - } - - private Element getSubPartElementTableStructure(SubPartArguments args) { - - String type = args.getType(); - int[] indices = args.getIndices(); - - // Get correct RowContainer for type from Escalator - RowContainer container = null; - if (type.equalsIgnoreCase("header")) { - container = getHeader(); - } else if (type.equalsIgnoreCase("cell")) { - // If wanted row is not visible, we need to scroll there. - Range visibleRowRange = getVisibleRowRange(); - if (indices.length > 0 && !visibleRowRange.contains(indices[0])) { - try { - scrollToRow(indices[0], ScrollDestination.ANY, 0); - } catch (IllegalArgumentException e) { - getLogger().log(Level.SEVERE, e.getMessage()); - } - // Scrolling causes a lazy loading event. No element can - // currently be retrieved. - return null; - } - container = getBody(); - } else if (type.equalsIgnoreCase("footer")) { - container = getFooter(); - } - - if (null != container) { - if (indices.length == 0) { - // No indexing. Just return the wanted container element - return container.getElement(); - } else { - try { - return getSubPart(container, indices); - } catch (Exception e) { - getLogger().log(Level.SEVERE, e.getMessage()); - } - } - } - return null; - } - - private Element getSubPart(RowContainer container, int[] indices) { - Element targetElement = container.getRowElement(indices[0]); - - // Scroll wanted column to view if able - if (indices.length > 1 && targetElement != null) { - if (getColumnConfiguration().getFrozenColumnCount() <= indices[1]) { - scrollToColumn(indices[1], ScrollDestination.ANY, 0); - } - - targetElement = getCellFromRow(TableRowElement.as(targetElement), - indices[1]); - - for (int i = 2; i < indices.length && targetElement != null; ++i) { - targetElement = (Element) targetElement.getChild(indices[i]); - } - } - - return targetElement; - } - - private static Element getCellFromRow(TableRowElement rowElement, int index) { - int childCount = rowElement.getCells().getLength(); - if (index < 0 || index >= childCount) { - return null; - } - - TableCellElement currentCell = null; - boolean indexInColspan = false; - int i = 0; - - while (!indexInColspan) { - currentCell = rowElement.getCells().getItem(i); - - // Calculate if this is the cell we are looking for - int colSpan = currentCell.getColSpan(); - indexInColspan = index < colSpan + i; - - // Increment by colspan to skip over hidden cells - i += colSpan; - } - return currentCell; - } - - private Element getSubPartElementSpacer(SubPartArguments args) { - if ("spacer".equals(args.getType()) && args.getIndicesLength() == 1) { - return body.spacerContainer.getSubPartElement(args.getIndex(0)); - } else { - return null; - } - } - - @Override - @SuppressWarnings("deprecation") - public String getSubPartName(com.google.gwt.user.client.Element subElement) { - - /* - * The spacer check needs to be before table structure check, because - * (for now) the table structure will take spacer elements into account - * as well, when it shouldn't. - */ - - String spacer = getSubPartNameSpacer(subElement); - if (spacer != null) { - return spacer; - } - - String tableStructure = getSubPartNameTableStructure(subElement); - if (tableStructure != null) { - return tableStructure; - } - - return null; - } - - private String getSubPartNameTableStructure(Element subElement) { - - List containers = Arrays.asList(getHeader(), getBody(), - getFooter()); - List containerType = Arrays.asList("header", "cell", "footer"); - - for (int i = 0; i < containers.size(); ++i) { - RowContainer container = containers.get(i); - boolean containerRow = (subElement.getTagName().equalsIgnoreCase( - "tr") && subElement.getParentElement() == container - .getElement()); - if (containerRow) { - /* - * Wanted SubPart is row that is a child of containers root to - * get indices, we use a cell that is a child of this row - */ - subElement = subElement.getFirstChildElement(); - } - - Cell cell = container.getCell(subElement); - if (cell != null) { - // Skip the column index if subElement was a child of root - return containerType.get(i) + "[" + cell.getRow() - + (containerRow ? "]" : "][" + cell.getColumn() + "]"); - } - } - return null; - } - - private String getSubPartNameSpacer(Element subElement) { - return body.spacerContainer.getSubPartName(subElement); - } - - private void logWarning(String message) { - getLogger().warning(message); - } - - /** - * This is an internal method for calculating minimum width for Column - * resize. - * - * @return minimum width for column - */ - double getMinCellWidth(int colIndex) { - return columnConfiguration.getMinCellWidth(colIndex); - } -} diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java deleted file mode 100644 index c4e3491992..0000000000 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ /dev/null @@ -1,8877 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.widgets; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.TreeMap; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.core.shared.GWT; -import com.google.gwt.dom.client.BrowserEvents; -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.EventTarget; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.TableCellElement; -import com.google.gwt.dom.client.TableRowElement; -import com.google.gwt.dom.client.TableSectionElement; -import com.google.gwt.dom.client.Touch; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.KeyEvent; -import com.google.gwt.event.dom.client.MouseEvent; -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.touch.client.Point; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Event.NativePreviewEvent; -import com.google.gwt.user.client.Event.NativePreviewHandler; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.CheckBox; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HasEnabled; -import com.google.gwt.user.client.ui.HasWidgets; -import com.google.gwt.user.client.ui.MenuBar; -import com.google.gwt.user.client.ui.MenuItem; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.ResizeComposite; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.DeferredWorker; -import com.vaadin.client.Focusable; -import com.vaadin.client.WidgetUtil; -import com.vaadin.client.data.DataChangeHandler; -import com.vaadin.client.data.DataSource; -import com.vaadin.client.data.DataSource.RowHandle; -import com.vaadin.client.renderers.ComplexRenderer; -import com.vaadin.client.renderers.Renderer; -import com.vaadin.client.renderers.WidgetRenderer; -import com.vaadin.client.ui.FocusUtil; -import com.vaadin.client.ui.SubPartAware; -import com.vaadin.client.ui.dd.DragAndDropHandler; -import com.vaadin.client.ui.dd.DragAndDropHandler.DragAndDropCallback; -import com.vaadin.client.ui.dd.DragHandle; -import com.vaadin.client.ui.dd.DragHandle.DragHandleCallback; -import com.vaadin.client.widget.escalator.Cell; -import com.vaadin.client.widget.escalator.ColumnConfiguration; -import com.vaadin.client.widget.escalator.EscalatorUpdater; -import com.vaadin.client.widget.escalator.FlyweightCell; -import com.vaadin.client.widget.escalator.Row; -import com.vaadin.client.widget.escalator.RowContainer; -import com.vaadin.client.widget.escalator.RowVisibilityChangeEvent; -import com.vaadin.client.widget.escalator.RowVisibilityChangeHandler; -import com.vaadin.client.widget.escalator.ScrollbarBundle.Direction; -import com.vaadin.client.widget.escalator.Spacer; -import com.vaadin.client.widget.escalator.SpacerUpdater; -import com.vaadin.client.widget.grid.AutoScroller; -import com.vaadin.client.widget.grid.AutoScroller.AutoScrollerCallback; -import com.vaadin.client.widget.grid.AutoScroller.ScrollAxis; -import com.vaadin.client.widget.grid.CellReference; -import com.vaadin.client.widget.grid.CellStyleGenerator; -import com.vaadin.client.widget.grid.DataAvailableEvent; -import com.vaadin.client.widget.grid.DataAvailableHandler; -import com.vaadin.client.widget.grid.DefaultEditorEventHandler; -import com.vaadin.client.widget.grid.DetailsGenerator; -import com.vaadin.client.widget.grid.EditorHandler; -import com.vaadin.client.widget.grid.EditorHandler.EditorRequest; -import com.vaadin.client.widget.grid.EventCellReference; -import com.vaadin.client.widget.grid.HeightAwareDetailsGenerator; -import com.vaadin.client.widget.grid.RendererCellReference; -import com.vaadin.client.widget.grid.RowReference; -import com.vaadin.client.widget.grid.RowStyleGenerator; -import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler; -import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler; -import com.vaadin.client.widget.grid.events.BodyClickHandler; -import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler; -import com.vaadin.client.widget.grid.events.BodyKeyDownHandler; -import com.vaadin.client.widget.grid.events.BodyKeyPressHandler; -import com.vaadin.client.widget.grid.events.BodyKeyUpHandler; -import com.vaadin.client.widget.grid.events.ColumnReorderEvent; -import com.vaadin.client.widget.grid.events.ColumnReorderHandler; -import com.vaadin.client.widget.grid.events.ColumnResizeEvent; -import com.vaadin.client.widget.grid.events.ColumnResizeHandler; -import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent; -import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler; -import com.vaadin.client.widget.grid.events.FooterClickHandler; -import com.vaadin.client.widget.grid.events.FooterDoubleClickHandler; -import com.vaadin.client.widget.grid.events.FooterKeyDownHandler; -import com.vaadin.client.widget.grid.events.FooterKeyPressHandler; -import com.vaadin.client.widget.grid.events.FooterKeyUpHandler; -import com.vaadin.client.widget.grid.events.GridClickEvent; -import com.vaadin.client.widget.grid.events.GridDoubleClickEvent; -import com.vaadin.client.widget.grid.events.GridKeyDownEvent; -import com.vaadin.client.widget.grid.events.GridKeyPressEvent; -import com.vaadin.client.widget.grid.events.GridKeyUpEvent; -import com.vaadin.client.widget.grid.events.HeaderClickHandler; -import com.vaadin.client.widget.grid.events.HeaderDoubleClickHandler; -import com.vaadin.client.widget.grid.events.HeaderKeyDownHandler; -import com.vaadin.client.widget.grid.events.HeaderKeyPressHandler; -import com.vaadin.client.widget.grid.events.HeaderKeyUpHandler; -import com.vaadin.client.widget.grid.events.ScrollEvent; -import com.vaadin.client.widget.grid.events.ScrollHandler; -import com.vaadin.client.widget.grid.events.SelectAllEvent; -import com.vaadin.client.widget.grid.events.SelectAllHandler; -import com.vaadin.client.widget.grid.selection.HasSelectionHandlers; -import com.vaadin.client.widget.grid.selection.MultiSelectionRenderer; -import com.vaadin.client.widget.grid.selection.SelectionEvent; -import com.vaadin.client.widget.grid.selection.SelectionHandler; -import com.vaadin.client.widget.grid.selection.SelectionModel; -import com.vaadin.client.widget.grid.selection.SelectionModel.Multi; -import com.vaadin.client.widget.grid.selection.SelectionModel.Single; -import com.vaadin.client.widget.grid.selection.SelectionModelMulti; -import com.vaadin.client.widget.grid.selection.SelectionModelNone; -import com.vaadin.client.widget.grid.selection.SelectionModelSingle; -import com.vaadin.client.widget.grid.sort.Sort; -import com.vaadin.client.widget.grid.sort.SortEvent; -import com.vaadin.client.widget.grid.sort.SortHandler; -import com.vaadin.client.widget.grid.sort.SortOrder; -import com.vaadin.client.widgets.Escalator.AbstractRowContainer; -import com.vaadin.client.widgets.Escalator.SubPartArguments; -import com.vaadin.client.widgets.Grid.Editor.State; -import com.vaadin.client.widgets.Grid.StaticSection.StaticCell; -import com.vaadin.client.widgets.Grid.StaticSection.StaticRow; -import com.vaadin.shared.data.sort.SortDirection; -import com.vaadin.shared.ui.grid.GridConstants; -import com.vaadin.shared.ui.grid.GridConstants.Section; -import com.vaadin.shared.ui.grid.GridStaticCellType; -import com.vaadin.shared.ui.grid.HeightMode; -import com.vaadin.shared.ui.grid.Range; -import com.vaadin.shared.ui.grid.ScrollDestination; -import com.vaadin.shared.util.SharedUtil; - -/** - * A data grid view that supports columns and lazy loading of data rows from a - * data source. - * - *

Columns

- *

- * Each column in Grid is represented by a {@link Column}. Each - * {@code GridColumn} has a custom implementation for - * {@link Column#getValue(Object)} that gets the row object as an argument, and - * returns the value for that particular column, extracted from the row object. - *

- * Each column also has a Renderer. Its function is to take the value that is - * given by the {@code GridColumn} and display it to the user. A simple column - * might have a {@link com.vaadin.client.renderers.TextRenderer TextRenderer} - * that simply takes in a {@code String} and displays it as the cell's content. - * A more complex renderer might be - * {@link com.vaadin.client.renderers.ProgressBarRenderer ProgressBarRenderer} - * that takes in a floating point number, and displays a progress bar instead, - * based on the given number. - *

- * See: {@link #addColumn(Column)}, {@link #addColumn(Column, int)} and - * {@link #addColumns(Column...)}. Also - * {@link Column#setRenderer(Renderer)}. - * - *

Data Sources

- *

- * Grid gets its data from a {@link DataSource}, providing row objects to Grid - * from a user-defined endpoint. It can be either a local in-memory data source - * (e.g. {@link com.vaadin.client.widget.grid.datasources.ListDataSource - * ListDataSource}) or even a remote one, retrieving data from e.g. a REST API - * (see {@link com.vaadin.client.data.AbstractRemoteDataSource - * AbstractRemoteDataSource}). - * - * - * @param - * The row type of the grid. The row type is the POJO type from where - * the data is retrieved into the column cells. - * @since 7.4 - * @author Vaadin Ltd - */ -public class Grid extends ResizeComposite implements - HasSelectionHandlers, SubPartAware, DeferredWorker, Focusable, - com.google.gwt.user.client.ui.Focusable, HasWidgets, HasEnabled { - - private static final String STYLE_NAME = "v-grid"; - - private static final String SELECT_ALL_CHECKBOX_CLASSNAME = "-select-all-checkbox"; - - /** - * Abstract base class for Grid header and footer sections. - * - * @since 7.5.0 - * - * @param - * the type of the rows in the section - */ - public abstract static class StaticSection> { - - /** - * A header or footer cell. Has a simple textual caption. - * - */ - public static class StaticCell { - - private Object content = null; - - private int colspan = 1; - - private StaticSection section; - - private GridStaticCellType type = GridStaticCellType.TEXT; - - private String styleName = null; - - /** - * Sets the text displayed in this cell. - * - * @param text - * a plain text caption - */ - public void setText(String text) { - this.content = text; - this.type = GridStaticCellType.TEXT; - section.requestSectionRefresh(); - } - - /** - * Returns the text displayed in this cell. - * - * @return the plain text caption - */ - public String getText() { - if (type != GridStaticCellType.TEXT) { - throw new IllegalStateException( - "Cannot fetch Text from a cell with type " + type); - } - return (String) content; - } - - protected StaticSection getSection() { - assert section != null; - return section; - } - - protected void setSection(StaticSection section) { - this.section = section; - } - - /** - * Returns the amount of columns the cell spans. By default is 1. - * - * @return The amount of columns the cell spans. - */ - public int getColspan() { - return colspan; - } - - /** - * Sets the amount of columns the cell spans. Must be more or equal - * to 1. By default is 1. - * - * @param colspan - * the colspan to set - */ - public void setColspan(int colspan) { - if (colspan < 1) { - throw new IllegalArgumentException( - "Colspan cannot be less than 1"); - } - - this.colspan = colspan; - section.requestSectionRefresh(); - } - - /** - * Returns the html inside the cell. - * - * @throws IllegalStateException - * if trying to retrive HTML from a cell with a type - * other than {@link GridStaticCellType#HTML}. - * @return the html content of the cell. - */ - public String getHtml() { - if (type != GridStaticCellType.HTML) { - throw new IllegalStateException( - "Cannot fetch HTML from a cell with type " + type); - } - return (String) content; - } - - /** - * Sets the content of the cell to the provided html. All previous - * content is discarded and the cell type is set to - * {@link GridStaticCellType#HTML}. - * - * @param html - * The html content of the cell - */ - public void setHtml(String html) { - this.content = html; - this.type = GridStaticCellType.HTML; - section.requestSectionRefresh(); - } - - /** - * Returns the widget in the cell. - * - * @throws IllegalStateException - * if the cell is not {@link GridStaticCellType#WIDGET} - * - * @return the widget in the cell - */ - public Widget getWidget() { - if (type != GridStaticCellType.WIDGET) { - throw new IllegalStateException( - "Cannot fetch Widget from a cell with type " + type); - } - return (Widget) content; - } - - /** - * Set widget as the content of the cell. The type of the cell - * becomes {@link GridStaticCellType#WIDGET}. All previous content - * is discarded. - * - * @param widget - * The widget to add to the cell. Should not be - * previously attached anywhere (widget.getParent == - * null). - */ - public void setWidget(Widget widget) { - if (this.content == widget) { - return; - } - - if (this.content instanceof Widget) { - // Old widget in the cell, detach it first - section.getGrid().detachWidget((Widget) this.content); - } - this.content = widget; - this.type = GridStaticCellType.WIDGET; - section.requestSectionRefresh(); - } - - /** - * Returns the type of the cell. - * - * @return the type of content the cell contains. - */ - public GridStaticCellType getType() { - return type; - } - - /** - * Returns the custom style name for this cell. - * - * @return the style name or null if no style name has been set - */ - public String getStyleName() { - return styleName; - } - - /** - * Sets a custom style name for this cell. - * - * @param styleName - * the style name to set or null to not use any style - * name - */ - public void setStyleName(String styleName) { - this.styleName = styleName; - section.requestSectionRefresh(); - - } - - /** - * Called when the cell is detached from the row - * - * @since 7.6.3 - */ - void detach() { - if (this.content instanceof Widget) { - // Widget in the cell, detach it - section.getGrid().detachWidget((Widget) this.content); - } - } - } - - /** - * Abstract base class for Grid header and footer rows. - * - * @param - * the type of the cells in the row - */ - public abstract static class StaticRow { - - private Map, CELLTYPE> cells = new HashMap, CELLTYPE>(); - - private StaticSection section; - - /** - * Map from set of spanned columns to cell meta data. - */ - private Map>, CELLTYPE> cellGroups = new HashMap>, CELLTYPE>(); - - /** - * A custom style name for the row or null if none is set. - */ - private String styleName = null; - - /** - * Returns the cell on given GridColumn. If the column is merged - * returned cell is the cell for the whole group. - * - * @param column - * the column in grid - * @return the cell on given column, merged cell for merged columns, - * null if not found - */ - public CELLTYPE getCell(Column column) { - Set> cellGroup = getCellGroupForColumn(column); - if (cellGroup != null) { - return cellGroups.get(cellGroup); - } - return cells.get(column); - } - - /** - * Returns true if this row contains spanned cells. - * - * @since 7.5.0 - * @return does this row contain spanned cells - */ - public boolean hasSpannedCells() { - return !cellGroups.isEmpty(); - } - - /** - * Merges columns cells in a row - * - * @param columns - * the columns which header should be merged - * @return the remaining visible cell after the merge, or the cell - * on first column if all are hidden - */ - public CELLTYPE join(Column... columns) { - if (columns.length <= 1) { - throw new IllegalArgumentException( - "You can't merge less than 2 columns together."); - } - - HashSet> columnGroup = new HashSet>(); - // NOTE: this doesn't care about hidden columns, those are - // filtered in calculateColspans() - for (Column column : columns) { - if (!cells.containsKey(column)) { - throw new IllegalArgumentException( - "Given column does not exists on row " + column); - } else if (getCellGroupForColumn(column) != null) { - throw new IllegalStateException( - "Column is already in a group."); - } - columnGroup.add(column); - } - - CELLTYPE joinedCell = createCell(); - cellGroups.put(columnGroup, joinedCell); - joinedCell.setSection(getSection()); - - calculateColspans(); - - return joinedCell; - } - - /** - * Merges columns cells in a row - * - * @param cells - * The cells to merge. Must be from the same row. - * @return The remaining visible cell after the merge, or the first - * cell if all columns are hidden - */ - public CELLTYPE join(CELLTYPE... cells) { - if (cells.length <= 1) { - throw new IllegalArgumentException( - "You can't merge less than 2 cells together."); - } - - Column[] columns = new Column[cells.length]; - - int j = 0; - for (Column column : this.cells.keySet()) { - CELLTYPE cell = this.cells.get(column); - if (!this.cells.containsValue(cells[j])) { - throw new IllegalArgumentException( - "Given cell does not exists on row"); - } else if (cell.equals(cells[j])) { - columns[j++] = column; - if (j == cells.length) { - break; - } - } - } - - return join(columns); - } - - private Set> getCellGroupForColumn(Column column) { - for (Set> group : cellGroups.keySet()) { - if (group.contains(column)) { - return group; - } - } - return null; - } - - void calculateColspans() { - // Reset all cells - for (CELLTYPE cell : this.cells.values()) { - cell.setColspan(1); - } - // Set colspan for grouped cells - for (Set> group : cellGroups.keySet()) { - if (!checkMergedCellIsContinuous(group)) { - // on error simply break the merged cell - cellGroups.get(group).setColspan(1); - } else { - int colSpan = 0; - for (Column column : group) { - if (!column.isHidden()) { - colSpan++; - } - } - // colspan can't be 0 - cellGroups.get(group).setColspan(Math.max(1, colSpan)); - } - } - - } - - private boolean checkMergedCellIsContinuous( - Set> mergedCell) { - // no matter if hidden or not, just check for continuous order - final List> columnOrder = new ArrayList>( - section.grid.getColumns()); - - if (!columnOrder.containsAll(mergedCell)) { - return false; - } - - for (int i = 0; i < columnOrder.size(); ++i) { - if (!mergedCell.contains(columnOrder.get(i))) { - continue; - } - - for (int j = 1; j < mergedCell.size(); ++j) { - if (!mergedCell.contains(columnOrder.get(i + j))) { - return false; - } - } - return true; - } - return false; - } - - protected void addCell(Column column) { - CELLTYPE cell = createCell(); - cell.setSection(getSection()); - cells.put(column, cell); - } - - protected void removeCell(Column column) { - cells.remove(column); - } - - protected abstract CELLTYPE createCell(); - - protected StaticSection getSection() { - return section; - } - - protected void setSection(StaticSection section) { - this.section = section; - } - - /** - * Returns the custom style name for this row. - * - * @return the style name or null if no style name has been set - */ - public String getStyleName() { - return styleName; - } - - /** - * Sets a custom style name for this row. - * - * @param styleName - * the style name to set or null to not use any style - * name - */ - public void setStyleName(String styleName) { - this.styleName = styleName; - section.requestSectionRefresh(); - } - - /** - * Called when the row is detached from the grid - * - * @since 7.6.3 - */ - void detach() { - // Avoid calling detach twice for a merged cell - HashSet cells = new HashSet(); - for (Column column : getSection().grid.getColumns()) { - cells.add(getCell(column)); - } - for (CELLTYPE cell : cells) { - cell.detach(); - } - } - } - - private Grid grid; - - private List rows = new ArrayList(); - - private boolean visible = true; - - /** - * Creates and returns a new instance of the row type. - * - * @return the created row - */ - protected abstract ROWTYPE createRow(); - - /** - * Informs the grid that this section should be re-rendered. - *

- * Note that re-render means calling update() on each cell, - * preAttach()/postAttach()/preDetach()/postDetach() is not called as - * the cells are not removed from the DOM. - */ - protected abstract void requestSectionRefresh(); - - /** - * Sets the visibility of the whole section. - * - * @param visible - * true to show this section, false to hide - */ - public void setVisible(boolean visible) { - this.visible = visible; - requestSectionRefresh(); - } - - /** - * Returns the visibility of this section. - * - * @return true if visible, false otherwise. - */ - public boolean isVisible() { - return visible; - } - - /** - * Inserts a new row at the given position. Shifts the row currently at - * that position and any subsequent rows down (adds one to their - * indices). - * - * @param index - * the position at which to insert the row - * @return the new row - * - * @throws IndexOutOfBoundsException - * if the index is out of bounds - * @see #appendRow() - * @see #prependRow() - * @see #removeRow(int) - * @see #removeRow(StaticRow) - */ - public ROWTYPE addRowAt(int index) { - ROWTYPE row = createRow(); - row.setSection(this); - for (int i = 0; i < getGrid().getColumnCount(); ++i) { - row.addCell(grid.getColumn(i)); - } - rows.add(index, row); - - requestSectionRefresh(); - return row; - } - - /** - * Adds a new row at the top of this section. - * - * @return the new row - * @see #appendRow() - * @see #addRowAt(int) - * @see #removeRow(int) - * @see #removeRow(StaticRow) - */ - public ROWTYPE prependRow() { - return addRowAt(0); - } - - /** - * Adds a new row at the bottom of this section. - * - * @return the new row - * @see #prependRow() - * @see #addRowAt(int) - * @see #removeRow(int) - * @see #removeRow(StaticRow) - */ - public ROWTYPE appendRow() { - return addRowAt(rows.size()); - } - - /** - * Removes the row at the given position. - * - * @param index - * the position of the row - * - * @throws IndexOutOfBoundsException - * if the index is out of bounds - * @see #addRowAt(int) - * @see #appendRow() - * @see #prependRow() - * @see #removeRow(StaticRow) - */ - public void removeRow(int index) { - ROWTYPE row = rows.remove(index); - row.detach(); - requestSectionRefresh(); - } - - /** - * Removes the given row from the section. - * - * @param row - * the row to be removed - * - * @throws IllegalArgumentException - * if the row does not exist in this section - * @see #addRowAt(int) - * @see #appendRow() - * @see #prependRow() - * @see #removeRow(int) - */ - public void removeRow(ROWTYPE row) { - try { - removeRow(rows.indexOf(row)); - } catch (IndexOutOfBoundsException e) { - throw new IllegalArgumentException( - "Section does not contain the given row"); - } - } - - /** - * Returns the row at the given position. - * - * @param index - * the position of the row - * @return the row with the given index - * - * @throws IndexOutOfBoundsException - * if the index is out of bounds - */ - public ROWTYPE getRow(int index) { - try { - return rows.get(index); - } catch (IndexOutOfBoundsException e) { - throw new IllegalArgumentException("Row with index " + index - + " does not exist"); - } - } - - /** - * Returns the number of rows in this section. - * - * @return the number of rows - */ - public int getRowCount() { - return rows.size(); - } - - protected List getRows() { - return rows; - } - - protected int getVisibleRowCount() { - return isVisible() ? getRowCount() : 0; - } - - protected void addColumn(Column column) { - for (ROWTYPE row : rows) { - row.addCell(column); - } - } - - protected void removeColumn(Column column) { - for (ROWTYPE row : rows) { - row.removeCell(column); - } - } - - protected void setGrid(Grid grid) { - this.grid = grid; - } - - protected Grid getGrid() { - assert grid != null; - return grid; - } - - protected void updateColSpans() { - for (ROWTYPE row : rows) { - if (row.hasSpannedCells()) { - row.calculateColspans(); - } - } - } - } - - /** - * Represents the header section of a Grid. A header consists of a single - * header row containing a header cell for each column. Each cell has a - * simple textual caption. - */ - protected static class Header extends StaticSection { - private HeaderRow defaultRow; - - private boolean markAsDirty = false; - - @Override - public void removeRow(int index) { - HeaderRow removedRow = getRow(index); - super.removeRow(index); - if (removedRow == defaultRow) { - setDefaultRow(null); - } - } - - /** - * Sets the default row of this header. The default row is a special - * header row providing a user interface for sorting columns. - * - * @param row - * the new default row, or null for no default row - * - * @throws IllegalArgumentException - * this header does not contain the row - */ - public void setDefaultRow(HeaderRow row) { - if (row == defaultRow) { - return; - } - if (row != null && !getRows().contains(row)) { - throw new IllegalArgumentException( - "Cannot set a default row that does not exist in the container"); - } - if (defaultRow != null) { - defaultRow.setDefault(false); - } - if (row != null) { - row.setDefault(true); - } - - defaultRow = row; - requestSectionRefresh(); - } - - /** - * Returns the current default row of this header. The default row is a - * special header row providing a user interface for sorting columns. - * - * @return the default row or null if no default row set - */ - public HeaderRow getDefaultRow() { - return defaultRow; - } - - @Override - protected HeaderRow createRow() { - return new HeaderRow(); - } - - @Override - protected void requestSectionRefresh() { - markAsDirty = true; - - /* - * Defer the refresh so if we multiple times call refreshSection() - * (for example when updating cell values) we only get one actual - * refresh in the end. - */ - Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() { - - @Override - public void execute() { - if (markAsDirty) { - markAsDirty = false; - getGrid().refreshHeader(); - } - } - }); - } - - /** - * Returns the events consumed by the header - * - * @return a collection of BrowserEvents - */ - public Collection getConsumedEvents() { - return Arrays.asList(BrowserEvents.TOUCHSTART, - BrowserEvents.TOUCHMOVE, BrowserEvents.TOUCHEND, - BrowserEvents.TOUCHCANCEL, BrowserEvents.CLICK); - } - - @Override - protected void addColumn(Column column) { - super.addColumn(column); - - // Add default content for new columns. - if (defaultRow != null) { - column.setDefaultHeaderContent(defaultRow.getCell(column)); - } - } - } - - /** - * A single row in a grid header section. - * - */ - public static class HeaderRow extends StaticSection.StaticRow { - - private boolean isDefault = false; - - protected void setDefault(boolean isDefault) { - this.isDefault = isDefault; - if (isDefault) { - for (Column column : getSection().grid.getColumns()) { - column.setDefaultHeaderContent(getCell(column)); - } - } - } - - public boolean isDefault() { - return isDefault; - } - - @Override - protected HeaderCell createCell() { - return new HeaderCell(); - } - } - - /** - * A single cell in a grid header row. Has a caption and, if it's in a - * default row, a drag handle. - */ - public static class HeaderCell extends StaticSection.StaticCell { - } - - /** - * Represents the footer section of a Grid. The footer is always empty. - */ - protected static class Footer extends StaticSection { - private boolean markAsDirty = false; - - @Override - protected FooterRow createRow() { - return new FooterRow(); - } - - @Override - protected void requestSectionRefresh() { - markAsDirty = true; - - /* - * Defer the refresh so if we multiple times call refreshSection() - * (for example when updating cell values) we only get one actual - * refresh in the end. - */ - Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() { - - @Override - public void execute() { - if (markAsDirty) { - markAsDirty = false; - getGrid().refreshFooter(); - } - } - }); - } - } - - /** - * A single cell in a grid Footer row. Has a textual caption. - * - */ - public static class FooterCell extends StaticSection.StaticCell { - } - - /** - * A single row in a grid Footer section. - * - */ - public static class FooterRow extends StaticSection.StaticRow { - - @Override - protected FooterCell createCell() { - return new FooterCell(); - } - } - - private static class EditorRequestImpl implements EditorRequest { - - /** - * A callback interface used to notify the invoker of the editor handler - * of completed editor requests. - * - * @param - * the row data type - */ - public static interface RequestCallback { - /** - * The method that must be called when the request has been - * processed correctly. - * - * @param request - * the original request object - */ - public void onSuccess(EditorRequest request); - - /** - * The method that must be called when processing the request has - * produced an aborting error. - * - * @param request - * the original request object - */ - public void onError(EditorRequest request); - } - - private Grid grid; - private final int rowIndex; - private final int columnIndex; - private RequestCallback callback; - private boolean completed = false; - - public EditorRequestImpl(Grid grid, int rowIndex, int columnIndex, - RequestCallback callback) { - this.grid = grid; - this.rowIndex = rowIndex; - this.columnIndex = columnIndex; - this.callback = callback; - } - - @Override - public int getRowIndex() { - return rowIndex; - } - - @Override - public int getColumnIndex() { - return columnIndex; - } - - @Override - public T getRow() { - return grid.getDataSource().getRow(rowIndex); - } - - @Override - public Grid getGrid() { - return grid; - } - - @Override - public Widget getWidget(Grid.Column column) { - Widget w = grid.getEditorWidget(column); - assert w != null; - return w; - } - - private void complete(String errorMessage, - Collection> errorColumns) { - if (completed) { - throw new IllegalStateException( - "An EditorRequest must be completed exactly once"); - } - completed = true; - - if (errorColumns == null) { - errorColumns = Collections.emptySet(); - } - grid.getEditor().setEditorError(errorMessage, errorColumns); - } - - @Override - public void success() { - complete(null, null); - if (callback != null) { - callback.onSuccess(this); - } - } - - @Override - public void failure(String errorMessage, - Collection> errorColumns) { - complete(errorMessage, errorColumns); - if (callback != null) { - callback.onError(this); - } - } - - @Override - public boolean isCompleted() { - return completed; - } - } - - /** - * A wrapper for native DOM events originating from Grid. In addition to the - * native event, contains a {@link CellReference} instance specifying which - * cell the event originated from. - * - * @since 7.6 - * @param - * The row type of the grid - */ - public static class GridEvent { - private Event event; - private EventCellReference cell; - - protected GridEvent(Event event, EventCellReference cell) { - this.event = event; - this.cell = cell; - } - - /** - * Returns the wrapped DOM event. - * - * @return the DOM event - */ - public Event getDomEvent() { - return event; - } - - /** - * Returns the Grid cell this event originated from. - * - * @return the event cell - */ - public EventCellReference getCell() { - return cell; - } - - /** - * Returns the Grid instance this event originated from. - * - * @return the grid - */ - public Grid getGrid() { - return cell.getGrid(); - } - } - - /** - * A wrapper for native DOM events related to the {@link Editor Grid editor} - * . - * - * @since 7.6 - * @param - * the row type of the grid - */ - public static class EditorDomEvent extends GridEvent { - - private final Widget editorWidget; - - protected EditorDomEvent(Event event, EventCellReference cell, - Widget editorWidget) { - super(event, cell); - this.editorWidget = editorWidget; - } - - /** - * Returns the editor of the Grid this event originated from. - * - * @return the related editor instance - */ - public Editor getEditor() { - return getGrid().getEditor(); - } - - /** - * Returns the currently focused editor widget. - * - * @return the focused editor widget or {@code null} if not editable - */ - public Widget getEditorWidget() { - return editorWidget; - } - - /** - * Returns the row index the editor is open at. If the editor is not - * open, returns -1. - * - * @return the index of the edited row or -1 if editor is not open - */ - public int getRowIndex() { - return getEditor().rowIndex; - } - - /** - * Returns the column index the editor was opened at. If the editor is - * not open, returns -1. - * - * @return the column index or -1 if editor is not open - */ - public int getFocusedColumnIndex() { - return getEditor().focusedColumnIndex; - } - } - - /** - * An editor UI for Grid rows. A single Grid row at a time can be opened for - * editing. - * - * @since 7.6 - * @param - * the row type of the grid - */ - public static class Editor implements DeferredWorker { - - public static final int KEYCODE_SHOW = KeyCodes.KEY_ENTER; - public static final int KEYCODE_HIDE = KeyCodes.KEY_ESCAPE; - - private static final String ERROR_CLASS_NAME = "error"; - private static final String NOT_EDITABLE_CLASS_NAME = "not-editable"; - - ScheduledCommand fieldFocusCommand = new ScheduledCommand() { - private int count = 0; - - @Override - public void execute() { - Element focusedElement = WidgetUtil.getFocusedElement(); - if (focusedElement == grid.getElement() - || focusedElement == Document.get().getBody() - || count > 2) { - focusColumn(focusedColumnIndex); - } else { - ++count; - Scheduler.get().scheduleDeferred(this); - } - } - }; - - /** - * A handler for events related to the Grid editor. Responsible for - * opening, moving or closing the editor based on the received event. - * - * @since 7.6 - * @author Vaadin Ltd - * @param - * the row type of the grid - */ - public interface EventHandler { - /** - * Handles editor-related events in an appropriate way. Opens, - * moves, or closes the editor based on the given event. - * - * @param event - * the received event - * @return true if the event was handled and nothing else should be - * done, false otherwise - */ - boolean handleEvent(EditorDomEvent event); - } - - protected enum State { - INACTIVE, ACTIVATING, BINDING, ACTIVE, SAVING - } - - private Grid grid; - private EditorHandler handler; - private EventHandler eventHandler = GWT - .create(DefaultEditorEventHandler.class); - - private DivElement editorOverlay = DivElement.as(DOM.createDiv()); - private DivElement cellWrapper = DivElement.as(DOM.createDiv()); - private DivElement frozenCellWrapper = DivElement.as(DOM.createDiv()); - - private DivElement messageAndButtonsWrapper = DivElement.as(DOM - .createDiv()); - - private DivElement messageWrapper = DivElement.as(DOM.createDiv()); - private DivElement buttonsWrapper = DivElement.as(DOM.createDiv()); - - // Element which contains the error message for the editor - // Should only be added to the DOM when there's a message to show - private DivElement message = DivElement.as(DOM.createDiv()); - - private Map, Widget> columnToWidget = new HashMap, Widget>(); - private List focusHandlers = new ArrayList(); - - private boolean enabled = false; - private State state = State.INACTIVE; - private int rowIndex = -1; - private int focusedColumnIndex = -1; - private String styleName = null; - - private HandlerRegistration hScrollHandler; - private HandlerRegistration vScrollHandler; - - private final Button saveButton; - private final Button cancelButton; - - private static final int SAVE_TIMEOUT_MS = 5000; - private final Timer saveTimeout = new Timer() { - @Override - public void run() { - getLogger().warning( - "Editor save action is taking longer than expected (" - + SAVE_TIMEOUT_MS + "ms). Does your " - + EditorHandler.class.getSimpleName() - + " remember to call success() or fail()?"); - } - }; - - private final EditorRequestImpl.RequestCallback saveRequestCallback = new EditorRequestImpl.RequestCallback() { - @Override - public void onSuccess(EditorRequest request) { - if (state == State.SAVING) { - cleanup(); - cancel(); - grid.clearSortOrder(); - } - } - - @Override - public void onError(EditorRequest request) { - if (state == State.SAVING) { - cleanup(); - } - } - - private void cleanup() { - state = State.ACTIVE; - setButtonsEnabled(true); - saveTimeout.cancel(); - } - }; - - private static final int BIND_TIMEOUT_MS = 5000; - private final Timer bindTimeout = new Timer() { - @Override - public void run() { - getLogger().warning( - "Editor bind action is taking longer than expected (" - + BIND_TIMEOUT_MS + "ms). Does your " - + EditorHandler.class.getSimpleName() - + " remember to call success() or fail()?"); - } - }; - - private final EditorRequestImpl.RequestCallback bindRequestCallback = new EditorRequestImpl.RequestCallback() { - @Override - public void onSuccess(EditorRequest request) { - if (state == State.BINDING) { - state = State.ACTIVE; - bindTimeout.cancel(); - - rowIndex = request.getRowIndex(); - focusedColumnIndex = request.getColumnIndex(); - if (focusedColumnIndex >= 0) { - // Update internal focus of Grid - grid.focusCell(rowIndex, focusedColumnIndex); - } - - showOverlay(); - } - } - - @Override - public void onError(EditorRequest request) { - if (state == State.BINDING) { - if (rowIndex == -1) { - doCancel(); - } else { - state = State.ACTIVE; - // TODO: Maybe restore focus? - } - bindTimeout.cancel(); - } - } - }; - - /** A set of all the columns that display an error flag. */ - private final Set> columnErrors = new HashSet>(); - private boolean buffered = true; - - /** Original position of editor */ - private double originalTop; - /** Original scroll position of grid when editor was opened */ - private double originalScrollTop; - private RowHandle pinnedRowHandle; - - public Editor() { - saveButton = new Button(); - saveButton.setText(GridConstants.DEFAULT_SAVE_CAPTION); - saveButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - save(); - } - }); - - cancelButton = new Button(); - cancelButton.setText(GridConstants.DEFAULT_CANCEL_CAPTION); - cancelButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - cancel(); - } - }); - } - - public void setEditorError(String errorMessage, - Collection> errorColumns) { - - if (errorMessage == null) { - message.removeFromParent(); - } else { - message.setInnerText(errorMessage); - if (message.getParentElement() == null) { - messageWrapper.appendChild(message); - } - } - // In unbuffered mode only show message wrapper if there is an error - if (!isBuffered()) { - setMessageAndButtonsWrapperVisible(errorMessage != null); - } - - if (state == State.ACTIVE || state == State.SAVING) { - for (Column c : grid.getColumns()) { - grid.getEditor().setEditorColumnError(c, - errorColumns.contains(c)); - } - } - } - - public int getRow() { - return rowIndex; - } - - /** - * If a cell of this Grid had focus once this editRow call was - * triggered, the editor component at the previously focused column - * index will be focused. - * - * If a Grid cell was not focused prior to calling this method, it will - * be equivalent to {@code editRow(rowIndex, -1)}. - * - * @see #editRow(int, int) - */ - public void editRow(int rowIndex) { - // Focus the last focused column in the editor iff grid or its child - // was focused before the edit request - Cell focusedCell = grid.cellFocusHandler.getFocusedCell(); - Element focusedElement = WidgetUtil.getFocusedElement(); - if (focusedCell != null && focusedElement != null - && grid.getElement().isOrHasChild(focusedElement)) { - editRow(rowIndex, focusedCell.getColumn()); - } else { - editRow(rowIndex, -1); - } - } - - /** - * Opens the editor over the row with the given index and attempts to - * focus the editor widget in the given column index. Does not move - * focus if the widget is not focusable or if the column index is -1. - * - * @param rowIndex - * the index of the row to be edited - * @param columnIndex - * the column index of the editor widget that should be - * initially focused or -1 to not set focus - * - * @throws IllegalStateException - * if this editor is not enabled - * @throws IllegalStateException - * if this editor is already in edit mode and in buffered - * mode - * - * @since 7.5 - */ - public void editRow(final int rowIndex, final int columnIndex) { - if (!enabled) { - throw new IllegalStateException( - "Cannot edit row: editor is not enabled"); - } - - if (isWorkPending()) { - // Request pending a response, don't move try to start another - // request. - return; - } - - if (state != State.INACTIVE && this.rowIndex != rowIndex) { - if (isBuffered()) { - throw new IllegalStateException( - "Cannot edit row: editor already in edit mode"); - } else if (!columnErrors.isEmpty()) { - // Don't move row if errors are present - - // FIXME: Should attempt bind if error field values have - // changed. - - return; - } - } - if (columnIndex >= grid.getVisibleColumns().size()) { - throw new IllegalArgumentException("Edited column index " - + columnIndex - + " was bigger than visible column count."); - } - - if (this.rowIndex == rowIndex && focusedColumnIndex == columnIndex) { - // NO-OP - return; - } - - if (this.rowIndex == rowIndex) { - if (focusedColumnIndex != columnIndex) { - if (columnIndex >= grid.getFrozenColumnCount()) { - // Scroll to new focused column. - grid.getEscalator().scrollToColumn(columnIndex, - ScrollDestination.ANY, 0); - } - - focusedColumnIndex = columnIndex; - } - - updateHorizontalScrollPosition(); - - // Update Grid internal focus and focus widget if possible - if (focusedColumnIndex >= 0) { - grid.focusCell(rowIndex, focusedColumnIndex); - focusColumn(focusedColumnIndex); - } - - // No need to request anything from the editor handler. - return; - } - state = State.ACTIVATING; - - final Escalator escalator = grid.getEscalator(); - if (escalator.getVisibleRowRange().contains(rowIndex)) { - show(rowIndex, columnIndex); - } else { - vScrollHandler = grid.addScrollHandler(new ScrollHandler() { - @Override - public void onScroll(ScrollEvent event) { - if (escalator.getVisibleRowRange().contains(rowIndex)) { - show(rowIndex, columnIndex); - vScrollHandler.removeHandler(); - } - } - }); - grid.scrollToRow(rowIndex, - isBuffered() ? ScrollDestination.MIDDLE - : ScrollDestination.ANY); - } - } - - /** - * Cancels the currently active edit and hides the editor. Any changes - * that are not {@link #save() saved} are lost. - * - * @throws IllegalStateException - * if this editor is not enabled - * @throws IllegalStateException - * if this editor is not in edit mode - */ - public void cancel() { - if (!enabled) { - throw new IllegalStateException( - "Cannot cancel edit: editor is not enabled"); - } - if (state == State.INACTIVE) { - throw new IllegalStateException( - "Cannot cancel edit: editor is not in edit mode"); - } - handler.cancel(new EditorRequestImpl(grid, rowIndex, - focusedColumnIndex, null)); - doCancel(); - } - - private void doCancel() { - hideOverlay(); - state = State.INACTIVE; - rowIndex = -1; - focusedColumnIndex = -1; - grid.getEscalator().setScrollLocked(Direction.VERTICAL, false); - updateSelectionCheckboxesAsNeeded(true); - } - - private void updateSelectionCheckboxesAsNeeded(boolean isEnabled) { - // FIXME: This is too much guessing. Define a better way to do this. - if (grid.selectionColumn != null - && grid.selectionColumn.getRenderer() instanceof MultiSelectionRenderer) { - grid.refreshBody(); - CheckBox checkBox = (CheckBox) grid.getDefaultHeaderRow() - .getCell(grid.selectionColumn).getWidget(); - checkBox.setEnabled(isEnabled); - } - } - - /** - * Saves any unsaved changes to the data source and hides the editor. - * - * @throws IllegalStateException - * if this editor is not enabled - * @throws IllegalStateException - * if this editor is not in edit mode - */ - public void save() { - if (!enabled) { - throw new IllegalStateException( - "Cannot save: editor is not enabled"); - } - if (state != State.ACTIVE) { - throw new IllegalStateException( - "Cannot save: editor is not in edit mode"); - } - - state = State.SAVING; - setButtonsEnabled(false); - saveTimeout.schedule(SAVE_TIMEOUT_MS); - EditorRequest request = new EditorRequestImpl(grid, rowIndex, - focusedColumnIndex, saveRequestCallback); - handler.save(request); - updateSelectionCheckboxesAsNeeded(true); - } - - /** - * Returns the handler responsible for binding data and editor widgets - * to this editor. - * - * @return the editor handler or null if not set - */ - public EditorHandler getHandler() { - return handler; - } - - /** - * Sets the handler responsible for binding data and editor widgets to - * this editor. - * - * @param rowHandler - * the new editor handler - * - * @throws IllegalStateException - * if this editor is currently in edit mode - */ - public void setHandler(EditorHandler rowHandler) { - if (state != State.INACTIVE) { - throw new IllegalStateException( - "Cannot set EditorHandler: editor is currently in edit mode"); - } - handler = rowHandler; - } - - public boolean isEnabled() { - return enabled; - } - - /** - * Sets the enabled state of this editor. - * - * @param enabled - * true if enabled, false otherwise - * - * @throws IllegalStateException - * if in edit mode and trying to disable - * @throws IllegalStateException - * if the editor handler is not set - */ - public void setEnabled(boolean enabled) { - if (enabled == false && state != State.INACTIVE) { - throw new IllegalStateException( - "Cannot disable: editor is in edit mode"); - } else if (enabled == true && getHandler() == null) { - throw new IllegalStateException( - "Cannot enable: EditorHandler not set"); - } - this.enabled = enabled; - } - - protected void show(int rowIndex, int columnIndex) { - if (state == State.ACTIVATING) { - state = State.BINDING; - bindTimeout.schedule(BIND_TIMEOUT_MS); - EditorRequest request = new EditorRequestImpl(grid, - rowIndex, columnIndex, bindRequestCallback); - handler.bind(request); - grid.getEscalator().setScrollLocked(Direction.VERTICAL, - isBuffered()); - updateSelectionCheckboxesAsNeeded(false); - } - } - - protected void setGrid(final Grid grid) { - assert grid != null : "Grid cannot be null"; - assert this.grid == null : "Can only attach editor to Grid once"; - - this.grid = grid; - } - - protected State getState() { - return state; - } - - protected void setState(State state) { - this.state = state; - } - - /** - * Returns the editor widget associated with the given column. If the - * editor is not active or the column is not - * {@link Grid.Column#isEditable() editable}, returns null. - * - * @param column - * the column - * @return the widget if the editor is open and the column is editable, - * null otherwise - */ - protected Widget getWidget(Column column) { - return columnToWidget.get(column); - } - - /** - * Equivalent to {@code showOverlay()}. The argument is ignored. - * - * @param unused - * ignored argument - * - * @deprecated As of 7.5, use {@link #showOverlay()} instead. - */ - @Deprecated - protected void showOverlay(TableRowElement unused) { - showOverlay(); - } - - /** - * Opens the editor overlay over the table row indicated by - * {@link #getRow()}. - * - * @since 7.5 - */ - protected void showOverlay() { - // Ensure overlay is hidden initially - hideOverlay(); - DivElement gridElement = DivElement.as(grid.getElement()); - - TableRowElement tr = grid.getEscalator().getBody() - .getRowElement(rowIndex); - - hScrollHandler = grid.addScrollHandler(new ScrollHandler() { - @Override - public void onScroll(ScrollEvent event) { - updateHorizontalScrollPosition(); - updateVerticalScrollPosition(); - } - }); - - gridElement.appendChild(editorOverlay); - editorOverlay.appendChild(frozenCellWrapper); - editorOverlay.appendChild(cellWrapper); - editorOverlay.appendChild(messageAndButtonsWrapper); - - updateBufferedStyleName(); - - int frozenColumns = grid.getVisibleFrozenColumnCount(); - double frozenColumnsWidth = 0; - double cellHeight = 0; - - for (int i = 0; i < tr.getCells().getLength(); i++) { - Element cell = createCell(tr.getCells().getItem(i)); - cellHeight = Math.max(cellHeight, WidgetUtil - .getRequiredHeightBoundingClientRectDouble(tr - .getCells().getItem(i))); - - Column column = grid.getVisibleColumn(i); - - if (i < frozenColumns) { - frozenCellWrapper.appendChild(cell); - frozenColumnsWidth += WidgetUtil - .getRequiredWidthBoundingClientRectDouble(tr - .getCells().getItem(i)); - } else { - cellWrapper.appendChild(cell); - } - - if (column.isEditable()) { - Widget editor = getHandler().getWidget(column); - - if (editor != null) { - columnToWidget.put(column, editor); - grid.attachWidget(editor, cell); - } - - if (i == focusedColumnIndex) { - if (BrowserInfo.get().isIE8()) { - Scheduler.get().scheduleDeferred(fieldFocusCommand); - } else { - focusColumn(focusedColumnIndex); - } - } - } else { - cell.addClassName(NOT_EDITABLE_CLASS_NAME); - cell.addClassName(tr.getCells().getItem(i).getClassName()); - // If the focused or frozen stylename is present it should - // not be inherited by the editor cell as it is not useful - // in the editor and would look broken without additional - // style rules. This is a bit of a hack. - cell.removeClassName(grid.cellFocusStyleName); - cell.removeClassName("frozen"); - - if (column == grid.selectionColumn) { - // Duplicate selection column CheckBox - - pinnedRowHandle = grid.getDataSource().getHandle( - grid.getDataSource().getRow(rowIndex)); - pinnedRowHandle.pin(); - - // We need to duplicate the selection CheckBox for the - // editor overlay since the original one is hidden by - // the overlay - final CheckBox checkBox = GWT.create(CheckBox.class); - checkBox.setValue(grid.isSelected(pinnedRowHandle - .getRow())); - checkBox.sinkEvents(Event.ONCLICK); - - checkBox.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - T row = pinnedRowHandle.getRow(); - if (grid.isSelected(row)) { - grid.deselect(row); - } else { - grid.select(row); - } - } - }); - grid.attachWidget(checkBox, cell); - columnToWidget.put(column, checkBox); - - // Only enable CheckBox in non-buffered mode - checkBox.setEnabled(!isBuffered()); - - } else if (!(column.getRenderer() instanceof WidgetRenderer)) { - // Copy non-widget content directly - cell.setInnerHTML(tr.getCells().getItem(i) - .getInnerHTML()); - } - } - } - - setBounds(frozenCellWrapper, 0, 0, frozenColumnsWidth, 0); - setBounds(cellWrapper, frozenColumnsWidth, 0, tr.getOffsetWidth() - - frozenColumnsWidth, cellHeight); - - // Only add these elements once - if (!messageAndButtonsWrapper.isOrHasChild(messageWrapper)) { - messageAndButtonsWrapper.appendChild(messageWrapper); - messageAndButtonsWrapper.appendChild(buttonsWrapper); - } - - if (isBuffered()) { - grid.attachWidget(saveButton, buttonsWrapper); - grid.attachWidget(cancelButton, buttonsWrapper); - } - - setMessageAndButtonsWrapperVisible(isBuffered()); - - updateHorizontalScrollPosition(); - - AbstractRowContainer body = (AbstractRowContainer) grid - .getEscalator().getBody(); - double rowTop = body.getRowTop(tr); - - int bodyTop = body.getElement().getAbsoluteTop(); - int gridTop = gridElement.getAbsoluteTop(); - double overlayTop = rowTop + bodyTop - gridTop; - - originalScrollTop = grid.getScrollTop(); - if (!isBuffered() || buttonsShouldBeRenderedBelow(tr)) { - // Default case, editor buttons are below the edited row - editorOverlay.getStyle().setTop(overlayTop, Unit.PX); - originalTop = overlayTop; - editorOverlay.getStyle().clearBottom(); - } else { - // Move message and buttons wrapper on top of cell wrapper if - // there is not enough space visible space under and fix the - // overlay from the bottom - editorOverlay.insertFirst(messageAndButtonsWrapper); - int gridHeight = grid.getElement().getOffsetHeight(); - editorOverlay.getStyle() - .setBottom( - gridHeight - overlayTop - tr.getOffsetHeight(), - Unit.PX); - editorOverlay.getStyle().clearTop(); - } - - // Do not render over the vertical scrollbar - editorOverlay.getStyle().setWidth(grid.escalator.getInnerWidth(), - Unit.PX); - } - - private void focusColumn(int colIndex) { - if (colIndex < 0 || colIndex >= grid.getVisibleColumns().size()) { - // NO-OP - return; - } - - Widget editor = getWidget(grid.getVisibleColumn(colIndex)); - if (editor instanceof Focusable) { - ((Focusable) editor).focus(); - } else if (editor instanceof com.google.gwt.user.client.ui.Focusable) { - ((com.google.gwt.user.client.ui.Focusable) editor) - .setFocus(true); - } else { - grid.focus(); - } - } - - private boolean buttonsShouldBeRenderedBelow(TableRowElement tr) { - TableSectionElement tfoot = grid.escalator.getFooter().getElement(); - double tfootPageTop = WidgetUtil.getBoundingClientRect(tfoot) - .getTop(); - double trPageBottom = WidgetUtil.getBoundingClientRect(tr) - .getBottom(); - int messageAndButtonsHeight = messageAndButtonsWrapper - .getOffsetHeight(); - double bottomOfButtons = trPageBottom + messageAndButtonsHeight; - - return bottomOfButtons < tfootPageTop; - } - - protected void hideOverlay() { - if (editorOverlay.getParentElement() == null) { - return; - } - - if (pinnedRowHandle != null) { - pinnedRowHandle.unpin(); - pinnedRowHandle = null; - } - - for (HandlerRegistration r : focusHandlers) { - r.removeHandler(); - } - focusHandlers.clear(); - - for (Widget w : columnToWidget.values()) { - setParent(w, null); - } - columnToWidget.clear(); - - if (isBuffered()) { - grid.detachWidget(saveButton); - grid.detachWidget(cancelButton); - } - - editorOverlay.removeAllChildren(); - cellWrapper.removeAllChildren(); - frozenCellWrapper.removeAllChildren(); - editorOverlay.removeFromParent(); - - hScrollHandler.removeHandler(); - - clearEditorColumnErrors(); - } - - private void updateBufferedStyleName() { - if (isBuffered()) { - editorOverlay.removeClassName("unbuffered"); - editorOverlay.addClassName("buffered"); - } else { - editorOverlay.removeClassName("buffered"); - editorOverlay.addClassName("unbuffered"); - } - } - - protected void setStylePrimaryName(String primaryName) { - if (styleName != null) { - editorOverlay.removeClassName(styleName); - - cellWrapper.removeClassName(styleName + "-cells"); - frozenCellWrapper.removeClassName(styleName + "-cells"); - messageAndButtonsWrapper.removeClassName(styleName + "-footer"); - - messageWrapper.removeClassName(styleName + "-message"); - buttonsWrapper.removeClassName(styleName + "-buttons"); - - saveButton.removeStyleName(styleName + "-save"); - cancelButton.removeStyleName(styleName + "-cancel"); - } - styleName = primaryName + "-editor"; - editorOverlay.setClassName(styleName); - - cellWrapper.setClassName(styleName + "-cells"); - frozenCellWrapper.setClassName(styleName + "-cells frozen"); - messageAndButtonsWrapper.setClassName(styleName + "-footer"); - - messageWrapper.setClassName(styleName + "-message"); - buttonsWrapper.setClassName(styleName + "-buttons"); - - saveButton.setStyleName(styleName + "-save"); - cancelButton.setStyleName(styleName + "-cancel"); - } - - /** - * Creates an editor cell corresponding to the given table cell. The - * returned element is empty and has the same dimensions and position as - * the table cell. - * - * @param td - * the table cell used as a reference - * @return an editor cell corresponding to the given cell - */ - protected Element createCell(TableCellElement td) { - DivElement cell = DivElement.as(DOM.createDiv()); - double width = WidgetUtil - .getRequiredWidthBoundingClientRectDouble(td); - double height = WidgetUtil - .getRequiredHeightBoundingClientRectDouble(td); - setBounds(cell, td.getOffsetLeft(), td.getOffsetTop(), width, - height); - return cell; - } - - private static void setBounds(Element e, double left, double top, - double width, double height) { - Style style = e.getStyle(); - style.setLeft(left, Unit.PX); - style.setTop(top, Unit.PX); - style.setWidth(width, Unit.PX); - style.setHeight(height, Unit.PX); - } - - private void updateHorizontalScrollPosition() { - double scrollLeft = grid.getScrollLeft(); - cellWrapper.getStyle().setLeft( - frozenCellWrapper.getOffsetWidth() - scrollLeft, Unit.PX); - } - - /** - * Moves the editor overlay on scroll so that it stays on top of the - * edited row. This will also snap the editor to top or bottom of the - * row container if the edited row is scrolled out of the visible area. - */ - private void updateVerticalScrollPosition() { - if (isBuffered()) { - return; - } - - double newScrollTop = grid.getScrollTop(); - - int gridTop = grid.getElement().getAbsoluteTop(); - int editorHeight = editorOverlay.getOffsetHeight(); - - Escalator escalator = grid.getEscalator(); - TableSectionElement header = escalator.getHeader().getElement(); - int footerTop = escalator.getFooter().getElement().getAbsoluteTop(); - int headerBottom = header.getAbsoluteBottom(); - - double newTop = originalTop - (newScrollTop - originalScrollTop); - - if (newTop + gridTop < headerBottom) { - // Snap editor to top of the row container - newTop = header.getOffsetHeight(); - } else if (newTop + gridTop > footerTop - editorHeight) { - // Snap editor to the bottom of the row container - newTop = footerTop - editorHeight - gridTop; - } - - editorOverlay.getStyle().setTop(newTop, Unit.PX); - } - - protected void setGridEnabled(boolean enabled) { - // TODO: This should be informed to handler as well so possible - // fields can be disabled. - setButtonsEnabled(enabled); - } - - private void setButtonsEnabled(boolean enabled) { - saveButton.setEnabled(enabled); - cancelButton.setEnabled(enabled); - } - - public void setSaveCaption(String saveCaption) - throws IllegalArgumentException { - if (saveCaption == null) { - throw new IllegalArgumentException( - "Save caption cannot be null"); - } - saveButton.setText(saveCaption); - } - - public String getSaveCaption() { - return saveButton.getText(); - } - - public void setCancelCaption(String cancelCaption) - throws IllegalArgumentException { - if (cancelCaption == null) { - throw new IllegalArgumentException( - "Cancel caption cannot be null"); - } - cancelButton.setText(cancelCaption); - } - - public String getCancelCaption() { - return cancelButton.getText(); - } - - public void setEditorColumnError(Column column, boolean hasError) { - if (state != State.ACTIVE && state != State.SAVING) { - throw new IllegalStateException("Cannot set cell error " - + "status: editor is neither active nor saving."); - } - - if (isEditorColumnError(column) == hasError) { - return; - } - - Element editorCell = getWidget(column).getElement() - .getParentElement(); - if (hasError) { - editorCell.addClassName(ERROR_CLASS_NAME); - columnErrors.add(column); - } else { - editorCell.removeClassName(ERROR_CLASS_NAME); - columnErrors.remove(column); - } - } - - public void clearEditorColumnErrors() { - - /* - * editorOverlay has no children if it's not active, effectively - * making this loop a NOOP. - */ - Element e = editorOverlay.getFirstChildElement(); - while (e != null) { - e.removeClassName(ERROR_CLASS_NAME); - e = e.getNextSiblingElement(); - } - - columnErrors.clear(); - } - - public boolean isEditorColumnError(Column column) { - return columnErrors.contains(column); - } - - public void setBuffered(boolean buffered) { - this.buffered = buffered; - setMessageAndButtonsWrapperVisible(buffered); - } - - public boolean isBuffered() { - return buffered; - } - - private void setMessageAndButtonsWrapperVisible(boolean visible) { - if (visible) { - messageAndButtonsWrapper.getStyle().clearDisplay(); - } else { - messageAndButtonsWrapper.getStyle().setDisplay(Display.NONE); - } - } - - /** - * Sets the event handler for this Editor. - * - * @since 7.6 - * @param handler - * the new event handler - */ - public void setEventHandler(EventHandler handler) { - eventHandler = handler; - } - - /** - * Returns the event handler of this Editor. - * - * @since 7.6 - * @return the current event handler - */ - public EventHandler getEventHandler() { - return eventHandler; - } - - @Override - public boolean isWorkPending() { - return saveTimeout.isRunning() || bindTimeout.isRunning(); - } - - protected int getElementColumn(Element e) { - int frozenCells = frozenCellWrapper.getChildCount(); - if (frozenCellWrapper.isOrHasChild(e)) { - for (int i = 0; i < frozenCells; ++i) { - if (frozenCellWrapper.getChild(i).isOrHasChild(e)) { - return i; - } - } - } - - if (cellWrapper.isOrHasChild(e)) { - for (int i = 0; i < cellWrapper.getChildCount(); ++i) { - if (cellWrapper.getChild(i).isOrHasChild(e)) { - return i + frozenCells; - } - } - } - - return -1; - } - } - - public static abstract class AbstractGridKeyEvent - extends KeyEvent { - - private Grid grid; - private final Type associatedType = new Type( - getBrowserEventType(), this); - private final CellReference targetCell; - - public AbstractGridKeyEvent(Grid grid, CellReference targetCell) { - this.grid = grid; - this.targetCell = targetCell; - } - - protected abstract String getBrowserEventType(); - - /** - * Gets the Grid instance for this event. - * - * @return grid - */ - public Grid getGrid() { - return grid; - } - - /** - * Gets the focused cell for this event. - * - * @return focused cell - */ - public CellReference getFocusedCell() { - return targetCell; - } - - @Override - protected void dispatch(HANDLER handler) { - EventTarget target = getNativeEvent().getEventTarget(); - if (Element.is(target) - && !grid.isElementInChildWidget(Element.as(target))) { - - Section section = Section.FOOTER; - final RowContainer container = grid.cellFocusHandler.containerWithFocus; - if (container == grid.escalator.getHeader()) { - section = Section.HEADER; - } else if (container == grid.escalator.getBody()) { - section = Section.BODY; - } - - doDispatch(handler, section); - } - } - - protected abstract void doDispatch(HANDLER handler, Section section); - - @Override - public Type getAssociatedType() { - return associatedType; - } - } - - public static abstract class AbstractGridMouseEvent - extends MouseEvent { - - private Grid grid; - private final CellReference targetCell; - private final Type associatedType = new Type( - getBrowserEventType(), this); - - public AbstractGridMouseEvent(Grid grid, CellReference targetCell) { - this.grid = grid; - this.targetCell = targetCell; - } - - protected abstract String getBrowserEventType(); - - /** - * Gets the Grid instance for this event. - * - * @return grid - */ - public Grid getGrid() { - return grid; - } - - /** - * Gets the reference of target cell for this event. - * - * @return target cell - */ - public CellReference getTargetCell() { - return targetCell; - } - - @Override - protected void dispatch(HANDLER handler) { - EventTarget target = getNativeEvent().getEventTarget(); - if (!Element.is(target)) { - // Target is not an element - return; - } - - Element targetElement = Element.as(target); - if (grid.isElementInChildWidget(targetElement)) { - // Target is some widget inside of Grid - return; - } - - final RowContainer container = grid.escalator - .findRowContainer(targetElement); - if (container == null) { - // No container for given element - return; - } - - Section section = Section.FOOTER; - if (container == grid.escalator.getHeader()) { - section = Section.HEADER; - } else if (container == grid.escalator.getBody()) { - section = Section.BODY; - } - - doDispatch(handler, section); - } - - protected abstract void doDispatch(HANDLER handler, Section section); - - @Override - public Type getAssociatedType() { - return associatedType; - } - } - - private static final String CUSTOM_STYLE_PROPERTY_NAME = "customStyle"; - - /** - * An initial height that is given to new details rows before rendering the - * appropriate widget that we then can be measure - * - * @see GridSpacerUpdater - */ - private static final double DETAILS_ROW_INITIAL_HEIGHT = 50; - - private EventCellReference eventCell = new EventCellReference(this); - private GridKeyDownEvent keyDown = new GridKeyDownEvent(this, eventCell); - private GridKeyUpEvent keyUp = new GridKeyUpEvent(this, eventCell); - private GridKeyPressEvent keyPress = new GridKeyPressEvent(this, eventCell); - private GridClickEvent clickEvent = new GridClickEvent(this, eventCell); - private GridDoubleClickEvent doubleClickEvent = new GridDoubleClickEvent( - this, eventCell); - - private class CellFocusHandler { - - private RowContainer containerWithFocus = escalator.getBody(); - private int rowWithFocus = 0; - private Range cellFocusRange = Range.withLength(0, 1); - private int lastFocusedBodyRow = 0; - private int lastFocusedHeaderRow = 0; - private int lastFocusedFooterRow = 0; - private TableCellElement cellWithFocusStyle = null; - private TableRowElement rowWithFocusStyle = null; - - public CellFocusHandler() { - sinkEvents(getNavigationEvents()); - } - - private Cell getFocusedCell() { - return new Cell(rowWithFocus, cellFocusRange.getStart(), - cellWithFocusStyle); - } - - /** - * Sets style names for given cell when needed. - */ - public void updateFocusedCellStyle(FlyweightCell cell, - RowContainer cellContainer) { - int cellRow = cell.getRow(); - int cellColumn = cell.getColumn(); - int colSpan = cell.getColSpan(); - boolean columnHasFocus = Range.withLength(cellColumn, colSpan) - .intersects(cellFocusRange); - - if (cellContainer == containerWithFocus) { - // Cell is in the current container - if (cellRow == rowWithFocus && columnHasFocus) { - if (cellWithFocusStyle != cell.getElement()) { - // Cell is correct but it does not have focused style - if (cellWithFocusStyle != null) { - // Remove old focus style - setStyleName(cellWithFocusStyle, - cellFocusStyleName, false); - } - cellWithFocusStyle = cell.getElement(); - - // Add focus style to correct cell. - setStyleName(cellWithFocusStyle, cellFocusStyleName, - true); - } - } else if (cellWithFocusStyle == cell.getElement()) { - // Due to escalator reusing cells, a new cell has the same - // element but is not the focused cell. - setStyleName(cellWithFocusStyle, cellFocusStyleName, false); - cellWithFocusStyle = null; - } - } - } - - /** - * Sets focus style for the given row if needed. - * - * @param row - * a row object - */ - public void updateFocusedRowStyle(Row row) { - if (rowWithFocus == row.getRow() - && containerWithFocus == escalator.getBody()) { - if (row.getElement() != rowWithFocusStyle) { - // Row should have focus style but does not have it. - if (rowWithFocusStyle != null) { - setStyleName(rowWithFocusStyle, rowFocusStyleName, - false); - } - rowWithFocusStyle = row.getElement(); - setStyleName(rowWithFocusStyle, rowFocusStyleName, true); - } - } else if (rowWithFocusStyle == row.getElement() - || (containerWithFocus != escalator.getBody() && rowWithFocusStyle != null)) { - // Remove focus style. - setStyleName(rowWithFocusStyle, rowFocusStyleName, false); - rowWithFocusStyle = null; - } - } - - /** - * Sets the currently focused. - *

- * NOTE: the column index is the index in DOM, not the logical - * column index which includes hidden columns. - * - * @param rowIndex - * the index of the row having focus - * @param columnIndexDOM - * the index of the cell having focus - * @param container - * the row container having focus - */ - private void setCellFocus(int rowIndex, int columnIndexDOM, - RowContainer container) { - if (rowIndex == rowWithFocus - && cellFocusRange.contains(columnIndexDOM) - && container == this.containerWithFocus) { - refreshRow(rowWithFocus); - return; - } - - int oldRow = rowWithFocus; - rowWithFocus = rowIndex; - Range oldRange = cellFocusRange; - - if (container == escalator.getBody()) { - scrollToRow(rowWithFocus); - cellFocusRange = Range.withLength(columnIndexDOM, 1); - } else { - int i = 0; - Element cell = container.getRowElement(rowWithFocus) - .getFirstChildElement(); - do { - int colSpan = cell - .getPropertyInt(FlyweightCell.COLSPAN_ATTR); - Range cellRange = Range.withLength(i, colSpan); - if (cellRange.contains(columnIndexDOM)) { - cellFocusRange = cellRange; - break; - } - cell = cell.getNextSiblingElement(); - ++i; - } while (cell != null); - } - int columnIndex = getColumns().indexOf( - getVisibleColumn(columnIndexDOM)); - if (columnIndex >= escalator.getColumnConfiguration() - .getFrozenColumnCount()) { - escalator.scrollToColumn(columnIndexDOM, ScrollDestination.ANY, - 10); - } - - if (this.containerWithFocus == container) { - if (oldRange.equals(cellFocusRange) && oldRow != rowWithFocus) { - refreshRow(oldRow); - } else { - refreshHeader(); - refreshFooter(); - } - } else { - RowContainer oldContainer = this.containerWithFocus; - this.containerWithFocus = container; - - if (oldContainer == escalator.getBody()) { - lastFocusedBodyRow = oldRow; - } else if (oldContainer == escalator.getHeader()) { - lastFocusedHeaderRow = oldRow; - } else { - lastFocusedFooterRow = oldRow; - } - - if (!oldRange.equals(cellFocusRange)) { - refreshHeader(); - refreshFooter(); - if (oldContainer == escalator.getBody()) { - oldContainer.refreshRows(oldRow, 1); - } - } else { - oldContainer.refreshRows(oldRow, 1); - } - } - refreshRow(rowWithFocus); - } - - /** - * Sets focus on a cell. - * - *

- * Note: cell focus is not the same as JavaScript's - * {@code document.activeElement}. - * - * @param cell - * a cell object - */ - public void setCellFocus(CellReference cell) { - setCellFocus(cell.getRowIndex(), cell.getColumnIndexDOM(), - escalator.findRowContainer(cell.getElement())); - } - - /** - * Gets list of events that can be used for cell focusing. - * - * @return list of navigation related event types - */ - public Collection getNavigationEvents() { - return Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.CLICK); - } - - /** - * Handle events that can move the cell focus. - */ - public void handleNavigationEvent(Event event, CellReference cell) { - if (event.getType().equals(BrowserEvents.CLICK)) { - setCellFocus(cell); - // Grid should have focus when clicked. - getElement().focus(); - } else if (event.getType().equals(BrowserEvents.KEYDOWN)) { - int newRow = rowWithFocus; - RowContainer newContainer = containerWithFocus; - int newColumn = cellFocusRange.getStart(); - - switch (event.getKeyCode()) { - case KeyCodes.KEY_DOWN: - ++newRow; - break; - case KeyCodes.KEY_UP: - --newRow; - break; - case KeyCodes.KEY_RIGHT: - if (cellFocusRange.getEnd() >= getVisibleColumns().size()) { - return; - } - newColumn = cellFocusRange.getEnd(); - break; - case KeyCodes.KEY_LEFT: - if (newColumn == 0) { - return; - } - --newColumn; - break; - case KeyCodes.KEY_TAB: - if (event.getShiftKey()) { - newContainer = getPreviousContainer(containerWithFocus); - } else { - newContainer = getNextContainer(containerWithFocus); - } - - if (newContainer == containerWithFocus) { - return; - } - break; - case KeyCodes.KEY_HOME: - if (newContainer.getRowCount() > 0) { - newRow = 0; - } - break; - case KeyCodes.KEY_END: - if (newContainer.getRowCount() > 0) { - newRow = newContainer.getRowCount() - 1; - } - break; - case KeyCodes.KEY_PAGEDOWN: - case KeyCodes.KEY_PAGEUP: - if (newContainer.getRowCount() > 0) { - boolean down = event.getKeyCode() == KeyCodes.KEY_PAGEDOWN; - // If there is a visible focused cell, scroll by one - // page from its position. Otherwise, use the first or - // the last visible row as the scroll start position. - // This avoids jumping when using both keyboard and the - // scroll bar for scrolling. - int firstVisible = getFirstVisibleRowIndex(); - int lastVisible = getLastVisibleRowIndex(); - if (newRow < firstVisible || newRow > lastVisible) { - newRow = down ? lastVisible : firstVisible; - } - // Scroll by a little less than the visible area to - // account for the possibility that the top and the - // bottom row are only partially visible. - int moveFocusBy = Math.max(1, lastVisible - - firstVisible - 1); - moveFocusBy *= down ? 1 : -1; - newRow += moveFocusBy; - newRow = Math.max(0, Math.min( - newContainer.getRowCount() - 1, newRow)); - } - break; - default: - return; - } - - if (newContainer != containerWithFocus) { - if (newContainer == escalator.getBody()) { - newRow = lastFocusedBodyRow; - } else if (newContainer == escalator.getHeader()) { - newRow = lastFocusedHeaderRow; - } else { - newRow = lastFocusedFooterRow; - } - } else if (newRow < 0) { - newContainer = getPreviousContainer(newContainer); - - if (newContainer == containerWithFocus) { - newRow = 0; - } else if (newContainer == escalator.getBody()) { - newRow = getLastVisibleRowIndex(); - } else { - newRow = newContainer.getRowCount() - 1; - } - } else if (newRow >= containerWithFocus.getRowCount()) { - newContainer = getNextContainer(newContainer); - - if (newContainer == containerWithFocus) { - newRow = containerWithFocus.getRowCount() - 1; - } else if (newContainer == escalator.getBody()) { - newRow = getFirstVisibleRowIndex(); - } else { - newRow = 0; - } - } - - if (newContainer.getRowCount() == 0) { - /* - * There are no rows in the container. Can't change the - * focused cell. - */ - return; - } - - event.preventDefault(); - event.stopPropagation(); - - setCellFocus(newRow, newColumn, newContainer); - } - - } - - private RowContainer getPreviousContainer(RowContainer current) { - if (current == escalator.getFooter()) { - current = escalator.getBody(); - } else if (current == escalator.getBody()) { - current = escalator.getHeader(); - } else { - return current; - } - - if (current.getRowCount() == 0) { - return getPreviousContainer(current); - } - return current; - } - - private RowContainer getNextContainer(RowContainer current) { - if (current == escalator.getHeader()) { - current = escalator.getBody(); - } else if (current == escalator.getBody()) { - current = escalator.getFooter(); - } else { - return current; - } - - if (current.getRowCount() == 0) { - return getNextContainer(current); - } - return current; - } - - private void refreshRow(int row) { - containerWithFocus.refreshRows(row, 1); - } - - /** - * Offsets the focused cell's range. - * - * @param offset - * offset for fixing focused cell's range - */ - public void offsetRangeBy(int offset) { - cellFocusRange = cellFocusRange.offsetBy(offset); - } - - /** - * Informs {@link CellFocusHandler} that certain range of rows has been - * added to the Grid body. {@link CellFocusHandler} will fix indices - * accordingly. - * - * @param added - * a range of added rows - */ - public void rowsAddedToBody(Range added) { - boolean bodyHasFocus = (containerWithFocus == escalator.getBody()); - boolean insertionIsAboveFocusedCell = (added.getStart() <= rowWithFocus); - if (bodyHasFocus && insertionIsAboveFocusedCell) { - rowWithFocus += added.length(); - rowWithFocus = Math.min(rowWithFocus, escalator.getBody() - .getRowCount() - 1); - refreshRow(rowWithFocus); - } - } - - /** - * Informs {@link CellFocusHandler} that certain range of rows has been - * removed from the Grid body. {@link CellFocusHandler} will fix indices - * accordingly. - * - * @param removed - * a range of removed rows - */ - public void rowsRemovedFromBody(Range removed) { - if (containerWithFocus != escalator.getBody()) { - return; - } else if (!removed.contains(rowWithFocus)) { - if (removed.getStart() > rowWithFocus) { - return; - } - rowWithFocus = rowWithFocus - removed.length(); - } else { - if (containerWithFocus.getRowCount() > removed.getEnd()) { - rowWithFocus = removed.getStart(); - } else if (removed.getStart() > 0) { - rowWithFocus = removed.getStart() - 1; - } else { - if (escalator.getHeader().getRowCount() > 0) { - rowWithFocus = Math.min(lastFocusedHeaderRow, escalator - .getHeader().getRowCount() - 1); - containerWithFocus = escalator.getHeader(); - } else if (escalator.getFooter().getRowCount() > 0) { - rowWithFocus = Math.min(lastFocusedFooterRow, escalator - .getFooter().getRowCount() - 1); - containerWithFocus = escalator.getFooter(); - } - } - } - refreshRow(rowWithFocus); - } - } - - public final class SelectionColumn extends Column { - - private boolean initDone = false; - private boolean selected = false; - private CheckBox selectAllCheckBox; - - SelectionColumn(final Renderer selectColumnRenderer) { - super(selectColumnRenderer); - } - - void initDone() { - setWidth(-1); - - setEditable(false); - setResizable(false); - - initDone = true; - } - - @Override - protected void setDefaultHeaderContent(HeaderCell selectionCell) { - /* - * TODO: Currently the select all check box is shown when multi - * selection is in use. This might result in malfunctions if no - * SelectAllHandlers are present. - * - * Later on this could be fixed so that it check such handlers - * exist. - */ - final SelectionModel.Multi model = (Multi) getSelectionModel(); - - if (selectAllCheckBox == null) { - selectAllCheckBox = GWT.create(CheckBox.class); - selectAllCheckBox.setStylePrimaryName(getStylePrimaryName() - + SELECT_ALL_CHECKBOX_CLASSNAME); - selectAllCheckBox - .addValueChangeHandler(new ValueChangeHandler() { - - @Override - public void onValueChange( - ValueChangeEvent event) { - if (event.getValue()) { - fireEvent(new SelectAllEvent(model)); - selected = true; - } else { - model.deselectAll(); - selected = false; - } - } - }); - selectAllCheckBox.setValue(selected); - - addHeaderClickHandler(new HeaderClickHandler() { - @Override - public void onClick(GridClickEvent event) { - CellReference targetCell = event.getTargetCell(); - int defaultRowIndex = getHeader().getRows().indexOf( - getDefaultHeaderRow()); - - if (targetCell.getColumnIndex() == 0 - && targetCell.getRowIndex() == defaultRowIndex) { - selectAllCheckBox.setValue( - !selectAllCheckBox.getValue(), true); - } - } - }); - - // Select all with space when "select all" cell is active - addHeaderKeyUpHandler(new HeaderKeyUpHandler() { - @Override - public void onKeyUp(GridKeyUpEvent event) { - if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE) { - return; - } - HeaderRow targetHeaderRow = getHeader().getRow( - event.getFocusedCell().getRowIndex()); - if (!targetHeaderRow.isDefault()) { - return; - } - if (event.getFocusedCell().getColumn() == SelectionColumn.this) { - // Send events to ensure state is updated - selectAllCheckBox.setValue( - !selectAllCheckBox.getValue(), true); - } - } - }); - } else { - for (HeaderRow row : header.getRows()) { - if (row.getCell(this).getType() == GridStaticCellType.WIDGET) { - // Detach from old header. - row.getCell(this).setText(""); - } - } - } - - selectionCell.setWidget(selectAllCheckBox); - } - - @Override - public Column setWidth(double pixels) { - if (pixels != getWidth() && initDone) { - throw new UnsupportedOperationException("The selection " - + "column cannot be modified after init"); - } else { - super.setWidth(pixels); - } - - return this; - } - - @Override - public Boolean getValue(T row) { - return Boolean.valueOf(isSelected(row)); - } - - @Override - public Column setExpandRatio(int ratio) { - throw new UnsupportedOperationException( - "can't change the expand ratio of the selection column"); - } - - @Override - public int getExpandRatio() { - return 0; - } - - @Override - public Column setMaximumWidth(double pixels) { - throw new UnsupportedOperationException( - "can't change the maximum width of the selection column"); - } - - @Override - public double getMaximumWidth() { - return -1; - } - - @Override - public Column setMinimumWidth(double pixels) { - throw new UnsupportedOperationException( - "can't change the minimum width of the selection column"); - } - - @Override - public double getMinimumWidth() { - return -1; - } - - @Override - public Column setEditable(boolean editable) { - if (initDone) { - throw new UnsupportedOperationException( - "can't set the selection column editable"); - } - super.setEditable(editable); - return this; - } - } - - /** - * Helper class for performing sorting through the user interface. Controls - * the sort() method, reporting USER as the event originator. This is a - * completely internal class, and is, as such, safe to re-name should a more - * descriptive name come to mind. - */ - private final class UserSorter { - - private final Timer timer; - private boolean scheduledMultisort; - private Column column; - - private UserSorter() { - timer = new Timer() { - - @Override - public void run() { - UserSorter.this.sort(column, scheduledMultisort); - } - }; - } - - /** - * Toggle sorting for a cell. If the multisort parameter is set to true, - * the cell's sort order is modified as a natural part of a multi-sort - * chain. If false, the sorting order is set to ASCENDING for that - * cell's column. If that column was already the only sorted column in - * the Grid, the sort direction is flipped. - * - * @param cell - * a valid cell reference - * @param multisort - * whether the sort command should act as a multi-sort stack - * or not - */ - public void sort(Column column, boolean multisort) { - - if (!columns.contains(column)) { - throw new IllegalArgumentException( - "Given column is not a column in this grid. " - + column.toString()); - } - - if (!column.isSortable()) { - return; - } - - final SortOrder so = getSortOrder(column); - - if (multisort) { - - // If the sort order exists, replace existing value with its - // opposite - if (so != null) { - final int idx = sortOrder.indexOf(so); - sortOrder.set(idx, so.getOpposite()); - } else { - // If it doesn't, just add a new sort order to the end of - // the list - sortOrder.add(new SortOrder(column)); - } - - } else { - - // Since we're doing single column sorting, first clear the - // list. Then, if the sort order existed, add its opposite, - // otherwise just add a new sort value - - int items = sortOrder.size(); - sortOrder.clear(); - if (so != null && items == 1) { - sortOrder.add(so.getOpposite()); - } else { - sortOrder.add(new SortOrder(column)); - } - } - - // sortOrder has been changed; tell the Grid to re-sort itself by - // user request. - Grid.this.sort(true); - } - - /** - * Perform a sort after a delay. - * - * @param delay - * delay, in milliseconds - */ - public void sortAfterDelay(int delay, boolean multisort) { - column = eventCell.getColumn(); - scheduledMultisort = multisort; - timer.schedule(delay); - } - - /** - * Check if a delayed sort command has been issued but not yet carried - * out. - * - * @return a boolean value - */ - public boolean isDelayedSortScheduled() { - return timer.isRunning(); - } - - /** - * Cancel a scheduled sort. - */ - public void cancelDelayedSort() { - timer.cancel(); - } - - } - - /** - * @see Grid#autoColumnWidthsRecalculator - */ - private class AutoColumnWidthsRecalculator { - private double lastCalculatedInnerWidth = -1; - - private final ScheduledCommand calculateCommand = new ScheduledCommand() { - - @Override - public void execute() { - if (!isScheduled) { - // something cancelled running this. - return; - } - - if (header.markAsDirty || footer.markAsDirty) { - if (rescheduleCount < 10) { - /* - * Headers and footers are rendered as finally, this way - * we re-schedule this loop as finally, at the end of - * the queue, so that the headers have a chance to - * render themselves. - */ - Scheduler.get().scheduleFinally(this); - rescheduleCount++; - } else { - /* - * We've tried too many times reschedule finally. Seems - * like something is being deferred. Let the queue - * execute and retry again. - */ - rescheduleCount = 0; - Scheduler.get().scheduleDeferred(this); - } - } else if (dataIsBeingFetched) { - Scheduler.get().scheduleDeferred(this); - } else { - calculate(); - } - } - }; - - private int rescheduleCount = 0; - private boolean isScheduled; - - /** - * Calculates and applies column widths, taking into account fixed - * widths and column expand rules - * - * @param immediately - * true if the widths should be executed - * immediately (ignoring lazy loading completely), or - * false if the command should be run after a - * while (duplicate non-immediately invocations are ignored). - * @see Column#setWidth(double) - * @see Column#setExpandRatio(int) - * @see Column#setMinimumWidth(double) - * @see Column#setMaximumWidth(double) - */ - public void schedule() { - if (!isScheduled && isAttached()) { - isScheduled = true; - Scheduler.get().scheduleFinally(calculateCommand); - } - } - - private void calculate() { - isScheduled = false; - rescheduleCount = 0; - - assert !dataIsBeingFetched : "Trying to calculate column widths even though data is still being fetched."; - - if (columnsAreGuaranteedToBeWiderThanGrid()) { - applyColumnWidths(); - } else { - applyColumnWidthsWithExpansion(); - } - - // Update latest width to prevent recalculate on height change. - lastCalculatedInnerWidth = escalator.getInnerWidth(); - } - - private boolean columnsAreGuaranteedToBeWiderThanGrid() { - double freeSpace = escalator.getInnerWidth(); - for (Column column : getVisibleColumns()) { - if (column.getWidth() >= 0) { - freeSpace -= column.getWidth(); - } else if (column.getMinimumWidth() >= 0) { - freeSpace -= column.getMinimumWidth(); - } - } - return freeSpace < 0; - } - - @SuppressWarnings("boxing") - private void applyColumnWidths() { - - /* Step 1: Apply all column widths as they are. */ - - Map selfWidths = new LinkedHashMap(); - List> columns = getVisibleColumns(); - for (int index = 0; index < columns.size(); index++) { - selfWidths.put(index, columns.get(index).getWidth()); - } - Grid.this.escalator.getColumnConfiguration().setColumnWidths( - selfWidths); - - /* - * Step 2: Make sure that each column ends up obeying their min/max - * width constraints if defined as autowidth. If constraints are - * violated, fix it. - */ - - Map constrainedWidths = new LinkedHashMap(); - for (int index = 0; index < columns.size(); index++) { - Column column = columns.get(index); - - boolean hasAutoWidth = column.getWidth() < 0; - if (!hasAutoWidth) { - continue; - } - - // TODO: bug: these don't honor the CSS max/min. :( - double actualWidth = column.getWidthActual(); - if (actualWidth < getMinWidth(column)) { - constrainedWidths.put(index, column.getMinimumWidth()); - } else if (actualWidth > getMaxWidth(column)) { - constrainedWidths.put(index, column.getMaximumWidth()); - } - } - Grid.this.escalator.getColumnConfiguration().setColumnWidths( - constrainedWidths); - } - - private void applyColumnWidthsWithExpansion() { - boolean defaultExpandRatios = true; - int totalRatios = 0; - double reservedPixels = 0; - final Set> columnsToExpand = new HashSet>(); - List> nonFixedColumns = new ArrayList>(); - Map columnSizes = new HashMap(); - final List> visibleColumns = getVisibleColumns(); - - /* - * Set all fixed widths and also calculate the size-to-fit widths - * for the autocalculated columns. - * - * This way we know with how many pixels we have left to expand the - * rest. - */ - for (Column column : visibleColumns) { - final double widthAsIs = column.getWidth(); - final boolean isFixedWidth = widthAsIs >= 0; - // Check for max width just to be sure we don't break the limits - final double widthFixed = Math.max( - Math.min(getMaxWidth(column), widthAsIs), - column.getMinimumWidth()); - defaultExpandRatios = defaultExpandRatios - && (column.getExpandRatio() == -1 || column == selectionColumn); - - if (isFixedWidth) { - columnSizes.put(visibleColumns.indexOf(column), widthFixed); - reservedPixels += widthFixed; - } else { - nonFixedColumns.add(column); - columnSizes.put(visibleColumns.indexOf(column), -1.0d); - } - } - - setColumnSizes(columnSizes); - - for (Column column : nonFixedColumns) { - final int expandRatio = (defaultExpandRatios ? 1 : column - .getExpandRatio()); - final double maxWidth = getMaxWidth(column); - final double newWidth = Math.min(maxWidth, - column.getWidthActual()); - boolean shouldExpand = newWidth < maxWidth && expandRatio > 0 - && column != selectionColumn; - if (shouldExpand) { - totalRatios += expandRatio; - columnsToExpand.add(column); - } - reservedPixels += newWidth; - columnSizes.put(visibleColumns.indexOf(column), newWidth); - } - - /* - * Now that we know how many pixels we need at the very least, we - * can distribute the remaining pixels to all columns according to - * their expand ratios. - */ - double pixelsToDistribute = escalator.getInnerWidth() - - reservedPixels; - if (pixelsToDistribute <= 0 || totalRatios <= 0) { - if (pixelsToDistribute <= 0) { - // Set column sizes for expanding columns - setColumnSizes(columnSizes); - } - - return; - } - - /* - * Check for columns that hit their max width. Adjust - * pixelsToDistribute and totalRatios accordingly. Recheck. Stop - * when no new columns hit their max width - */ - boolean aColumnHasMaxedOut; - do { - aColumnHasMaxedOut = false; - final double widthPerRatio = pixelsToDistribute / totalRatios; - final Iterator> i = columnsToExpand.iterator(); - while (i.hasNext()) { - final Column column = i.next(); - final int expandRatio = getExpandRatio(column, - defaultExpandRatios); - final int columnIndex = visibleColumns.indexOf(column); - final double autoWidth = columnSizes.get(columnIndex); - final double maxWidth = getMaxWidth(column); - double expandedWidth = autoWidth + widthPerRatio - * expandRatio; - - if (maxWidth <= expandedWidth) { - i.remove(); - totalRatios -= expandRatio; - aColumnHasMaxedOut = true; - pixelsToDistribute -= maxWidth - autoWidth; - columnSizes.put(columnIndex, maxWidth); - } - } - } while (aColumnHasMaxedOut); - - if (totalRatios <= 0 && columnsToExpand.isEmpty()) { - setColumnSizes(columnSizes); - return; - } - assert pixelsToDistribute > 0 : "We've run out of pixels to distribute (" - + pixelsToDistribute - + "px to " - + totalRatios - + " ratios between " + columnsToExpand.size() + " columns)"; - assert totalRatios > 0 && !columnsToExpand.isEmpty() : "Bookkeeping out of sync. Ratios: " - + totalRatios + " Columns: " + columnsToExpand.size(); - - /* - * If we still have anything left, distribute the remaining pixels - * to the remaining columns. - */ - final double widthPerRatio; - int leftOver = 0; - if (BrowserInfo.get().isIE8() || BrowserInfo.get().isIE9() - || BrowserInfo.getBrowserString().contains("PhantomJS")) { - // These browsers report subpixels as integers. this usually - // results into issues.. - widthPerRatio = (int) (pixelsToDistribute / totalRatios); - leftOver = (int) (pixelsToDistribute - widthPerRatio - * totalRatios); - } else { - widthPerRatio = pixelsToDistribute / totalRatios; - } - for (Column column : columnsToExpand) { - final int expandRatio = getExpandRatio(column, - defaultExpandRatios); - final int columnIndex = visibleColumns.indexOf(column); - final double autoWidth = columnSizes.get(columnIndex); - double totalWidth = autoWidth + widthPerRatio * expandRatio; - if (leftOver > 0) { - totalWidth += 1; - leftOver--; - } - columnSizes.put(columnIndex, totalWidth); - - totalRatios -= expandRatio; - } - assert totalRatios == 0 : "Bookkeeping error: there were still some ratios left undistributed: " - + totalRatios; - - /* - * Check the guarantees for minimum width and scoot back the columns - * that don't care. - */ - boolean minWidthsCausedReflows; - do { - minWidthsCausedReflows = false; - - /* - * First, let's check which columns were too cramped, and expand - * them. Also keep track on how many pixels we grew - we need to - * remove those pixels from other columns - */ - double pixelsToRemoveFromOtherColumns = 0; - for (Column column : visibleColumns) { - /* - * We can't iterate over columnsToExpand, even though that - * would be convenient. This is because some column without - * an expand ratio might still have a min width - those - * wouldn't show up in that set. - */ - - double minWidth = getMinWidth(column); - final int columnIndex = visibleColumns.indexOf(column); - double currentWidth = columnSizes.get(columnIndex); - boolean hasAutoWidth = column.getWidth() < 0; - if (hasAutoWidth && currentWidth < minWidth) { - columnSizes.put(columnIndex, minWidth); - pixelsToRemoveFromOtherColumns += (minWidth - currentWidth); - minWidthsCausedReflows = true; - - /* - * Remove this column form the set if it exists. This - * way we make sure that it doesn't get shrunk in the - * next step. - */ - columnsToExpand.remove(column); - } - } - - /* - * Now we need to shrink the remaining columns according to - * their ratios. Recalculate the sum of remaining ratios. - */ - totalRatios = 0; - for (Column column : columnsToExpand) { - totalRatios += getExpandRatio(column, defaultExpandRatios); - } - final double pixelsToRemovePerRatio = pixelsToRemoveFromOtherColumns - / totalRatios; - for (Column column : columnsToExpand) { - final double pixelsToRemove = pixelsToRemovePerRatio - * getExpandRatio(column, defaultExpandRatios); - int colIndex = visibleColumns.indexOf(column); - columnSizes.put(colIndex, columnSizes.get(colIndex) - - pixelsToRemove); - } - - } while (minWidthsCausedReflows); - - // Finally set all the column sizes. - setColumnSizes(columnSizes); - } - - private void setColumnSizes(Map columnSizes) { - // Set all widths at once - escalator.getColumnConfiguration().setColumnWidths(columnSizes); - } - - private int getExpandRatio(Column column, - boolean defaultExpandRatios) { - int expandRatio = column.getExpandRatio(); - if (expandRatio > 0) { - return expandRatio; - } else if (expandRatio < 0) { - assert defaultExpandRatios : "No columns should've expanded"; - return 1; - } else { - assert false : "this method should've not been called at all if expandRatio is 0"; - return 0; - } - } - - /** - * Returns the maximum width of the column, or {@link Double#MAX_VALUE} - * if defined as negative. - */ - private double getMaxWidth(Column column) { - double maxWidth = column.getMaximumWidth(); - if (maxWidth >= 0) { - return maxWidth; - } else { - return Double.MAX_VALUE; - } - } - - /** - * Returns the minimum width of the column, or {@link Double#MIN_VALUE} - * if defined as negative. - */ - private double getMinWidth(Column column) { - double minWidth = column.getMinimumWidth(); - if (minWidth >= 0) { - return minWidth; - } else { - return Double.MIN_VALUE; - } - } - - /** - * Check whether the auto width calculation is currently scheduled. - * - * @return true if auto width calculation is currently - * scheduled - */ - public boolean isScheduled() { - return isScheduled; - } - } - - private class GridSpacerUpdater implements SpacerUpdater { - - private static final String STRIPE_CLASSNAME = "stripe"; - - private final Map elementToWidgetMap = new HashMap(); - - @Override - public void init(Spacer spacer) { - initTheming(spacer); - - int rowIndex = spacer.getRow(); - - Widget detailsWidget = null; - try { - detailsWidget = detailsGenerator.getDetails(rowIndex); - } catch (Throwable e) { - getLogger().log( - Level.SEVERE, - "Exception while generating details for row " - + rowIndex, e); - } - - final double spacerHeight; - Element spacerElement = spacer.getElement(); - if (detailsWidget == null) { - spacerElement.removeAllChildren(); - spacerHeight = DETAILS_ROW_INITIAL_HEIGHT; - } else { - Element element = detailsWidget.getElement(); - spacerElement.appendChild(element); - setParent(detailsWidget, Grid.this); - Widget previousWidget = elementToWidgetMap.put(element, - detailsWidget); - - assert previousWidget == null : "Overwrote a pre-existing widget on row " - + rowIndex + " without proper removal first."; - - /* - * Once we have the content properly inside the DOM, we should - * re-measure it to make sure that it's the correct height. - * - * This is rather tricky, since the row (tr) will get the - * height, but the spacer cell (td) has the borders, which - * should go on top of the previous row and next row. - */ - double contentHeight; - if (detailsGenerator instanceof HeightAwareDetailsGenerator) { - HeightAwareDetailsGenerator sadg = (HeightAwareDetailsGenerator) detailsGenerator; - contentHeight = sadg.getDetailsHeight(rowIndex); - } else { - contentHeight = WidgetUtil - .getRequiredHeightBoundingClientRectDouble(element); - } - double borderTopAndBottomHeight = WidgetUtil - .getBorderTopAndBottomThickness(spacerElement); - double measuredHeight = contentHeight - + borderTopAndBottomHeight; - assert getElement().isOrHasChild(spacerElement) : "The spacer element wasn't in the DOM during measurement, but was assumed to be."; - spacerHeight = measuredHeight; - } - - escalator.getBody().setSpacer(rowIndex, spacerHeight); - } - - @Override - public void destroy(Spacer spacer) { - Element spacerElement = spacer.getElement(); - - assert getElement().isOrHasChild(spacerElement) : "Trying " - + "to destroy a spacer that is not connected to this " - + "Grid's DOM. (row: " + spacer.getRow() + ", element: " - + spacerElement + ")"; - - Widget detailsWidget = elementToWidgetMap.remove(spacerElement - .getFirstChildElement()); - - if (detailsWidget != null) { - /* - * The widget may be null here if the previous generator - * returned a null widget. - */ - - assert spacerElement.getFirstChild() != null : "The " - + "details row to destroy did not contain a widget - " - + "probably removed by something else without " - + "permission? (row: " + spacer.getRow() - + ", element: " + spacerElement + ")"; - - setParent(detailsWidget, null); - spacerElement.removeAllChildren(); - } - } - - private void initTheming(Spacer spacer) { - Element spacerRoot = spacer.getElement(); - - if (spacer.getRow() % 2 == 1) { - spacerRoot.getParentElement().addClassName(STRIPE_CLASSNAME); - } else { - spacerRoot.getParentElement().removeClassName(STRIPE_CLASSNAME); - } - } - - } - - /** - * Sidebar displaying toggles for hidable columns and custom widgets - * provided by the application. - *

- * The button for opening the sidebar is automatically visible inside the - * grid, if it contains any column hiding options or custom widgets. The - * column hiding toggles and custom widgets become visible once the sidebar - * has been opened. - * - * @since 7.5.0 - */ - private static class Sidebar extends Composite implements HasEnabled { - - private final ClickHandler openCloseButtonHandler = new ClickHandler() { - - @Override - public void onClick(ClickEvent event) { - if (!isOpen()) { - open(); - } else { - close(); - } - } - }; - - private final FlowPanel rootContainer; - - private final FlowPanel content; - - private final MenuBar menuBar; - - private final Button openCloseButton; - - private final Grid grid; - - private Overlay overlay; - - private Sidebar(Grid grid) { - this.grid = grid; - - rootContainer = new FlowPanel(); - initWidget(rootContainer); - - openCloseButton = new Button(); - - openCloseButton.addClickHandler(openCloseButtonHandler); - - rootContainer.add(openCloseButton); - - content = new FlowPanel() { - @Override - public boolean remove(Widget w) { - // Check here to catch child.removeFromParent() calls - boolean removed = super.remove(w); - if (removed) { - updateVisibility(); - } - - return removed; - } - }; - - createOverlay(); - - menuBar = new MenuBar(true) { - - @Override - public MenuItem insertItem(MenuItem item, int beforeIndex) - throws IndexOutOfBoundsException { - if (getParent() == null) { - content.insert(this, 0); - updateVisibility(); - } - return super.insertItem(item, beforeIndex); - } - - @Override - public void removeItem(MenuItem item) { - super.removeItem(item); - if (getItems().isEmpty()) { - menuBar.removeFromParent(); - } - } - - @Override - public void onBrowserEvent(Event event) { - // selecting a item with enter will lose the focus and - // selected item, which means that further keyboard - // selection won't work unless we do this: - if (event.getTypeInt() == Event.ONKEYDOWN - && event.getKeyCode() == KeyCodes.KEY_ENTER) { - final MenuItem item = getSelectedItem(); - super.onBrowserEvent(event); - Scheduler.get().scheduleDeferred( - new ScheduledCommand() { - - @Override - public void execute() { - selectItem(item); - focus(); - } - }); - - } else { - super.onBrowserEvent(event); - } - } - - }; - KeyDownHandler keyDownHandler = new KeyDownHandler() { - - @Override - public void onKeyDown(KeyDownEvent event) { - if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) { - close(); - } - } - }; - openCloseButton.addDomHandler(keyDownHandler, - KeyDownEvent.getType()); - menuBar.addDomHandler(keyDownHandler, KeyDownEvent.getType()); - } - - /** - * Creates and initializes the overlay. - */ - private void createOverlay() { - overlay = GWT.create(Overlay.class); - overlay.setOwner(grid); - overlay.setAutoHideEnabled(true); - overlay.addStyleDependentName("popup"); - overlay.add(content); - overlay.addAutoHidePartner(rootContainer.getElement()); - overlay.addCloseHandler(new CloseHandler() { - @Override - public void onClose(CloseEvent event) { - removeStyleName("open"); - addStyleName("closed"); - } - }); - } - - /** - * Opens the sidebar if not yet opened. Opening the sidebar has no - * effect if it is empty. - */ - public void open() { - if (!isOpen() && isInDOM()) { - addStyleName("open"); - removeStyleName("closed"); - overlay.showRelativeTo(rootContainer); - } - } - - /** - * Closes the sidebar if not yet closed. - */ - public void close() { - overlay.hide(); - } - - /** - * Returns whether the sidebar is open or not. - * - * @return true if open, false if not - */ - public boolean isOpen() { - return overlay != null && overlay.isShowing(); - } - - @Override - public void setStylePrimaryName(String styleName) { - super.setStylePrimaryName(styleName); - overlay.setStylePrimaryName(styleName); - content.setStylePrimaryName(styleName + "-content"); - openCloseButton.setStylePrimaryName(styleName + "-button"); - if (isOpen()) { - addStyleName("open"); - removeStyleName("closed"); - } else { - removeStyleName("open"); - addStyleName("closed"); - } - } - - @Override - public void addStyleName(String style) { - super.addStyleName(style); - overlay.addStyleName(style); - } - - @Override - public void removeStyleName(String style) { - super.removeStyleName(style); - overlay.removeStyleName(style); - } - - private void setHeightToHeaderCellHeight() { - RowContainer header = grid.escalator.getHeader(); - if (header.getRowCount() == 0 - || !header.getRowElement(0).hasChildNodes()) { - getLogger() - .info("No header cell available when calculating sidebar button height"); - openCloseButton.setHeight(header.getDefaultRowHeight() + "px"); - - return; - } - - Element firstHeaderCell = header.getRowElement(0) - .getFirstChildElement(); - double height = WidgetUtil - .getRequiredHeightBoundingClientRectDouble(firstHeaderCell) - - (WidgetUtil.measureVerticalBorder(getElement()) / 2); - openCloseButton.setHeight(height + "px"); - } - - private void updateVisibility() { - final boolean hasWidgets = content.getWidgetCount() > 0; - final boolean isVisible = isInDOM(); - if (isVisible && !hasWidgets) { - Grid.setParent(this, null); - getElement().removeFromParent(); - } else if (!isVisible && hasWidgets) { - close(); - grid.getElement().appendChild(getElement()); - Grid.setParent(this, grid); - // border calculation won't work until attached - setHeightToHeaderCellHeight(); - } - } - - private boolean isInDOM() { - return getParent() != null; - } - - @Override - protected void onAttach() { - super.onAttach(); - // make sure the button will get correct height if the button should - // be visible when the grid is rendered the first time. - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - - @Override - public void execute() { - setHeightToHeaderCellHeight(); - } - }); - } - - @Override - public boolean isEnabled() { - return openCloseButton.isEnabled(); - } - - @Override - public void setEnabled(boolean enabled) { - if (!enabled && isOpen()) { - close(); - } - - openCloseButton.setEnabled(enabled); - } - } - - /** - * UI and functionality related to hiding columns with toggles in the - * sidebar. - */ - private final class ColumnHider { - - /** Map from columns to their hiding toggles, component might change */ - private HashMap, MenuItem> columnToHidingToggleMap = new HashMap, MenuItem>(); - - /** - * When column is being hidden with a toggle, do not refresh toggles for - * no reason. Also helps for keeping the keyboard navigation working. - */ - private boolean hidingColumn; - - private void updateColumnHidable(final Column column) { - if (column.isHidable()) { - MenuItem toggle = columnToHidingToggleMap.get(column); - if (toggle == null) { - toggle = createToggle(column); - } - toggle.setStyleName("hidden", column.isHidden()); - } else if (columnToHidingToggleMap.containsKey(column)) { - sidebar.menuBar.removeItem((columnToHidingToggleMap - .remove(column))); - } - updateTogglesOrder(); - } - - private MenuItem createToggle(final Column column) { - MenuItem toggle = new MenuItem(createHTML(column), true, - new ScheduledCommand() { - - @Override - public void execute() { - hidingColumn = true; - column.setHidden(!column.isHidden(), true); - hidingColumn = false; - } - }); - toggle.addStyleName("column-hiding-toggle"); - columnToHidingToggleMap.put(column, toggle); - return toggle; - } - - private String createHTML(Column column) { - final StringBuffer buf = new StringBuffer(); - buf.append("

"); - String caption = column.getHidingToggleCaption(); - if (caption == null) { - caption = column.headerCaption; - } - buf.append(caption); - buf.append("
"); - - return buf.toString(); - } - - private void updateTogglesOrder() { - if (!hidingColumn) { - int lastIndex = 0; - for (Column column : getColumns()) { - if (column.isHidable()) { - final MenuItem menuItem = columnToHidingToggleMap - .get(column); - sidebar.menuBar.removeItem(menuItem); - sidebar.menuBar.insertItem(menuItem, lastIndex++); - } - } - } - } - - private void updateHidingToggle(Column column) { - if (column.isHidable()) { - MenuItem toggle = columnToHidingToggleMap.get(column); - toggle.setHTML(createHTML(column)); - toggle.setStyleName("hidden", column.isHidden()); - } // else we can just ignore - } - - private void removeColumnHidingToggle(Column column) { - sidebar.menuBar.removeItem(columnToHidingToggleMap.get(column)); - } - - } - - /** - * Escalator used internally by grid to render the rows - */ - private Escalator escalator = GWT.create(Escalator.class); - - private final Header header = GWT.create(Header.class); - - private final Footer footer = GWT.create(Footer.class); - - private final Sidebar sidebar = new Sidebar(this); - - /** - * List of columns in the grid. Order defines the visible order. - */ - private List> columns = new ArrayList>(); - - /** - * The datasource currently in use. Note: it is null - * on initialization, but not after that. - */ - private DataSource dataSource; - - /** - * Currently available row range in DataSource. - */ - private Range currentDataAvailable = Range.withLength(0, 0); - - /** - * The number of frozen columns, 0 freezes the selection column if - * displayed, -1 also prevents selection col from freezing. - */ - private int frozenColumnCount = 0; - - /** - * Current sort order. The (private) sort() method reads this list to - * determine the order in which to present rows. - */ - private List sortOrder = new ArrayList(); - - private Renderer selectColumnRenderer = null; - - private SelectionColumn selectionColumn; - - private String rowStripeStyleName; - private String rowHasDataStyleName; - private String rowSelectedStyleName; - private String cellFocusStyleName; - private String rowFocusStyleName; - - /** - * Current selection model. - */ - private SelectionModel selectionModel; - - protected final CellFocusHandler cellFocusHandler; - - private final UserSorter sorter = new UserSorter(); - - private final Editor editor = GWT.create(Editor.class); - - private boolean dataIsBeingFetched = false; - - /** - * The cell a click event originated from - *

- * This is a workaround to make Chrome work like Firefox. In Chrome, - * normally if you start a drag on one cell and release on: - *

    - *
  • that same cell, the click event is that {@code }. - *
  • a cell on that same row, the click event is the parent {@code }. - *
  • a cell on another row, the click event is the table section ancestor - * ({@code }, {@code } or {@code }). - *
- * - * @see #onBrowserEvent(Event) - */ - private Cell cellOnPrevMouseDown; - - /** - * A scheduled command to re-evaluate the widths of all columns - * that have calculated widths. Most probably called because - * minwidth/maxwidth/expandratio has changed. - */ - private final AutoColumnWidthsRecalculator autoColumnWidthsRecalculator = new AutoColumnWidthsRecalculator(); - - private boolean enabled = true; - - private DetailsGenerator detailsGenerator = DetailsGenerator.NULL; - private GridSpacerUpdater gridSpacerUpdater = new GridSpacerUpdater(); - /** A set keeping track of the indices of all currently open details */ - private Set visibleDetails = new HashSet(); - - private boolean columnReorderingAllowed; - - private ColumnHider columnHider = new ColumnHider(); - - private DragAndDropHandler dndHandler = new DragAndDropHandler(); - - private AutoScroller autoScroller = new AutoScroller(this); - - private DragAndDropHandler.DragAndDropCallback headerCellDndCallback = new DragAndDropCallback() { - - private final AutoScrollerCallback autoScrollerCallback = new AutoScrollerCallback() { - - @Override - public void onAutoScroll(int scrollDiff) { - autoScrollX = scrollDiff; - onDragUpdate(null); - } - - @Override - public void onAutoScrollReachedMin() { - // make sure the drop marker is visible on the left - autoScrollX = 0; - updateDragDropMarker(clientX); - } - - @Override - public void onAutoScrollReachedMax() { - // make sure the drop marker is visible on the right - autoScrollX = 0; - updateDragDropMarker(clientX); - } - }; - /** - * Elements for displaying the dragged column(s) and drop marker - * properly - */ - private Element table; - private Element tableHeader; - /** Marks the column drop location */ - private Element dropMarker; - /** A copy of the dragged column(s), moves with cursor. */ - private Element dragElement; - /** Tracks index of the column whose left side the drop would occur */ - private int latestColumnDropIndex; - /** - * Map of possible drop positions for the column and the corresponding - * column index. - */ - private final TreeMap possibleDropPositions = new TreeMap(); - /** - * Makes sure that drag cancel doesn't cause anything unwanted like sort - */ - private HandlerRegistration columnSortPreventRegistration; - - private int clientX; - - /** How much the grid is being auto scrolled while dragging. */ - private int autoScrollX; - - /** Captures the value of the focused column before reordering */ - private int focusedColumnIndex; - - /** Offset caused by the drag and drop marker width */ - private double dropMarkerWidthOffset; - - private void initHeaderDragElementDOM() { - if (table == null) { - tableHeader = DOM.createTHead(); - dropMarker = DOM.createDiv(); - tableHeader.appendChild(dropMarker); - table = DOM.createTable(); - table.appendChild(tableHeader); - table.setClassName("header-drag-table"); - } - // update the style names on each run in case primary name has been - // modified - tableHeader.setClassName(escalator.getHeader().getElement() - .getClassName()); - dropMarker.setClassName(getStylePrimaryName() + "-drop-marker"); - int topOffset = 0; - for (int i = 0; i < eventCell.getRowIndex(); i++) { - topOffset += escalator.getHeader().getRowElement(i) - .getFirstChildElement().getOffsetHeight(); - } - tableHeader.getStyle().setTop(topOffset, Unit.PX); - - getElement().appendChild(table); - - dropMarkerWidthOffset = WidgetUtil - .getRequiredWidthBoundingClientRectDouble(dropMarker) / 2; - } - - @Override - public void onDragUpdate(Event e) { - if (e != null) { - clientX = WidgetUtil.getTouchOrMouseClientX(e); - autoScrollX = 0; - } - resolveDragElementHorizontalPosition(clientX); - updateDragDropMarker(clientX); - } - - private void updateDragDropMarker(final int clientX) { - final double scrollLeft = getScrollLeft(); - final double cursorXCoordinate = clientX - - escalator.getHeader().getElement().getAbsoluteLeft(); - final Entry cellEdgeOnRight = possibleDropPositions - .ceilingEntry(cursorXCoordinate); - final Entry cellEdgeOnLeft = possibleDropPositions - .floorEntry(cursorXCoordinate); - final double diffToRightEdge = cellEdgeOnRight == null ? Double.MAX_VALUE - : cellEdgeOnRight.getKey() - cursorXCoordinate; - final double diffToLeftEdge = cellEdgeOnLeft == null ? Double.MAX_VALUE - : cursorXCoordinate - cellEdgeOnLeft.getKey(); - - double dropMarkerLeft = 0 - scrollLeft; - if (diffToRightEdge > diffToLeftEdge) { - latestColumnDropIndex = cellEdgeOnLeft.getValue(); - dropMarkerLeft += cellEdgeOnLeft.getKey(); - } else { - latestColumnDropIndex = cellEdgeOnRight.getValue(); - dropMarkerLeft += cellEdgeOnRight.getKey(); - } - - dropMarkerLeft += autoScrollX; - - final double frozenColumnsWidth = autoScroller - .getFrozenColumnsWidth(); - final double rightBoundaryForDrag = getSidebarBoundaryComparedTo(dropMarkerLeft); - final int visibleColumns = getVisibleColumns().size(); - - // First check if the drop marker should move left because of the - // sidebar opening button. this only the case if the grid is - // scrolled to the right - if (latestColumnDropIndex == visibleColumns - && rightBoundaryForDrag < dropMarkerLeft - && dropMarkerLeft <= escalator.getInnerWidth()) { - dropMarkerLeft = rightBoundaryForDrag - dropMarkerWidthOffset; - } - - // Check if the drop marker shouldn't be shown at all - else if (dropMarkerLeft < frozenColumnsWidth - || dropMarkerLeft > Math.min(rightBoundaryForDrag, - escalator.getInnerWidth()) || dropMarkerLeft < 0) { - dropMarkerLeft = -10000000; - } - dropMarker.getStyle().setLeft(dropMarkerLeft, Unit.PX); - } - - private void resolveDragElementHorizontalPosition(final int clientX) { - double left = clientX - table.getAbsoluteLeft(); - - // Do not show the drag element beyond a spanned header cell - // limitation - final Double leftBound = possibleDropPositions.firstKey(); - final Double rightBound = possibleDropPositions.lastKey(); - final double scrollLeft = getScrollLeft(); - if (left + scrollLeft < leftBound) { - left = leftBound - scrollLeft + autoScrollX; - } else if (left + scrollLeft > rightBound) { - left = rightBound - scrollLeft + autoScrollX; - } - - // Do not show the drag element beyond the grid - final double sidebarBoundary = getSidebarBoundaryComparedTo(left); - final double gridBoundary = escalator.getInnerWidth(); - final double rightBoundary = Math - .min(sidebarBoundary, gridBoundary); - - // Do not show on left of the frozen columns (even if scrolled) - final int frozenColumnsWidth = (int) autoScroller - .getFrozenColumnsWidth(); - - left = Math.max(frozenColumnsWidth, Math.min(left, rightBoundary)); - - left -= dragElement.getClientWidth() / 2; - dragElement.getStyle().setLeft(left, Unit.PX); - } - - private boolean isSidebarOnDraggedRow() { - return eventCell.getRowIndex() == 0 && sidebar.isInDOM() - && !sidebar.isOpen(); - } - - /** - * Returns the sidebar left coordinate, in relation to the grid. Or - * Double.MAX_VALUE if it doesn't cause a boundary. - */ - private double getSidebarBoundaryComparedTo(double left) { - if (isSidebarOnDraggedRow()) { - double absoluteLeft = left + getElement().getAbsoluteLeft(); - double sidebarLeft = sidebar.getElement().getAbsoluteLeft(); - double diff = absoluteLeft - sidebarLeft; - - if (diff > 0) { - return left - diff; - } - } - return Double.MAX_VALUE; - } - - @Override - public boolean onDragStart(Event e) { - calculatePossibleDropPositions(); - - if (possibleDropPositions.isEmpty()) { - return false; - } - - initHeaderDragElementDOM(); - // needs to clone focus and sorting indicators too (UX) - dragElement = DOM.clone(eventCell.getElement(), true); - dragElement.getStyle().clearWidth(); - dropMarker.getStyle().setProperty("height", - dragElement.getStyle().getHeight()); - tableHeader.appendChild(dragElement); - // mark the column being dragged for styling - eventCell.getElement().addClassName("dragged"); - // mark the floating cell, for styling & testing - dragElement.addClassName("dragged-column-header"); - - // start the auto scroll handler - autoScroller.setScrollArea(60); - autoScroller.start(e, ScrollAxis.HORIZONTAL, autoScrollerCallback); - return true; - } - - @Override - public void onDragEnd() { - table.removeFromParent(); - dragElement.removeFromParent(); - eventCell.getElement().removeClassName("dragged"); - } - - @Override - public void onDrop() { - final int draggedColumnIndex = eventCell.getColumnIndex(); - final int colspan = header.getRow(eventCell.getRowIndex()) - .getCell(eventCell.getColumn()).getColspan(); - if (latestColumnDropIndex != draggedColumnIndex - && latestColumnDropIndex != (draggedColumnIndex + colspan)) { - List> columns = getColumns(); - List> reordered = new ArrayList>(); - if (draggedColumnIndex < latestColumnDropIndex) { - reordered.addAll(columns.subList(0, draggedColumnIndex)); - reordered.addAll(columns.subList(draggedColumnIndex - + colspan, latestColumnDropIndex)); - reordered.addAll(columns.subList(draggedColumnIndex, - draggedColumnIndex + colspan)); - reordered.addAll(columns.subList(latestColumnDropIndex, - columns.size())); - } else { - reordered.addAll(columns.subList(0, latestColumnDropIndex)); - reordered.addAll(columns.subList(draggedColumnIndex, - draggedColumnIndex + colspan)); - reordered.addAll(columns.subList(latestColumnDropIndex, - draggedColumnIndex)); - reordered.addAll(columns.subList(draggedColumnIndex - + colspan, columns.size())); - } - reordered.remove(selectionColumn); // since setColumnOrder will - // add it anyway! - - // capture focused cell column before reorder - Cell focusedCell = cellFocusHandler.getFocusedCell(); - if (focusedCell != null) { - // take hidden columns into account - focusedColumnIndex = getColumns().indexOf( - getVisibleColumn(focusedCell.getColumn())); - } - - Column[] array = reordered.toArray(new Column[reordered - .size()]); - setColumnOrder(array); - transferCellFocusOnDrop(); - } // else no reordering - } - - private void transferCellFocusOnDrop() { - final Cell focusedCell = cellFocusHandler.getFocusedCell(); - if (focusedCell != null) { - final int focusedColumnIndexDOM = focusedCell.getColumn(); - final int focusedRowIndex = focusedCell.getRow(); - final int draggedColumnIndex = eventCell.getColumnIndex(); - // transfer focus if it was effected by the new column order - final RowContainer rowContainer = escalator - .findRowContainer(focusedCell.getElement()); - if (focusedColumnIndex == draggedColumnIndex) { - // move with the dragged column - int adjustedDropIndex = latestColumnDropIndex > draggedColumnIndex ? latestColumnDropIndex - 1 - : latestColumnDropIndex; - // remove hidden columns from indexing - adjustedDropIndex = getVisibleColumns().indexOf( - getColumn(adjustedDropIndex)); - cellFocusHandler.setCellFocus(focusedRowIndex, - adjustedDropIndex, rowContainer); - } else if (latestColumnDropIndex <= focusedColumnIndex - && draggedColumnIndex > focusedColumnIndex) { - cellFocusHandler.setCellFocus(focusedRowIndex, - focusedColumnIndexDOM + 1, rowContainer); - } else if (latestColumnDropIndex > focusedColumnIndex - && draggedColumnIndex < focusedColumnIndex) { - cellFocusHandler.setCellFocus(focusedRowIndex, - focusedColumnIndexDOM - 1, rowContainer); - } - } - } - - @Override - public void onDragCancel() { - // cancel next click so that we may prevent column sorting if - // mouse was released on top of the dragged cell - if (columnSortPreventRegistration == null) { - columnSortPreventRegistration = Event - .addNativePreviewHandler(new NativePreviewHandler() { - - @Override - public void onPreviewNativeEvent( - NativePreviewEvent event) { - if (event.getTypeInt() == Event.ONCLICK) { - event.cancel(); - event.getNativeEvent().preventDefault(); - columnSortPreventRegistration - .removeHandler(); - columnSortPreventRegistration = null; - } - } - }); - } - autoScroller.stop(); - } - - /** - * Returns the amount of frozen columns. The selection column is always - * considered frozen, since it can't be moved. - */ - private int getSelectionAndFrozenColumnCount() { - // no matter if selection column is frozen or not, it is considered - // frozen for column dnd reorder - if (getSelectionModel().getSelectionColumnRenderer() != null) { - return Math.max(0, getFrozenColumnCount()) + 1; - } else { - return Math.max(0, getFrozenColumnCount()); - } - } - - @SuppressWarnings("boxing") - private void calculatePossibleDropPositions() { - possibleDropPositions.clear(); - - final int draggedColumnIndex = eventCell.getColumnIndex(); - final StaticRow draggedCellRow = header.getRow(eventCell - .getRowIndex()); - final int draggedColumnRightIndex = draggedColumnIndex - + draggedCellRow.getCell(eventCell.getColumn()) - .getColspan(); - final int frozenColumns = getSelectionAndFrozenColumnCount(); - final Range draggedCellRange = Range.between(draggedColumnIndex, - draggedColumnRightIndex); - /* - * If the dragged cell intersects with a spanned cell in any other - * header or footer row, then the drag is limited inside that - * spanned cell. The same rules apply: the cell can't be dropped - * inside another spanned cell. The left and right bounds keep track - * of the edges of the most limiting spanned cell. - */ - int leftBound = -1; - int rightBound = getColumnCount() + 1; - - final HashSet unavailableColumnDropIndices = new HashSet(); - final List> rows = new ArrayList>(); - rows.addAll(header.getRows()); - rows.addAll(footer.getRows()); - for (StaticRow row : rows) { - if (!row.hasSpannedCells()) { - continue; - } - final boolean isDraggedCellRow = row.equals(draggedCellRow); - for (int cellColumnIndex = frozenColumns; cellColumnIndex < getColumnCount(); cellColumnIndex++) { - StaticCell cell = row.getCell(getColumn(cellColumnIndex)); - int colspan = cell.getColspan(); - if (colspan <= 1) { - continue; - } - final int cellColumnRightIndex = cellColumnIndex + colspan; - final Range cellRange = Range.between(cellColumnIndex, - cellColumnRightIndex); - final boolean intersects = draggedCellRange - .intersects(cellRange); - if (intersects && !isDraggedCellRow) { - // if the currently iterated cell is inside or same as - // the dragged cell, then it doesn't restrict the drag - if (cellRange.isSubsetOf(draggedCellRange)) { - cellColumnIndex = cellColumnRightIndex - 1; - continue; - } - /* - * if the dragged cell is a spanned cell and it crosses - * with the currently iterated cell without sharing - * either start or end then not possible to drag the - * cell. - */ - if (!draggedCellRange.isSubsetOf(cellRange)) { - return; - } - // the spanned cell overlaps the dragged cell (but is - // not the dragged cell) - if (cellColumnIndex <= draggedColumnIndex - && cellColumnIndex > leftBound) { - leftBound = cellColumnIndex; - } - if (cellColumnRightIndex < rightBound) { - rightBound = cellColumnRightIndex; - } - cellColumnIndex = cellColumnRightIndex - 1; - } - - else { // can't drop inside a spanned cell, or this is the - // dragged cell - while (colspan > 1) { - cellColumnIndex++; - colspan--; - unavailableColumnDropIndices.add(cellColumnIndex); - } - } - } - } - - if (leftBound == (rightBound - 1)) { - return; - } - - double position = autoScroller.getFrozenColumnsWidth(); - // iterate column indices and add possible drop positions - for (int i = frozenColumns; i < getColumnCount(); i++) { - Column column = getColumn(i); - if (!unavailableColumnDropIndices.contains(i) - && !column.isHidden()) { - if (leftBound != -1) { - if (i >= leftBound && i <= rightBound) { - possibleDropPositions.put(position, i); - } - } else { - possibleDropPositions.put(position, i); - } - } - position += column.getWidthActual(); - } - - if (leftBound == -1) { - // add the right side of the last column as columns.size() - possibleDropPositions.put(position, getColumnCount()); - } - } - - }; - - /** - * Enumeration for easy setting of selection mode. - */ - public enum SelectionMode { - - /** - * Shortcut for {@link SelectionModelSingle}. - */ - SINGLE { - - @Override - protected SelectionModel createModel() { - return GWT.create(SelectionModelSingle.class); - } - }, - - /** - * Shortcut for {@link SelectionModelMulti}. - */ - MULTI { - - @Override - protected SelectionModel createModel() { - return GWT.create(SelectionModelMulti.class); - } - }, - - /** - * Shortcut for {@link SelectionModelNone}. - */ - NONE { - - @Override - protected SelectionModel createModel() { - return GWT.create(SelectionModelNone.class); - } - }; - - protected abstract SelectionModel createModel(); - } - - /** - * Base class for grid columns internally used by the Grid. The user should - * use {@link Column} when creating new columns. - * - * @param - * the column type - * - * @param - * the row type - */ - public static abstract class Column { - - /** - * Default renderer for GridColumns. Renders everything into text - * through {@link Object#toString()}. - */ - private final class DefaultTextRenderer implements Renderer { - boolean warned = false; - private final String DEFAULT_RENDERER_WARNING = "This column uses a dummy default TextRenderer. " - + "A more suitable renderer should be set using the setRenderer() method."; - - @Override - public void render(RendererCellReference cell, Object data) { - if (!warned && !(data instanceof String)) { - getLogger().warning( - Column.this.toString() + ": " - + DEFAULT_RENDERER_WARNING); - warned = true; - } - - final String text; - if (data == null) { - text = ""; - } else { - text = data.toString(); - } - - cell.getElement().setInnerText(text); - } - } - - /** - * the column is associated with - */ - private Grid grid; - - /** - * Width of column in pixels as {@link #setWidth(double)} has been - * called - */ - private double widthUser = GridConstants.DEFAULT_COLUMN_WIDTH_PX; - - /** - * Renderer for rendering a value into the cell - */ - private Renderer bodyRenderer; - - private boolean sortable = false; - - private boolean editable = true; - - private boolean resizable = true; - - private boolean hidden = false; - - private boolean hidable = false; - - private String headerCaption = ""; - - private String hidingToggleCaption = null; - - private double minimumWidthPx = GridConstants.DEFAULT_MIN_WIDTH; - private double maximumWidthPx = GridConstants.DEFAULT_MAX_WIDTH; - private int expandRatio = GridConstants.DEFAULT_EXPAND_RATIO; - - /** - * Constructs a new column with a simple TextRenderer. - */ - public Column() { - setRenderer(new DefaultTextRenderer()); - } - - /** - * Constructs a new column with a simple TextRenderer. - * - * @param caption - * The header caption for this column - * - * @throws IllegalArgumentException - * if given header caption is null - */ - public Column(String caption) throws IllegalArgumentException { - this(); - setHeaderCaption(caption); - } - - /** - * Constructs a new column with a custom renderer. - * - * @param renderer - * The renderer to use for rendering the cells - * - * @throws IllegalArgumentException - * if given Renderer is null - */ - public Column(Renderer renderer) - throws IllegalArgumentException { - setRenderer(renderer); - } - - /** - * Constructs a new column with a custom renderer. - * - * @param renderer - * The renderer to use for rendering the cells - * @param caption - * The header caption for this column - * - * @throws IllegalArgumentException - * if given Renderer or header caption is null - */ - public Column(String caption, Renderer renderer) - throws IllegalArgumentException { - this(renderer); - setHeaderCaption(caption); - } - - /** - * Internally used by the grid to set itself - * - * @param grid - */ - private void setGrid(Grid grid) { - if (this.grid != null && grid != null) { - // Trying to replace grid - throw new IllegalStateException("Column already is attached " - + "to a grid. Remove the column first from the grid " - + "and then add it. (in: " + toString() + ")"); - } - - if (this.grid != null) { - this.grid.recalculateColumnWidths(); - } - this.grid = grid; - if (this.grid != null) { - this.grid.recalculateColumnWidths(); - } - } - - /** - * Sets a header caption for this column. - * - * @param caption - * The header caption for this column - * @return the column itself - * - */ - public Column setHeaderCaption(String caption) { - if (caption == null) { - caption = ""; - } - - if (!this.headerCaption.equals(caption)) { - this.headerCaption = caption; - if (grid != null) { - updateHeader(); - } - } - - return this; - } - - /** - * Returns the current header caption for this column - * - * @since 7.6 - * @return the header caption string - */ - public String getHeaderCaption() { - return headerCaption; - } - - private void updateHeader() { - HeaderRow row = grid.getHeader().getDefaultRow(); - if (row != null) { - row.getCell(this).setText(headerCaption); - if (isHidable()) { - grid.columnHider.updateHidingToggle(this); - } - } - } - - /** - * Returns the data that should be rendered into the cell. By default - * returning Strings and Widgets are supported. If the return type is a - * String then it will be treated as preformatted text. - *

- * To support other types you will need to pass a custom renderer to the - * column via the column constructor. - * - * @param row - * The row object that provides the cell content. - * - * @return The cell content - */ - public abstract C getValue(T row); - - /** - * The renderer to render the cell with. By default renders the data as - * a String or adds the widget into the cell if the column type is of - * widget type. - * - * @return The renderer to render the cell content with - */ - public Renderer getRenderer() { - return bodyRenderer; - } - - /** - * Sets a custom {@link Renderer} for this column. - * - * @param renderer - * The renderer to use for rendering the cells - * @return the column itself - * - * @throws IllegalArgumentException - * if given Renderer is null - */ - public Column setRenderer(Renderer renderer) - throws IllegalArgumentException { - if (renderer == null) { - throw new IllegalArgumentException("Renderer cannot be null."); - } - - if (renderer != bodyRenderer) { - // Variables used to restore removed column. - boolean columnRemoved = false; - double widthInConfiguration = 0.0d; - ColumnConfiguration conf = null; - int index = 0; - - if (grid != null - && (bodyRenderer instanceof WidgetRenderer || renderer instanceof WidgetRenderer)) { - // Column needs to be recreated. - index = grid.getColumns().indexOf(this); - conf = grid.escalator.getColumnConfiguration(); - widthInConfiguration = conf.getColumnWidth(index); - - conf.removeColumns(index, 1); - columnRemoved = true; - } - - // Complex renderers need to be destroyed. - if (bodyRenderer instanceof ComplexRenderer) { - ((ComplexRenderer) bodyRenderer).destroy(); - } - - bodyRenderer = renderer; - - if (columnRemoved) { - // Restore the column. - conf.insertColumns(index, 1); - conf.setColumnWidth(index, widthInConfiguration); - } - - if (grid != null) { - grid.refreshBody(); - } - } - return this; - } - - /** - * Sets the pixel width of the column. Use a negative value for the grid - * to autosize column based on content and available space. - *

- * This action is done "finally", once the current execution loop - * returns. This is done to reduce overhead of unintentionally always - * recalculate all columns, when modifying several columns at once. - *

- * If the column is currently {@link #isHidden() hidden}, then this set - * width has effect only once the column has been made visible again. - * - * @param pixels - * the width in pixels or negative for auto sizing - */ - public Column setWidth(double pixels) { - if (!WidgetUtil.pixelValuesEqual(widthUser, pixels)) { - widthUser = pixels; - if (!isHidden()) { - scheduleColumnWidthRecalculator(); - } - } - return this; - } - - void doSetWidth(double pixels) { - assert !isHidden() : "applying width for a hidden column"; - if (grid != null) { - int index = grid.getVisibleColumns().indexOf(this); - ColumnConfiguration conf = grid.escalator - .getColumnConfiguration(); - conf.setColumnWidth(index, pixels); - } - } - - /** - * Returns the pixel width of the column as given by the user. - *

- * Note: If a negative value was given to - * {@link #setWidth(double)}, that same negative value is returned here. - *

- * Note: Returns the value, even if the column is currently - * {@link #isHidden() hidden}. - * - * @return pixel width of the column, or a negative number if the column - * width has been automatically calculated. - * @see #setWidth(double) - * @see #getWidthActual() - */ - public double getWidth() { - return widthUser; - } - - /** - * Returns the effective pixel width of the column. - *

- * This differs from {@link #getWidth()} only when the column has been - * automatically resized, or when the column is currently - * {@link #isHidden() hidden}, when the value is 0. - * - * @return pixel width of the column. - */ - public double getWidthActual() { - if (isHidden()) { - return 0; - } - return grid.escalator.getColumnConfiguration() - .getColumnWidthActual( - grid.getVisibleColumns().indexOf(this)); - } - - void reapplyWidth() { - scheduleColumnWidthRecalculator(); - } - - /** - * Sets whether the column should be sortable by the user. The grid can - * be sorted by a sortable column by clicking or tapping the column's - * default header. Programmatic sorting using the Grid#sort methods is - * not affected by this setting. - * - * @param sortable - * {@code true} if the user should be able to sort the - * column, {@code false} otherwise - * @return the column itself - */ - public Column setSortable(boolean sortable) { - if (this.sortable != sortable) { - this.sortable = sortable; - if (grid != null) { - grid.refreshHeader(); - } - } - return this; - } - - /** - * Returns whether the user can sort the grid by this column. - *

- * Note: it is possible to sort by this column programmatically - * using the Grid#sort methods regardless of the returned value. - * - * @return {@code true} if the column is sortable by the user, - * {@code false} otherwise - */ - public boolean isSortable() { - return sortable; - } - - /** - * Sets whether this column can be resized by the user. - * - * @since 7.6 - * - * @param resizable - * {@code true} if this column should be resizable, - * {@code false} otherwise - */ - public Column setResizable(boolean resizable) { - if (this.resizable != resizable) { - this.resizable = resizable; - if (grid != null) { - grid.refreshHeader(); - } - } - return this; - } - - /** - * Returns whether this column can be resized by the user. Default is - * {@code true}. - *

- * Note: the column can be programmatically resized using - * {@link #setWidth(double)} and {@link #setWidthUndefined()} regardless - * of the returned value. - * - * @since 7.6 - * - * @return {@code true} if this column is resizable, {@code false} - * otherwise - */ - public boolean isResizable() { - return resizable; - } - - /** - * Hides or shows the column. By default columns are visible before - * explicitly hiding them. - * - * @since 7.5.0 - * @param hidden - * true to hide the column, false - * to show - */ - public Column setHidden(boolean hidden) { - setHidden(hidden, false); - return this; - } - - private void setHidden(boolean hidden, boolean userOriginated) { - if (this.hidden != hidden) { - if (hidden) { - grid.escalator.getColumnConfiguration().removeColumns( - grid.getVisibleColumns().indexOf(this), 1); - this.hidden = hidden; - } else { - this.hidden = hidden; - - final int columnIndex = grid.getVisibleColumns().indexOf( - this); - grid.escalator.getColumnConfiguration().insertColumns( - columnIndex, 1); - - // make sure column is set to frozen if it needs to be, - // escalator doesn't handle situation where the added column - // would be the last frozen column - int gridFrozenColumns = grid.getFrozenColumnCount(); - int escalatorFrozenColumns = grid.escalator - .getColumnConfiguration().getFrozenColumnCount(); - if (gridFrozenColumns > escalatorFrozenColumns - && escalatorFrozenColumns == columnIndex) { - grid.escalator.getColumnConfiguration() - .setFrozenColumnCount(++escalatorFrozenColumns); - } - } - grid.columnHider.updateHidingToggle(this); - grid.header.updateColSpans(); - grid.footer.updateColSpans(); - scheduleColumnWidthRecalculator(); - this.grid.fireEvent(new ColumnVisibilityChangeEvent(this, - hidden, userOriginated)); - } - } - - /** - * Returns whether this column is hidden. Default is {@code false}. - * - * @since 7.5.0 - * @return {@code true} if the column is currently hidden, {@code false} - * otherwise - */ - public boolean isHidden() { - return hidden; - } - - /** - * Set whether it is possible for the user to hide this column or not. - * Default is {@code false}. - *

- * Note: it is still possible to hide the column - * programmatically using {@link #setHidden(boolean)}. - * - * @since 7.5.0 - * @param hidable - * {@code true} the user can hide this column, {@code false} - * otherwise - */ - public Column setHidable(boolean hidable) { - if (this.hidable != hidable) { - this.hidable = hidable; - grid.columnHider.updateColumnHidable(this); - } - return this; - } - - /** - * Is it possible for the the user to hide this column. Default is - * {@code false}. - *

- * Note: the column can be programmatically hidden using - * {@link #setHidden(boolean)} regardless of the returned value. - * - * @since 7.5.0 - * @return true if the user can hide the column, - * false if not - */ - public boolean isHidable() { - return hidable; - } - - /** - * Sets the hiding toggle's caption for this column. Shown in the toggle - * for this column in the grid's sidebar when the column is - * {@link #isHidable() hidable}. - *

- * The default value is null. In this case the header - * caption is used, see {@link #setHeaderCaption(String)}. - * - * @since 7.5.0 - * @param hidingToggleCaption - * the caption for the hiding toggle for this column - */ - public Column setHidingToggleCaption(String hidingToggleCaption) { - this.hidingToggleCaption = hidingToggleCaption; - if (isHidable()) { - grid.columnHider.updateHidingToggle(this); - } - return this; - } - - /** - * Gets the hiding toggle caption for this column. - * - * @since 7.5.0 - * @see #setHidingToggleCaption(String) - * @return the hiding toggle's caption for this column - */ - public String getHidingToggleCaption() { - return hidingToggleCaption; - } - - @Override - public String toString() { - String details = ""; - - if (headerCaption != null && !headerCaption.isEmpty()) { - details += "header:\"" + headerCaption + "\" "; - } else { - details += "header:empty "; - } - - if (grid != null) { - int index = grid.getColumns().indexOf(this); - if (index != -1) { - details += "attached:#" + index + " "; - } else { - details += "attached:unindexed "; - } - } else { - details += "detached "; - } - - details += "sortable:" + sortable + " "; - - return getClass().getSimpleName() + "[" + details.trim() + "]"; - } - - /** - * Sets the minimum width for this column. - *

- * This defines the minimum guaranteed pixel width of the column - * when it is set to expand. - *

- * This action is done "finally", once the current execution loop - * returns. This is done to reduce overhead of unintentionally always - * recalculate all columns, when modifying several columns at once. - * - * @param pixels - * the minimum width - * @return this column - */ - public Column setMinimumWidth(double pixels) { - final double maxwidth = getMaximumWidth(); - if (pixels >= 0 && pixels > maxwidth && maxwidth >= 0) { - throw new IllegalArgumentException("New minimum width (" - + pixels + ") was greater than maximum width (" - + maxwidth + ")"); - } - - if (minimumWidthPx != pixels) { - minimumWidthPx = pixels; - scheduleColumnWidthRecalculator(); - } - return this; - } - - /** - * Sets the maximum width for this column. - *

- * This defines the maximum allowed pixel width of the column - * when it is set to expand. - *

- * This action is done "finally", once the current execution loop - * returns. This is done to reduce overhead of unintentionally always - * recalculate all columns, when modifying several columns at once. - * - * @param pixels - * the maximum width - * @param immediately - * true if the widths should be executed - * immediately (ignoring lazy loading completely), or - * false if the command should be run after a - * while (duplicate non-immediately invocations are ignored). - * @return this column - */ - public Column setMaximumWidth(double pixels) { - final double minwidth = getMinimumWidth(); - if (pixels >= 0 && pixels < minwidth && minwidth >= 0) { - throw new IllegalArgumentException("New maximum width (" - + pixels + ") was less than minimum width (" + minwidth - + ")"); - } - - if (maximumWidthPx != pixels) { - maximumWidthPx = pixels; - scheduleColumnWidthRecalculator(); - } - return this; - } - - /** - * Sets the ratio with which the column expands. - *

- * By default, all columns expand equally (treated as if all of them had - * an expand ratio of 1). Once at least one column gets a defined expand - * ratio, the implicit expand ratio is removed, and only the defined - * expand ratios are taken into account. - *

- * If a column has a defined width ({@link #setWidth(double)}), it - * overrides this method's effects. - *

- * Example: A grid with three columns, with expand ratios 0, 1 - * and 2, respectively. The column with a ratio of 0 is exactly - * as wide as its contents requires. The column with a ratio of - * 1 is as wide as it needs, plus a third of any excess - * space, bceause we have 3 parts total, and this column - * reservs only one of those. The column with a ratio of 2, is as wide - * as it needs to be, plus two thirds of the excess - * width. - *

- * This action is done "finally", once the current execution loop - * returns. This is done to reduce overhead of unintentionally always - * recalculate all columns, when modifying several columns at once. - * - * @param expandRatio - * the expand ratio of this column. {@code 0} to not have it - * expand at all. A negative number to clear the expand - * value. - * @return this column - */ - public Column setExpandRatio(int ratio) { - if (expandRatio != ratio) { - expandRatio = ratio; - scheduleColumnWidthRecalculator(); - } - return this; - } - - /** - * Clears the column's expand ratio. - *

- * Same as calling {@link #setExpandRatio(int) setExpandRatio(-1)} - * - * @return this column - */ - public Column clearExpandRatio() { - return setExpandRatio(-1); - } - - /** - * Gets the minimum width for this column. - * - * @return the minimum width for this column - * @see #setMinimumWidth(double) - */ - public double getMinimumWidth() { - return minimumWidthPx; - } - - /** - * Gets the maximum width for this column. - * - * @return the maximum width for this column - * @see #setMaximumWidth(double) - */ - public double getMaximumWidth() { - return maximumWidthPx; - } - - /** - * Gets the expand ratio for this column. - * - * @return the expand ratio for this column - * @see #setExpandRatio(int) - */ - public int getExpandRatio() { - return expandRatio; - } - - /** - * Sets whether the values in this column should be editable by the user - * when the row editor is active. By default columns are editable. - * - * @param editable - * {@code true} to set this column editable, {@code false} - * otherwise - * @return this column - * - * @throws IllegalStateException - * if the editor is currently active - * - * @see Grid#editRow(int) - * @see Grid#isEditorActive() - */ - public Column setEditable(boolean editable) { - if (editable != this.editable && grid.isEditorActive()) { - throw new IllegalStateException( - "Cannot change column editable status while the editor is active"); - } - this.editable = editable; - return this; - } - - /** - * Returns whether the values in this column are editable by the user - * when the row editor is active. - * - * @return {@code true} if this column is editable, {@code false} - * otherwise - * - * @see #setEditable(boolean) - */ - public boolean isEditable() { - return editable; - } - - private void scheduleColumnWidthRecalculator() { - if (grid != null) { - grid.recalculateColumnWidths(); - } else { - /* - * NOOP - * - * Since setGrid() will call reapplyWidths as the colum is - * attached to a grid, it will call setWidth, which, in turn, - * will call this method again. Therefore, it's guaranteed that - * the recalculation is scheduled eventually, once the column is - * attached to a grid. - */ - } - } - - /** - * Resets the default header cell contents to column header captions. - * - * @since 7.5.1 - * @param cell - * default header cell for this column - */ - protected void setDefaultHeaderContent(HeaderCell cell) { - cell.setText(headerCaption); - } - } - - protected class BodyUpdater implements EscalatorUpdater { - - @Override - public void preAttach(Row row, Iterable cellsToAttach) { - int rowIndex = row.getRow(); - rowReference.set(rowIndex, getDataSource().getRow(rowIndex), - row.getElement()); - for (FlyweightCell cell : cellsToAttach) { - Renderer renderer = findRenderer(cell); - if (renderer instanceof ComplexRenderer) { - try { - Column column = getVisibleColumn(cell.getColumn()); - rendererCellReference.set(cell, - getColumns().indexOf(column), column); - ((ComplexRenderer) renderer) - .init(rendererCellReference); - } catch (RuntimeException e) { - getLogger().log( - Level.SEVERE, - "Error initing cell in column " - + cell.getColumn(), e); - } - } - } - } - - @Override - public void postAttach(Row row, Iterable attachedCells) { - for (FlyweightCell cell : attachedCells) { - Renderer renderer = findRenderer(cell); - if (renderer instanceof WidgetRenderer) { - try { - WidgetRenderer widgetRenderer = (WidgetRenderer) renderer; - - Widget widget = widgetRenderer.createWidget(); - assert widget != null : "WidgetRenderer.createWidget() returned null. It should return a widget."; - assert widget.getParent() == null : "WidgetRenderer.createWidget() returned a widget which already is attached."; - assert cell.getElement().getChildCount() == 0 : "Cell content should be empty when adding Widget"; - - // Physical attach - cell.getElement().appendChild(widget.getElement()); - - // Logical attach - setParent(widget, Grid.this); - } catch (RuntimeException e) { - getLogger().log( - Level.SEVERE, - "Error attaching child widget in column " - + cell.getColumn(), e); - } - } - } - } - - @Override - public void update(Row row, Iterable cellsToUpdate) { - int rowIndex = row.getRow(); - TableRowElement rowElement = row.getElement(); - T rowData = dataSource.getRow(rowIndex); - - boolean hasData = rowData != null; - - /* - * TODO could be more efficient to build a list of all styles that - * should be used and update the element only once instead of - * attempting to update only the ones that have changed. - */ - - // Assign stylename for rows with data - boolean usedToHaveData = rowElement - .hasClassName(rowHasDataStyleName); - - if (usedToHaveData != hasData) { - setStyleName(rowElement, rowHasDataStyleName, hasData); - } - - boolean isEvenIndex = (row.getRow() % 2 == 0); - setStyleName(rowElement, rowStripeStyleName, !isEvenIndex); - - rowReference.set(rowIndex, rowData, rowElement); - - if (hasData) { - setStyleName(rowElement, rowSelectedStyleName, - isSelected(rowData)); - - if (rowStyleGenerator != null) { - try { - String rowStylename = rowStyleGenerator - .getStyle(rowReference); - setCustomStyleName(rowElement, rowStylename); - } catch (RuntimeException e) { - getLogger().log( - Level.SEVERE, - "Error generating styles for row " - + row.getRow(), e); - } - } else { - // Remove in case there was a generator previously - setCustomStyleName(rowElement, null); - } - } else if (usedToHaveData) { - setStyleName(rowElement, rowSelectedStyleName, false); - - setCustomStyleName(rowElement, null); - } - - cellFocusHandler.updateFocusedRowStyle(row); - - for (FlyweightCell cell : cellsToUpdate) { - Column column = getVisibleColumn(cell.getColumn()); - final int columnIndex = getColumns().indexOf(column); - - assert column != null : "Column was not found from cell (" - + cell.getColumn() + "," + cell.getRow() + ")"; - - cellFocusHandler.updateFocusedCellStyle(cell, - escalator.getBody()); - - if (hasData && cellStyleGenerator != null) { - try { - cellReference - .set(cell.getColumn(), columnIndex, column); - String generatedStyle = cellStyleGenerator - .getStyle(cellReference); - setCustomStyleName(cell.getElement(), generatedStyle); - } catch (RuntimeException e) { - getLogger().log( - Level.SEVERE, - "Error generating style for cell in column " - + cell.getColumn(), e); - } - } else if (hasData || usedToHaveData) { - setCustomStyleName(cell.getElement(), null); - } - - Renderer renderer = column.getRenderer(); - - try { - rendererCellReference.set(cell, columnIndex, column); - if (renderer instanceof ComplexRenderer) { - // Hide cell content if needed - ComplexRenderer clxRenderer = (ComplexRenderer) renderer; - if (hasData) { - if (!usedToHaveData) { - // Prepare cell for rendering - clxRenderer.setContentVisible( - rendererCellReference, true); - } - - Object value = column.getValue(rowData); - clxRenderer.render(rendererCellReference, value); - - } else { - // Prepare cell for no data - clxRenderer.setContentVisible( - rendererCellReference, false); - } - - } else if (hasData) { - // Simple renderers just render - Object value = column.getValue(rowData); - renderer.render(rendererCellReference, value); - - } else { - // Clear cell if there is no data - cell.getElement().removeAllChildren(); - } - } catch (RuntimeException e) { - getLogger().log( - Level.SEVERE, - "Error rendering cell in column " - + cell.getColumn(), e); - } - } - } - - @Override - public void preDetach(Row row, Iterable cellsToDetach) { - for (FlyweightCell cell : cellsToDetach) { - Renderer renderer = findRenderer(cell); - if (renderer instanceof WidgetRenderer) { - try { - Widget w = WidgetUtil.findWidget(cell.getElement() - .getFirstChildElement(), null); - if (w != null) { - - // Logical detach - setParent(w, null); - - // Physical detach - cell.getElement().removeChild(w.getElement()); - } - } catch (RuntimeException e) { - getLogger().log( - Level.SEVERE, - "Error detaching widget in column " - + cell.getColumn(), e); - } - } - } - } - - @Override - public void postDetach(Row row, Iterable detachedCells) { - int rowIndex = row.getRow(); - // Passing null row data since it might not exist in the data source - // any more - rowReference.set(rowIndex, null, row.getElement()); - for (FlyweightCell cell : detachedCells) { - Renderer renderer = findRenderer(cell); - if (renderer instanceof ComplexRenderer) { - try { - Column column = getVisibleColumn(cell.getColumn()); - rendererCellReference.set(cell, - getColumns().indexOf(column), column); - ((ComplexRenderer) renderer) - .destroy(rendererCellReference); - } catch (RuntimeException e) { - getLogger().log( - Level.SEVERE, - "Error destroying cell in column " - + cell.getColumn(), e); - } - } - } - } - } - - protected class StaticSectionUpdater implements EscalatorUpdater { - - private StaticSection section; - private RowContainer container; - - public StaticSectionUpdater(StaticSection section, - RowContainer container) { - super(); - this.section = section; - this.container = container; - } - - @Override - public void update(Row row, Iterable cellsToUpdate) { - StaticSection.StaticRow staticRow = section.getRow(row.getRow()); - final List> columns = getVisibleColumns(); - - setCustomStyleName(row.getElement(), staticRow.getStyleName()); - - for (FlyweightCell cell : cellsToUpdate) { - final StaticSection.StaticCell metadata = staticRow - .getCell(columns.get(cell.getColumn())); - - // Decorate default row with sorting indicators - if (staticRow instanceof HeaderRow) { - addSortingIndicatorsToHeaderRow((HeaderRow) staticRow, cell); - } - - // Assign colspan to cell before rendering - cell.setColSpan(metadata.getColspan()); - - Element td = cell.getElement(); - td.removeAllChildren(); - setCustomStyleName(td, metadata.getStyleName()); - - Element content; - // Wrap text or html content in default header to isolate - // the content from the possible column resize drag handle - // next to it - if (metadata.getType() != GridStaticCellType.WIDGET) { - content = DOM.createDiv(); - - if (staticRow instanceof HeaderRow) { - content.setClassName(getStylePrimaryName() - + "-column-header-content"); - if (((HeaderRow) staticRow).isDefault()) { - content.setClassName(content.getClassName() + " " - + getStylePrimaryName() - + "-column-default-header-content"); - } - } else if (staticRow instanceof FooterRow) { - content.setClassName(getStylePrimaryName() - + "-column-footer-content"); - } else { - getLogger().severe( - "Unhandled static row type " - + staticRow.getClass() - .getCanonicalName()); - } - - td.appendChild(content); - } else { - content = td; - } - - switch (metadata.getType()) { - case TEXT: - content.setInnerText(metadata.getText()); - break; - case HTML: - content.setInnerHTML(metadata.getHtml()); - break; - case WIDGET: - preDetach(row, Arrays.asList(cell)); - content.setInnerHTML(""); - postAttach(row, Arrays.asList(cell)); - break; - } - - // XXX: Should add only once in preAttach/postAttach or when - // resizable status changes - // Only add resize handles to default header row for now - if (columns.get(cell.getColumn()).isResizable() - && staticRow instanceof HeaderRow - && ((HeaderRow) staticRow).isDefault()) { - - final int column = cell.getColumn(); - DragHandle dragger = new DragHandle(getStylePrimaryName() - + "-column-resize-handle", - new DragHandleCallback() { - - private Column col = getVisibleColumn(column); - private double initialWidth = 0; - private double minCellWidth; - - @Override - public void onUpdate(double deltaX, - double deltaY) { - col.setWidth(Math.max(minCellWidth, - initialWidth + deltaX)); - } - - @Override - public void onStart() { - initialWidth = col.getWidthActual(); - - minCellWidth = escalator - .getMinCellWidth(getColumns() - .indexOf(col)); - for (Column c : getColumns()) { - if (selectionColumn == c) { - // Don't modify selection column. - continue; - } - - if (c.getWidth() < 0) { - c.setWidth(c.getWidthActual()); - fireEvent(new ColumnResizeEvent( - c)); - } - } - - WidgetUtil.setTextSelectionEnabled( - getElement(), false); - } - - @Override - public void onComplete() { - fireEvent(new ColumnResizeEvent(col)); - - WidgetUtil.setTextSelectionEnabled( - getElement(), true); - } - - @Override - public void onCancel() { - col.setWidth(initialWidth); - - WidgetUtil.setTextSelectionEnabled( - getElement(), true); - } - }); - dragger.addTo(td); - } - - cellFocusHandler.updateFocusedCellStyle(cell, container); - } - } - - private void addSortingIndicatorsToHeaderRow(HeaderRow headerRow, - FlyweightCell cell) { - - Element cellElement = cell.getElement(); - - boolean sortedBefore = cellElement.hasClassName("sort-asc") - || cellElement.hasClassName("sort-desc"); - - cleanup(cell); - if (!headerRow.isDefault()) { - // Nothing more to do if not in the default row - return; - } - - final Column column = getVisibleColumn(cell.getColumn()); - SortOrder sortingOrder = getSortOrder(column); - boolean sortable = column.isSortable(); - - if (sortable) { - cellElement.addClassName("sortable"); - } - - if (!sortable || sortingOrder == null) { - // Only apply sorting indicators to sortable header columns - return; - } - - if (SortDirection.ASCENDING == sortingOrder.getDirection()) { - cellElement.addClassName("sort-asc"); - } else { - cellElement.addClassName("sort-desc"); - } - - int sortIndex = Grid.this.getSortOrder().indexOf(sortingOrder); - if (sortIndex > -1 && Grid.this.getSortOrder().size() > 1) { - // Show sort order indicator if column is - // sorted and other sorted columns also exists. - cellElement.setAttribute("sort-order", - String.valueOf(sortIndex + 1)); - } - - if (!sortedBefore) { - verifyColumnWidth(column); - } - } - - /** - * Sort indicator requires a bit more space from the cell than normally. - * This method check that the now sorted column has enough width. - * - * @param column - * sorted column - */ - private void verifyColumnWidth(Column column) { - int colIndex = getColumns().indexOf(column); - double minWidth = escalator.getMinCellWidth(colIndex); - if (column.getWidthActual() < minWidth) { - // Fix column size - escalator.getColumnConfiguration().setColumnWidth(colIndex, - minWidth); - - fireEvent(new ColumnResizeEvent(column)); - } - } - - /** - * Finds the sort order for this column - */ - private SortOrder getSortOrder(Column column) { - for (SortOrder order : Grid.this.getSortOrder()) { - if (order.getColumn() == column) { - return order; - } - } - return null; - } - - private void cleanup(FlyweightCell cell) { - Element cellElement = cell.getElement(); - cellElement.removeAttribute("sort-order"); - cellElement.removeClassName("sort-desc"); - cellElement.removeClassName("sort-asc"); - cellElement.removeClassName("sortable"); - } - - @Override - public void preAttach(Row row, Iterable cellsToAttach) { - } - - @Override - public void postAttach(Row row, Iterable attachedCells) { - StaticSection.StaticRow gridRow = section.getRow(row.getRow()); - List> columns = getVisibleColumns(); - - for (FlyweightCell cell : attachedCells) { - StaticSection.StaticCell metadata = gridRow.getCell(columns - .get(cell.getColumn())); - /* - * If the cell contains widgets that are not currently attached - * then attach them now. - */ - if (GridStaticCellType.WIDGET.equals(metadata.getType())) { - final Widget widget = metadata.getWidget(); - if (widget != null && !widget.isAttached()) { - getGrid().attachWidget(metadata.getWidget(), - cell.getElement()); - } - } - } - } - - @Override - public void preDetach(Row row, Iterable cellsToDetach) { - if (section.getRowCount() > row.getRow()) { - StaticSection.StaticRow gridRow = section.getRow(row - .getRow()); - List> columns = getVisibleColumns(); - for (FlyweightCell cell : cellsToDetach) { - StaticSection.StaticCell metadata = gridRow.getCell(columns - .get(cell.getColumn())); - - if (GridStaticCellType.WIDGET.equals(metadata.getType()) - && metadata.getWidget() != null - && metadata.getWidget().isAttached()) { - - getGrid().detachWidget(metadata.getWidget()); - } - } - } - } - - protected Grid getGrid() { - return section.grid; - } - - @Override - public void postDetach(Row row, Iterable detachedCells) { - } - }; - - /** - * Creates a new instance. - */ - public Grid() { - initWidget(escalator); - getElement().setTabIndex(0); - cellFocusHandler = new CellFocusHandler(); - - setStylePrimaryName(STYLE_NAME); - - escalator.getHeader().setEscalatorUpdater(createHeaderUpdater()); - escalator.getBody().setEscalatorUpdater(createBodyUpdater()); - escalator.getFooter().setEscalatorUpdater(createFooterUpdater()); - - header.setGrid(this); - HeaderRow defaultRow = header.appendRow(); - header.setDefaultRow(defaultRow); - - footer.setGrid(this); - - editor.setGrid(this); - - setSelectionMode(SelectionMode.SINGLE); - - escalator.getBody().setSpacerUpdater(gridSpacerUpdater); - - escalator.addScrollHandler(new ScrollHandler() { - @Override - public void onScroll(ScrollEvent event) { - fireEvent(new ScrollEvent()); - } - }); - - escalator - .addRowVisibilityChangeHandler(new RowVisibilityChangeHandler() { - @Override - public void onRowVisibilityChange( - RowVisibilityChangeEvent event) { - if (dataSource != null && dataSource.size() != 0) { - dataIsBeingFetched = true; - dataSource.ensureAvailability( - event.getFirstVisibleRow(), - event.getVisibleRowCount()); - } - } - }); - - // Default action on SelectionEvents. Refresh the body so changed - // become visible. - addSelectionHandler(new SelectionHandler() { - - @Override - public void onSelect(SelectionEvent event) { - refreshBody(); - } - }); - - // Sink header events and key events - sinkEvents(getHeader().getConsumedEvents()); - sinkEvents(Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.KEYUP, - BrowserEvents.KEYPRESS, BrowserEvents.DBLCLICK, - BrowserEvents.MOUSEDOWN, BrowserEvents.CLICK)); - - // Make ENTER and SHIFT+ENTER in the header perform sorting - addHeaderKeyUpHandler(new HeaderKeyUpHandler() { - @Override - public void onKeyUp(GridKeyUpEvent event) { - if (event.getNativeKeyCode() != KeyCodes.KEY_ENTER) { - return; - } - if (getHeader().getRow(event.getFocusedCell().getRowIndex()) - .isDefault()) { - // Only sort for enter on the default header - sorter.sort(event.getFocusedCell().getColumn(), - event.isShiftKeyDown()); - } - } - }); - - addDataAvailableHandler(new DataAvailableHandler() { - @Override - public void onDataAvailable(DataAvailableEvent event) { - dataIsBeingFetched = false; - } - }); - } - - @Override - public boolean isEnabled() { - return enabled; - } - - @Override - public void setEnabled(boolean enabled) { - if (enabled == this.enabled) { - return; - } - - this.enabled = enabled; - getElement().setTabIndex(enabled ? 0 : -1); - - // Editor save and cancel buttons need to be disabled. - boolean editorOpen = editor.getState() != State.INACTIVE; - if (editorOpen) { - editor.setGridEnabled(enabled); - } - - sidebar.setEnabled(enabled); - - getEscalator().setScrollLocked(Direction.VERTICAL, - !enabled || editorOpen); - getEscalator().setScrollLocked(Direction.HORIZONTAL, !enabled); - } - - @Override - public void setStylePrimaryName(String style) { - super.setStylePrimaryName(style); - escalator.setStylePrimaryName(style); - editor.setStylePrimaryName(style); - sidebar.setStylePrimaryName(style + "-sidebar"); - sidebar.addStyleName("v-contextmenu"); - - String rowStyle = getStylePrimaryName() + "-row"; - rowHasDataStyleName = rowStyle + "-has-data"; - rowSelectedStyleName = rowStyle + "-selected"; - rowStripeStyleName = rowStyle + "-stripe"; - - cellFocusStyleName = getStylePrimaryName() + "-cell-focused"; - rowFocusStyleName = getStylePrimaryName() + "-row-focused"; - - if (isAttached()) { - refreshHeader(); - refreshBody(); - refreshFooter(); - } - } - - /** - * Creates the escalator updater used to update the header rows in this - * grid. The updater is invoked when header rows or columns are added or - * removed, or the content of existing header cells is changed. - * - * @return the new header updater instance - * - * @see GridHeader - * @see Grid#getHeader() - */ - protected EscalatorUpdater createHeaderUpdater() { - return new StaticSectionUpdater(header, escalator.getHeader()); - } - - /** - * Creates the escalator updater used to update the body rows in this grid. - * The updater is invoked when body rows or columns are added or removed, - * the content of body cells is changed, or the body is scrolled to expose - * previously hidden content. - * - * @return the new body updater instance - */ - protected EscalatorUpdater createBodyUpdater() { - return new BodyUpdater(); - } - - /** - * Creates the escalator updater used to update the footer rows in this - * grid. The updater is invoked when header rows or columns are added or - * removed, or the content of existing header cells is changed. - * - * @return the new footer updater instance - * - * @see GridFooter - * @see #getFooter() - */ - protected EscalatorUpdater createFooterUpdater() { - return new StaticSectionUpdater(footer, escalator.getFooter()); - } - - /** - * Refreshes header or footer rows on demand - * - * @param rows - * The row container - * @param firstRowIsVisible - * is the first row visible - * @param isHeader - * true if we refreshing the header, else assumed - * the footer - */ - private void refreshRowContainer(RowContainer rows, StaticSection section) { - - // Add or Remove rows on demand - int rowDiff = section.getVisibleRowCount() - rows.getRowCount(); - if (rowDiff > 0) { - rows.insertRows(0, rowDiff); - } else if (rowDiff < 0) { - rows.removeRows(0, -rowDiff); - } - - // Refresh all the rows - if (rows.getRowCount() > 0) { - rows.refreshRows(0, rows.getRowCount()); - } - } - - /** - * Focus a body cell by row and column index. - * - * @param rowIndex - * index of row to focus - * @param columnIndex - * index of cell to focus - */ - void focusCell(int rowIndex, int columnIndex) { - final Range rowRange = Range.between(0, dataSource.size()); - final Range columnRange = Range.between(0, getVisibleColumns().size()); - - assert rowRange.contains(rowIndex) : "Illegal row index. Should be in range " - + rowRange; - assert columnRange.contains(columnIndex) : "Illegal column index. Should be in range " - + columnRange; - - if (rowRange.contains(rowIndex) && columnRange.contains(columnIndex)) { - cellFocusHandler.setCellFocus(rowIndex, columnIndex, - escalator.getBody()); - WidgetUtil.focus(getElement()); - } - } - - /** - * Refreshes all header rows - */ - void refreshHeader() { - refreshRowContainer(escalator.getHeader(), header); - } - - /** - * Refreshes all body rows - */ - private void refreshBody() { - escalator.getBody().refreshRows(0, escalator.getBody().getRowCount()); - } - - /** - * Refreshes all footer rows - */ - void refreshFooter() { - refreshRowContainer(escalator.getFooter(), footer); - } - - /** - * Adds columns as the last columns in the grid. - * - * @param columns - * the columns to add - */ - public void addColumns(Column... columns) { - int count = getColumnCount(); - for (Column column : columns) { - addColumn(column, count++); - } - } - - /** - * Adds a column as the last column in the grid. - * - * @param column - * the column to add - * @return given column - */ - public > C addColumn(C column) { - addColumn(column, getColumnCount()); - return column; - } - - /** - * Inserts a column into a specific position in the grid. - * - * @param index - * the index where the column should be inserted into - * @param column - * the column to add - * @return given column - * - * @throws IllegalStateException - * if Grid's current selection model renders a selection column, - * and {@code index} is 0. - */ - public > C addColumn(C column, int index) { - if (column == selectionColumn) { - throw new IllegalArgumentException("The selection column many " - + "not be added manually"); - } else if (selectionColumn != null && index == 0) { - throw new IllegalStateException("A column cannot be inserted " - + "before the selection column"); - } - - addColumnSkipSelectionColumnCheck(column, index); - return column; - } - - private void addColumnSkipSelectionColumnCheck(Column column, - int index) { - // Register column with grid - columns.add(index, column); - - header.addColumn(column); - footer.addColumn(column); - - // Register this grid instance with the column - ((Column) column).setGrid(this); - - // Grid knows about hidden columns, Escalator only knows about what is - // visible so column indexes do not match - if (!column.isHidden()) { - int escalatorIndex = index; - for (int existingColumn = 0; existingColumn < index; existingColumn++) { - if (getColumn(existingColumn).isHidden()) { - escalatorIndex--; - } - } - escalator.getColumnConfiguration().insertColumns(escalatorIndex, 1); - } - - // Reapply column width - column.reapplyWidth(); - - // Sink all renderer events - Set events = new HashSet(); - events.addAll(getConsumedEventsForRenderer(column.getRenderer())); - - if (column.isHidable()) { - columnHider.updateColumnHidable(column); - } - - sinkEvents(events); - } - - private void sinkEvents(Collection events) { - assert events != null; - - int eventsToSink = 0; - for (String typeName : events) { - int typeInt = Event.getTypeInt(typeName); - if (typeInt < 0) { - // Type not recognized by typeInt - sinkBitlessEvent(typeName); - } else { - eventsToSink |= typeInt; - } - } - - if (eventsToSink > 0) { - sinkEvents(eventsToSink); - } - } - - private Renderer findRenderer(FlyweightCell cell) { - Column column = getVisibleColumn(cell.getColumn()); - assert column != null : "Could not find column at index:" - + cell.getColumn(); - return column.getRenderer(); - } - - /** - * Removes a column from the grid. - * - * @param column - * the column to remove - */ - public void removeColumn(Column column) { - if (column != null && column.equals(selectionColumn)) { - throw new IllegalArgumentException( - "The selection column may not be removed manually."); - } - - removeColumnSkipSelectionColumnCheck(column); - } - - private void removeColumnSkipSelectionColumnCheck(Column column) { - int columnIndex = columns.indexOf(column); - - // Remove from column configuration - escalator.getColumnConfiguration().removeColumns( - getVisibleColumns().indexOf(column), 1); - - updateFrozenColumns(); - - header.removeColumn(column); - footer.removeColumn(column); - - // de-register column with grid - ((Column) column).setGrid(null); - - columns.remove(columnIndex); - - if (column.isHidable()) { - columnHider.removeColumnHidingToggle(column); - } - } - - /** - * Returns the amount of columns in the grid. - *

- * NOTE: this includes the hidden columns in the count. - * - * @return The number of columns in the grid - */ - public int getColumnCount() { - return columns.size(); - } - - /** - * Returns a list columns in the grid, including hidden columns. - *

- * For currently visible columns, use {@link #getVisibleColumns()}. - * - * @return A unmodifiable list of the columns in the grid - */ - public List> getColumns() { - return Collections - .unmodifiableList(new ArrayList>(columns)); - } - - /** - * Returns a list of the currently visible columns in the grid. - *

- * No {@link Column#isHidden() hidden} columns included. - * - * @since 7.5.0 - * @return A unmodifiable list of the currently visible columns in the grid - */ - public List> getVisibleColumns() { - ArrayList> visible = new ArrayList>(); - for (Column c : columns) { - if (!c.isHidden()) { - visible.add(c); - } - } - return Collections.unmodifiableList(visible); - } - - /** - * Returns a column by its index in the grid. - *

- * NOTE: The indexing includes hidden columns. - * - * @param index - * the index of the column - * @return The column in the given index - * @throws IllegalArgumentException - * if the column index does not exist in the grid - */ - public Column getColumn(int index) throws IllegalArgumentException { - if (index < 0 || index >= columns.size()) { - throw new IllegalStateException("Column not found."); - } - return columns.get(index); - } - - private Column getVisibleColumn(int index) - throws IllegalArgumentException { - List> visibleColumns = getVisibleColumns(); - if (index < 0 || index >= visibleColumns.size()) { - throw new IllegalStateException("Column not found."); - } - return visibleColumns.get(index); - } - - /** - * Returns the header section of this grid. The default header contains a - * single row displaying the column captions. - * - * @return the header - */ - protected Header getHeader() { - return header; - } - - /** - * Gets the header row at given index. - * - * @param rowIndex - * 0 based index for row. Counted from top to bottom - * @return header row at given index - * @throws IllegalArgumentException - * if no row exists at given index - */ - public HeaderRow getHeaderRow(int rowIndex) { - return header.getRow(rowIndex); - } - - /** - * Inserts a new row at the given position to the header section. Shifts the - * row currently at that position and any subsequent rows down (adds one to - * their indices). - * - * @param index - * the position at which to insert the row - * @return the new row - * - * @throws IllegalArgumentException - * if the index is less than 0 or greater than row count - * @see #appendHeaderRow() - * @see #prependHeaderRow() - * @see #removeHeaderRow(HeaderRow) - * @see #removeHeaderRow(int) - */ - public HeaderRow addHeaderRowAt(int index) { - return header.addRowAt(index); - } - - /** - * Adds a new row at the bottom of the header section. - * - * @return the new row - * @see #prependHeaderRow() - * @see #addHeaderRowAt(int) - * @see #removeHeaderRow(HeaderRow) - * @see #removeHeaderRow(int) - */ - public HeaderRow appendHeaderRow() { - return header.appendRow(); - } - - /** - * Returns the current default row of the header section. The default row is - * a special header row providing a user interface for sorting columns. - * Setting a header caption for column updates cells in the default header. - * - * @return the default row or null if no default row set - */ - public HeaderRow getDefaultHeaderRow() { - return header.getDefaultRow(); - } - - /** - * Gets the row count for the header section. - * - * @return row count - */ - public int getHeaderRowCount() { - return header.getRowCount(); - } - - /** - * Adds a new row at the top of the header section. - * - * @return the new row - * @see #appendHeaderRow() - * @see #addHeaderRowAt(int) - * @see #removeHeaderRow(HeaderRow) - * @see #removeHeaderRow(int) - */ - public HeaderRow prependHeaderRow() { - return header.prependRow(); - } - - /** - * Removes the given row from the header section. - * - * @param row - * the row to be removed - * - * @throws IllegalArgumentException - * if the row does not exist in this section - * @see #removeHeaderRow(int) - * @see #addHeaderRowAt(int) - * @see #appendHeaderRow() - * @see #prependHeaderRow() - */ - public void removeHeaderRow(HeaderRow row) { - header.removeRow(row); - } - - /** - * Removes the row at the given position from the header section. - * - * @param index - * the position of the row - * - * @throws IllegalArgumentException - * if no row exists at given index - * @see #removeHeaderRow(HeaderRow) - * @see #addHeaderRowAt(int) - * @see #appendHeaderRow() - * @see #prependHeaderRow() - */ - public void removeHeaderRow(int rowIndex) { - header.removeRow(rowIndex); - } - - /** - * Sets the default row of the header. The default row is a special header - * row providing a user interface for sorting columns. - *

- * Note: Setting the default header row will reset all cell contents to - * Column defaults. - * - * @param row - * the new default row, or null for no default row - * - * @throws IllegalArgumentException - * header does not contain the row - */ - public void setDefaultHeaderRow(HeaderRow row) { - header.setDefaultRow(row); - } - - /** - * Sets the visibility of the header section. - * - * @param visible - * true to show header section, false to hide - */ - public void setHeaderVisible(boolean visible) { - header.setVisible(visible); - } - - /** - * Returns the visibility of the header section. - * - * @return true if visible, false otherwise. - */ - public boolean isHeaderVisible() { - return header.isVisible(); - } - - /* Grid Footers */ - - /** - * Returns the footer section of this grid. The default footer is empty. - * - * @return the footer - */ - protected Footer getFooter() { - return footer; - } - - /** - * Gets the footer row at given index. - * - * @param rowIndex - * 0 based index for row. Counted from top to bottom - * @return footer row at given index - * @throws IllegalArgumentException - * if no row exists at given index - */ - public FooterRow getFooterRow(int rowIndex) { - return footer.getRow(rowIndex); - } - - /** - * Inserts a new row at the given position to the footer section. Shifts the - * row currently at that position and any subsequent rows down (adds one to - * their indices). - * - * @param index - * the position at which to insert the row - * @return the new row - * - * @throws IllegalArgumentException - * if the index is less than 0 or greater than row count - * @see #appendFooterRow() - * @see #prependFooterRow() - * @see #removeFooterRow(FooterRow) - * @see #removeFooterRow(int) - */ - public FooterRow addFooterRowAt(int index) { - return footer.addRowAt(index); - } - - /** - * Adds a new row at the bottom of the footer section. - * - * @return the new row - * @see #prependFooterRow() - * @see #addFooterRowAt(int) - * @see #removeFooterRow(FooterRow) - * @see #removeFooterRow(int) - */ - public FooterRow appendFooterRow() { - return footer.appendRow(); - } - - /** - * Gets the row count for the footer. - * - * @return row count - */ - public int getFooterRowCount() { - return footer.getRowCount(); - } - - /** - * Adds a new row at the top of the footer section. - * - * @return the new row - * @see #appendFooterRow() - * @see #addFooterRowAt(int) - * @see #removeFooterRow(FooterRow) - * @see #removeFooterRow(int) - */ - public FooterRow prependFooterRow() { - return footer.prependRow(); - } - - /** - * Removes the given row from the footer section. - * - * @param row - * the row to be removed - * - * @throws IllegalArgumentException - * if the row does not exist in this section - * @see #removeFooterRow(int) - * @see #addFooterRowAt(int) - * @see #appendFooterRow() - * @see #prependFooterRow() - */ - public void removeFooterRow(FooterRow row) { - footer.removeRow(row); - } - - /** - * Removes the row at the given position from the footer section. - * - * @param index - * the position of the row - * - * @throws IllegalArgumentException - * if no row exists at given index - * @see #removeFooterRow(FooterRow) - * @see #addFooterRowAt(int) - * @see #appendFooterRow() - * @see #prependFooterRow() - */ - public void removeFooterRow(int rowIndex) { - footer.removeRow(rowIndex); - } - - /** - * Sets the visibility of the footer section. - * - * @param visible - * true to show footer section, false to hide - */ - public void setFooterVisible(boolean visible) { - footer.setVisible(visible); - } - - /** - * Returns the visibility of the footer section. - * - * @return true if visible, false otherwise. - */ - public boolean isFooterVisible() { - return footer.isVisible(); - } - - public Editor getEditor() { - return editor; - } - - protected Escalator getEscalator() { - return escalator; - } - - /** - * {@inheritDoc} - *

- * Note: This method will change the widget's size in the browser - * only if {@link #getHeightMode()} returns {@link HeightMode#CSS}. - * - * @see #setHeightMode(HeightMode) - */ - @Override - public void setHeight(String height) { - escalator.setHeight(height); - } - - @Override - public void setWidth(String width) { - escalator.setWidth(width); - } - - /** - * Sets the data source used by this grid. - * - * @param dataSource - * the data source to use, not null - * @throws IllegalArgumentException - * if dataSource is null - */ - public void setDataSource(final DataSource dataSource) - throws IllegalArgumentException { - if (dataSource == null) { - throw new IllegalArgumentException("dataSource can't be null."); - } - - selectionModel.reset(); - - if (this.dataSource != null) { - this.dataSource.setDataChangeHandler(null); - } - - this.dataSource = dataSource; - dataSource.setDataChangeHandler(new DataChangeHandler() { - @Override - public void dataUpdated(int firstIndex, int numberOfItems) { - escalator.getBody().refreshRows(firstIndex, numberOfItems); - } - - @Override - public void dataRemoved(int firstIndex, int numberOfItems) { - escalator.getBody().removeRows(firstIndex, numberOfItems); - Range removed = Range.withLength(firstIndex, numberOfItems); - cellFocusHandler.rowsRemovedFromBody(removed); - } - - @Override - public void dataAdded(int firstIndex, int numberOfItems) { - escalator.getBody().insertRows(firstIndex, numberOfItems); - Range added = Range.withLength(firstIndex, numberOfItems); - cellFocusHandler.rowsAddedToBody(added); - } - - @Override - public void dataAvailable(int firstIndex, int numberOfItems) { - currentDataAvailable = Range.withLength(firstIndex, - numberOfItems); - fireEvent(new DataAvailableEvent(currentDataAvailable)); - } - - @Override - public void resetDataAndSize(int newSize) { - RowContainer body = escalator.getBody(); - int oldSize = body.getRowCount(); - - // Hide all details. - Set oldDetails = new HashSet(visibleDetails); - for (int i : oldDetails) { - setDetailsVisible(i, false); - } - - if (newSize > oldSize) { - body.insertRows(oldSize, newSize - oldSize); - cellFocusHandler.rowsAddedToBody(Range.withLength(oldSize, - newSize - oldSize)); - } else if (newSize < oldSize) { - body.removeRows(newSize, oldSize - newSize); - cellFocusHandler.rowsRemovedFromBody(Range.withLength( - newSize, oldSize - newSize)); - } - - if (newSize > 0) { - dataIsBeingFetched = true; - Range visibleRowRange = escalator.getVisibleRowRange(); - dataSource.ensureAvailability(visibleRowRange.getStart(), - visibleRowRange.length()); - } else { - // We won't expect any data more data updates, so just make - // the bookkeeping happy - dataAvailable(0, 0); - } - - assert body.getRowCount() == newSize; - } - }); - - int previousRowCount = escalator.getBody().getRowCount(); - if (previousRowCount != 0) { - escalator.getBody().removeRows(0, previousRowCount); - } - - setEscalatorSizeFromDataSource(); - } - - private void setEscalatorSizeFromDataSource() { - assert escalator.getBody().getRowCount() == 0; - - int size = dataSource.size(); - if (size == -1 && isAttached()) { - // Exact size is not yet known, start with some reasonable guess - // just to get an initial backend request going - size = getEscalator().getMaxVisibleRowCount(); - } - if (size > 0) { - escalator.getBody().insertRows(0, size); - } - } - - /** - * Gets the {@Link DataSource} for this Grid. - * - * @return the data source used by this grid - */ - public DataSource getDataSource() { - return dataSource; - } - - /** - * Sets the number of frozen columns in this grid. Setting the count to 0 - * means that no data columns will be frozen, but the built-in selection - * checkbox column will still be frozen if it's in use. Setting the count to - * -1 will also disable the selection column. - *

- * The default value is 0. - * - * @param numberOfColumns - * the number of columns that should be frozen - * - * @throws IllegalArgumentException - * if the column count is < -1 or > the number of visible - * columns - */ - public void setFrozenColumnCount(int numberOfColumns) { - if (numberOfColumns < -1 || numberOfColumns > getColumnCount()) { - throw new IllegalArgumentException( - "count must be between -1 and the current number of columns (" - + getColumnCount() + ")"); - } - - frozenColumnCount = numberOfColumns; - updateFrozenColumns(); - } - - private void updateFrozenColumns() { - escalator.getColumnConfiguration().setFrozenColumnCount( - getVisibleFrozenColumnCount()); - } - - private int getVisibleFrozenColumnCount() { - int numberOfColumns = getFrozenColumnCount(); - - // for the escalator the hidden columns are not in the frozen column - // count, but for grid they are. thus need to convert the index - for (int i = 0; i < frozenColumnCount; i++) { - if (getColumn(i).isHidden()) { - numberOfColumns--; - } - } - - if (numberOfColumns == -1) { - numberOfColumns = 0; - } else if (selectionColumn != null) { - numberOfColumns++; - } - return numberOfColumns; - } - - /** - * Gets the number of frozen columns in this grid. 0 means that no data - * columns will be frozen, but the built-in selection checkbox column will - * still be frozen if it's in use. -1 means that not even the selection - * column is frozen. - *

- * NOTE: This includes {@link Column#isHidden() hidden columns} in - * the count. - * - * @return the number of frozen columns - */ - public int getFrozenColumnCount() { - return frozenColumnCount; - } - - public HandlerRegistration addRowVisibilityChangeHandler( - RowVisibilityChangeHandler handler) { - /* - * Reusing Escalator's RowVisibilityChangeHandler, since a scroll - * concept is too abstract. e.g. the event needs to be re-sent when the - * widget is resized. - */ - return escalator.addRowVisibilityChangeHandler(handler); - } - - /** - * Scrolls to a certain row, using {@link ScrollDestination#ANY}. - *

- * If the details for that row are visible, those will be taken into account - * as well. - * - * @param rowIndex - * zero-based index of the row to scroll to. - * @throws IllegalArgumentException - * if rowIndex is below zero, or above the maximum value - * supported by the data source. - */ - public void scrollToRow(int rowIndex) throws IllegalArgumentException { - scrollToRow(rowIndex, ScrollDestination.ANY, - GridConstants.DEFAULT_PADDING); - } - - /** - * Scrolls to a certain row, using user-specified scroll destination. - *

- * If the details for that row are visible, those will be taken into account - * as well. - * - * @param rowIndex - * zero-based index of the row to scroll to. - * @param destination - * desired destination placement of scrolled-to-row. See - * {@link ScrollDestination} for more information. - * @throws IllegalArgumentException - * if rowIndex is below zero, or above the maximum value - * supported by the data source. - */ - public void scrollToRow(int rowIndex, ScrollDestination destination) - throws IllegalArgumentException { - scrollToRow(rowIndex, destination, - destination == ScrollDestination.MIDDLE ? 0 - : GridConstants.DEFAULT_PADDING); - } - - /** - * Scrolls to a certain row using only user-specified parameters. - *

- * If the details for that row are visible, those will be taken into account - * as well. - * - * @param rowIndex - * zero-based index of the row to scroll to. - * @param destination - * desired destination placement of scrolled-to-row. See - * {@link ScrollDestination} for more information. - * @param paddingPx - * number of pixels to overscroll. Behavior depends on - * destination. - * @throws IllegalArgumentException - * if {@code destination} is {@link ScrollDestination#MIDDLE} - * and padding is nonzero, because having a padding on a - * centered row is undefined behavior, or if rowIndex is below - * zero or above the row count of the data source. - */ - private void scrollToRow(int rowIndex, ScrollDestination destination, - int paddingPx) throws IllegalArgumentException { - int maxsize = escalator.getBody().getRowCount() - 1; - - if (rowIndex < 0) { - throw new IllegalArgumentException("Row index (" + rowIndex - + ") is below zero!"); - } - - if (rowIndex > maxsize) { - throw new IllegalArgumentException("Row index (" + rowIndex - + ") is above maximum (" + maxsize + ")!"); - } - - escalator.scrollToRowAndSpacer(rowIndex, destination, paddingPx); - } - - /** - * Scrolls to the beginning of the very first row. - */ - public void scrollToStart() { - scrollToRow(0, ScrollDestination.START); - } - - /** - * Scrolls to the end of the very last row. - */ - public void scrollToEnd() { - scrollToRow(escalator.getBody().getRowCount() - 1, - ScrollDestination.END); - } - - /** - * Sets the vertical scroll offset. - * - * @param px - * the number of pixels this grid should be scrolled down - */ - public void setScrollTop(double px) { - escalator.setScrollTop(px); - } - - /** - * Gets the vertical scroll offset - * - * @return the number of pixels this grid is scrolled down - */ - public double getScrollTop() { - return escalator.getScrollTop(); - } - - /** - * Sets the horizontal scroll offset - * - * @since 7.5.0 - * @param px - * the number of pixels this grid should be scrolled right - */ - public void setScrollLeft(double px) { - escalator.setScrollLeft(px); - } - - /** - * Gets the horizontal scroll offset - * - * @return the number of pixels this grid is scrolled to the right - */ - public double getScrollLeft() { - return escalator.getScrollLeft(); - } - - /** - * Returns the height of the scrollable area in pixels. - * - * @since 7.5.0 - * @return the height of the scrollable area in pixels - */ - public double getScrollHeight() { - return escalator.getScrollHeight(); - } - - /** - * Returns the width of the scrollable area in pixels. - * - * @since 7.5.0 - * @return the width of the scrollable area in pixels. - */ - public double getScrollWidth() { - return escalator.getScrollWidth(); - } - - private static final Logger getLogger() { - return Logger.getLogger(Grid.class.getName()); - } - - /** - * Sets the number of rows that should be visible in Grid's body, while - * {@link #getHeightMode()} is {@link HeightMode#ROW}. - *

- * If Grid is currently not in {@link HeightMode#ROW}, the given value is - * remembered, and applied once the mode is applied. - * - * @param rows - * The height in terms of number of rows displayed in Grid's - * body. If Grid doesn't contain enough rows, white space is - * displayed instead. - * @throws IllegalArgumentException - * if {@code rows} is zero or less - * @throws IllegalArgumentException - * if {@code rows} is {@link Double#isInifinite(double) - * infinite} - * @throws IllegalArgumentException - * if {@code rows} is {@link Double#isNaN(double) NaN} - * - * @see #setHeightMode(HeightMode) - */ - public void setHeightByRows(double rows) throws IllegalArgumentException { - escalator.setHeightByRows(rows); - } - - /** - * Gets the amount of rows in Grid's body that are shown, while - * {@link #getHeightMode()} is {@link HeightMode#ROW}. - *

- * By default, it is {@value Escalator#DEFAULT_HEIGHT_BY_ROWS}. - * - * @return the amount of rows that should be shown in Grid's body, while in - * {@link HeightMode#ROW}. - * @see #setHeightByRows(double) - */ - public double getHeightByRows() { - return escalator.getHeightByRows(); - } - - /** - * Defines the mode in which the Grid widget's height is calculated. - *

- * If {@link HeightMode#CSS} is given, Grid will respect the values given - * via {@link #setHeight(String)}, and behave as a traditional Widget. - *

- * If {@link HeightMode#ROW} is given, Grid will make sure that the body - * will display as many rows as {@link #getHeightByRows()} defines. - * Note: If headers/footers are inserted or removed, the widget - * will resize itself to still display the required amount of rows in its - * body. It also takes the horizontal scrollbar into account. - * - * @param heightMode - * the mode in to which Grid should be set - */ - public void setHeightMode(HeightMode heightMode) { - /* - * This method is a workaround for the fact that Vaadin re-applies - * widget dimensions (height/width) on each state change event. The - * original design was to have setHeight an setHeightByRow be equals, - * and whichever was called the latest was considered in effect. - * - * But, because of Vaadin always calling setHeight on the widget, this - * approach doesn't work. - */ - - escalator.setHeightMode(heightMode); - } - - /** - * Returns the current {@link HeightMode} the Grid is in. - *

- * Defaults to {@link HeightMode#CSS}. - * - * @return the current HeightMode - */ - public HeightMode getHeightMode() { - return escalator.getHeightMode(); - } - - private Set getConsumedEventsForRenderer(Renderer renderer) { - Set events = new HashSet(); - if (renderer instanceof ComplexRenderer) { - Collection consumedEvents = ((ComplexRenderer) renderer) - .getConsumedEvents(); - if (consumedEvents != null) { - events.addAll(consumedEvents); - } - } - return events; - } - - @Override - public void onBrowserEvent(Event event) { - if (!isEnabled()) { - return; - } - - String eventType = event.getType(); - - if (eventType.equals(BrowserEvents.FOCUS) - || eventType.equals(BrowserEvents.BLUR)) { - super.onBrowserEvent(event); - return; - } - - EventTarget target = event.getEventTarget(); - - if (!Element.is(target) || isOrContainsInSpacer(Element.as(target))) { - return; - } - - Element e = Element.as(target); - RowContainer container = escalator.findRowContainer(e); - Cell cell; - - if (container == null) { - if (eventType.equals(BrowserEvents.KEYDOWN) - || eventType.equals(BrowserEvents.KEYUP) - || eventType.equals(BrowserEvents.KEYPRESS)) { - cell = cellFocusHandler.getFocusedCell(); - container = cellFocusHandler.containerWithFocus; - } else { - // Click might be in an editor cell, should still map. - if (editor.editorOverlay != null - && editor.editorOverlay.isOrHasChild(e)) { - container = escalator.getBody(); - int rowIndex = editor.getRow(); - int colIndex = editor.getElementColumn(e); - - if (colIndex < 0) { - // Click in editor, but not for any column. - return; - } - - TableCellElement cellElement = container - .getRowElement(rowIndex).getCells() - .getItem(colIndex); - - cell = new Cell(rowIndex, colIndex, cellElement); - } else { - if (escalator.getElement().isOrHasChild(e)) { - eventCell.set(new Cell(-1, -1, null), Section.BODY); - // Fire native events. - super.onBrowserEvent(event); - } - return; - } - } - } else { - cell = container.getCell(e); - if (eventType.equals(BrowserEvents.MOUSEDOWN)) { - cellOnPrevMouseDown = cell; - } else if (cell == null && eventType.equals(BrowserEvents.CLICK)) { - /* - * Chrome has an interesting idea on click targets (see - * cellOnPrevMouseDown javadoc). Firefox, on the other hand, has - * the mousedown target as the click target. - */ - cell = cellOnPrevMouseDown; - } - } - - assert cell != null : "received " + eventType - + "-event with a null cell target"; - eventCell.set(cell, getSectionFromContainer(container)); - - // Editor can steal focus from Grid and is still handled - if (isEditorEnabled() && handleEditorEvent(event, container)) { - return; - } - - // Fire GridKeyEvents and GridClickEvents. Pass the event to escalator. - super.onBrowserEvent(event); - - if (!isElementInChildWidget(e)) { - - if (handleHeaderCellDragStartEvent(event, container)) { - return; - } - - // Sorting through header Click / KeyUp - if (handleHeaderDefaultRowEvent(event, container)) { - return; - } - - if (handleRendererEvent(event, container)) { - return; - } - - if (handleCellFocusEvent(event, container)) { - return; - } - } - } - - private Section getSectionFromContainer(RowContainer container) { - assert container != null : "RowContainer should not be null"; - - if (container == escalator.getBody()) { - return Section.BODY; - } else if (container == escalator.getFooter()) { - return Section.FOOTER; - } else if (container == escalator.getHeader()) { - return Section.HEADER; - } - assert false : "RowContainer was not header, footer or body."; - return null; - } - - private boolean isOrContainsInSpacer(Node node) { - Node n = node; - while (n != null && n != getElement()) { - boolean isElement = Element.is(n); - if (isElement) { - String className = Element.as(n).getClassName(); - if (className.contains(getStylePrimaryName() + "-spacer")) { - return true; - } - } - n = n.getParentNode(); - } - return false; - } - - private boolean isElementInChildWidget(Element e) { - Widget w = WidgetUtil.findWidget(e, null); - - if (w == this) { - return false; - } - - /* - * If e is directly inside this grid, but the grid is wrapped in a - * Composite, findWidget is not going to find this, only the wrapper. - * Thus we need to check its parents to see if we encounter this; if we - * don't, the found widget is actually a parent of this, so we should - * return false. - */ - while (w != null && w != this) { - w = w.getParent(); - } - return w != null; - } - - private boolean handleEditorEvent(Event event, RowContainer container) { - Widget w; - if (editor.focusedColumnIndex < 0) { - w = null; - } else { - w = editor.getWidget(getColumn(editor.focusedColumnIndex)); - } - - EditorDomEvent editorEvent = new EditorDomEvent(event, - getEventCell(), w); - - return getEditor().getEventHandler().handleEvent(editorEvent); - } - - private boolean handleRendererEvent(Event event, RowContainer container) { - - if (container == escalator.getBody()) { - Column gridColumn = eventCell.getColumn(); - boolean enterKey = event.getType().equals(BrowserEvents.KEYDOWN) - && event.getKeyCode() == KeyCodes.KEY_ENTER; - boolean doubleClick = event.getType() - .equals(BrowserEvents.DBLCLICK); - - if (gridColumn.getRenderer() instanceof ComplexRenderer) { - ComplexRenderer cplxRenderer = (ComplexRenderer) gridColumn - .getRenderer(); - if (cplxRenderer.getConsumedEvents().contains(event.getType())) { - if (cplxRenderer.onBrowserEvent(eventCell, event)) { - return true; - } - } - - // Calls onActivate if KeyDown and Enter or double click - if ((enterKey || doubleClick) - && cplxRenderer.onActivate(eventCell)) { - return true; - } - } - } - return false; - } - - private boolean handleCellFocusEvent(Event event, RowContainer container) { - Collection navigation = cellFocusHandler.getNavigationEvents(); - if (navigation.contains(event.getType())) { - cellFocusHandler.handleNavigationEvent(event, eventCell); - } - return false; - } - - private boolean handleHeaderCellDragStartEvent(Event event, - RowContainer container) { - if (!isColumnReorderingAllowed()) { - return false; - } - if (container != escalator.getHeader()) { - return false; - } - if (eventCell.getColumnIndex() < escalator.getColumnConfiguration() - .getFrozenColumnCount()) { - return false; - } - - if (event.getTypeInt() == Event.ONMOUSEDOWN - && event.getButton() == NativeEvent.BUTTON_LEFT - || event.getTypeInt() == Event.ONTOUCHSTART) { - dndHandler.onDragStartOnDraggableElement(event, - headerCellDndCallback); - event.preventDefault(); - event.stopPropagation(); - return true; - } - return false; - } - - private Point rowEventTouchStartingPoint; - private CellStyleGenerator cellStyleGenerator; - private RowStyleGenerator rowStyleGenerator; - private RowReference rowReference = new RowReference(this); - private CellReference cellReference = new CellReference(rowReference); - private RendererCellReference rendererCellReference = new RendererCellReference( - (RowReference) rowReference); - - private boolean handleHeaderDefaultRowEvent(Event event, - RowContainer container) { - if (container != escalator.getHeader()) { - return false; - } - if (!getHeader().getRow(eventCell.getRowIndex()).isDefault()) { - return false; - } - if (!eventCell.getColumn().isSortable()) { - // Only handle sorting events if the column is sortable - return false; - } - - if (BrowserEvents.MOUSEDOWN.equals(event.getType()) - && event.getShiftKey()) { - // Don't select text when shift clicking on a header. - event.preventDefault(); - } - - if (BrowserEvents.TOUCHSTART.equals(event.getType())) { - if (event.getTouches().length() > 1) { - return false; - } - - event.preventDefault(); - - Touch touch = event.getChangedTouches().get(0); - rowEventTouchStartingPoint = new Point(touch.getClientX(), - touch.getClientY()); - - sorter.sortAfterDelay(GridConstants.LONG_TAP_DELAY, true); - - return true; - - } else if (BrowserEvents.TOUCHMOVE.equals(event.getType())) { - if (event.getTouches().length() > 1) { - return false; - } - - event.preventDefault(); - - Touch touch = event.getChangedTouches().get(0); - double diffX = Math.abs(touch.getClientX() - - rowEventTouchStartingPoint.getX()); - double diffY = Math.abs(touch.getClientY() - - rowEventTouchStartingPoint.getY()); - - // Cancel long tap if finger strays too far from - // starting point - if (diffX > GridConstants.LONG_TAP_THRESHOLD - || diffY > GridConstants.LONG_TAP_THRESHOLD) { - sorter.cancelDelayedSort(); - } - - return true; - - } else if (BrowserEvents.TOUCHEND.equals(event.getType())) { - if (event.getTouches().length() > 1) { - return false; - } - - if (sorter.isDelayedSortScheduled()) { - // Not a long tap yet, perform single sort - sorter.cancelDelayedSort(); - sorter.sort(eventCell.getColumn(), false); - } - - return true; - - } else if (BrowserEvents.TOUCHCANCEL.equals(event.getType())) { - if (event.getTouches().length() > 1) { - return false; - } - - sorter.cancelDelayedSort(); - - return true; - - } else if (BrowserEvents.CLICK.equals(event.getType())) { - - sorter.sort(eventCell.getColumn(), event.getShiftKey()); - - // Click events should go onward to cell focus logic - return false; - } else { - return false; - } - } - - @Override - @SuppressWarnings("deprecation") - public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - - /* - * handles details[] (translated to spacer[] for Escalator), cell[], - * header[] and footer[] - */ - - // "#header[0][0]/DRAGhANDLE" - Element escalatorElement = escalator.getSubPartElement(subPart - .replaceFirst("^details\\[", "spacer[")); - - if (escalatorElement != null) { - - int detailIdx = subPart.indexOf("/"); - if (detailIdx > 0) { - String detail = subPart.substring(detailIdx + 1); - getLogger().severe( - "Looking up detail from index " + detailIdx - + " onward: \"" + detail + "\""); - if (detail.equalsIgnoreCase("content")) { - // XXX: Fix this to look up by class name! - return DOM.asOld(Element.as(escalatorElement.getChild(0))); - } - if (detail.equalsIgnoreCase("draghandle")) { - // XXX: Fix this to look up by class name! - return DOM.asOld(Element.as(escalatorElement.getChild(1))); - } - } - - return DOM.asOld(escalatorElement); - } - - SubPartArguments args = SubPartArguments.create(subPart); - Element editor = getSubPartElementEditor(args); - if (editor != null) { - return DOM.asOld(editor); - } - - return null; - } - - private Element getSubPartElementEditor(SubPartArguments args) { - - if (!args.getType().equalsIgnoreCase("editor") - || editor.getState() != State.ACTIVE) { - return null; - } - - if (args.getIndicesLength() == 0) { - return editor.editorOverlay; - } else if (args.getIndicesLength() == 1) { - int index = args.getIndex(0); - if (index >= columns.size()) { - return null; - } - - escalator.scrollToColumn(index, ScrollDestination.ANY, 0); - Widget widget = editor.getWidget(columns.get(index)); - - if (widget != null) { - return widget.getElement(); - } - - // No widget for the column. - return null; - } - - return null; - } - - @Override - @SuppressWarnings("deprecation") - public String getSubPartName(com.google.gwt.user.client.Element subElement) { - - String escalatorStructureName = escalator.getSubPartName(subElement); - if (escalatorStructureName != null) { - return escalatorStructureName.replaceFirst("^spacer", "details"); - } - - String editorName = getSubPartNameEditor(subElement); - if (editorName != null) { - return editorName; - } - - return null; - } - - private String getSubPartNameEditor(Element subElement) { - - if (editor.getState() != State.ACTIVE - || !editor.editorOverlay.isOrHasChild(subElement)) { - return null; - } - - int i = 0; - for (Column column : columns) { - if (editor.getWidget(column).getElement().isOrHasChild(subElement)) { - return "editor[" + i + "]"; - } - ++i; - } - - return "editor"; - } - - private void setSelectColumnRenderer( - final Renderer selectColumnRenderer) { - if (this.selectColumnRenderer == selectColumnRenderer) { - return; - } - - if (this.selectColumnRenderer != null) { - if (this.selectColumnRenderer instanceof ComplexRenderer) { - // End of Life for the old selection column renderer. - ((ComplexRenderer) this.selectColumnRenderer).destroy(); - } - - // Clear field so frozen column logic in the remove method knows - // what to do - Column colToRemove = selectionColumn; - selectionColumn = null; - removeColumnSkipSelectionColumnCheck(colToRemove); - cellFocusHandler.offsetRangeBy(-1); - } - - this.selectColumnRenderer = selectColumnRenderer; - - if (selectColumnRenderer != null) { - cellFocusHandler.offsetRangeBy(1); - selectionColumn = new SelectionColumn(selectColumnRenderer); - - addColumnSkipSelectionColumnCheck(selectionColumn, 0); - selectionColumn.initDone(); - } else { - selectionColumn = null; - refreshBody(); - } - - updateFrozenColumns(); - } - - /** - * Sets the current selection model. - *

- * This function will call {@link SelectionModel#setGrid(Grid)}. - * - * @param selectionModel - * a selection model implementation. - * @throws IllegalArgumentException - * if selection model argument is null - */ - public void setSelectionModel(SelectionModel selectionModel) { - - if (selectionModel == null) { - throw new IllegalArgumentException("Selection model can't be null"); - } - - if (this.selectionModel != null) { - // Detach selection model from Grid. - this.selectionModel.setGrid(null); - } - - this.selectionModel = selectionModel; - selectionModel.setGrid(this); - setSelectColumnRenderer(this.selectionModel - .getSelectionColumnRenderer()); - - // Refresh rendered rows to update selection, if it has changed - refreshBody(); - } - - /** - * Gets a reference to the current selection model. - * - * @return the currently used SelectionModel instance. - */ - public SelectionModel getSelectionModel() { - return selectionModel; - } - - /** - * Sets current selection mode. - *

- * This is a shorthand method for {@link Grid#setSelectionModel}. - * - * @param mode - * a selection mode value - * @see {@link SelectionMode}. - */ - public void setSelectionMode(SelectionMode mode) { - SelectionModel model = mode.createModel(); - setSelectionModel(model); - } - - /** - * Test if a row is selected. - * - * @param row - * a row object - * @return true, if the current selection model considers the provided row - * object selected. - */ - public boolean isSelected(T row) { - return selectionModel.isSelected(row); - } - - /** - * Select a row using the current selection model. - *

- * Only selection models implementing {@link SelectionModel.Single} and - * {@link SelectionModel.Multi} are supported; for anything else, an - * exception will be thrown. - * - * @param row - * a row object - * @return true iff the current selection changed - * @throws IllegalStateException - * if the current selection model is not an instance of - * {@link SelectionModel.Single} or {@link SelectionModel.Multi} - */ - public boolean select(T row) { - if (selectionModel instanceof SelectionModel.Single) { - return ((SelectionModel.Single) selectionModel).select(row); - } else if (selectionModel instanceof SelectionModel.Multi) { - return ((SelectionModel.Multi) selectionModel) - .select(Collections.singleton(row)); - } else { - throw new IllegalStateException("Unsupported selection model"); - } - } - - /** - * Deselect a row using the current selection model. - *

- * Only selection models implementing {@link SelectionModel.Single} and - * {@link SelectionModel.Multi} are supported; for anything else, an - * exception will be thrown. - * - * @param row - * a row object - * @return true iff the current selection changed - * @throws IllegalStateException - * if the current selection model is not an instance of - * {@link SelectionModel.Single} or {@link SelectionModel.Multi} - */ - public boolean deselect(T row) { - if (selectionModel instanceof SelectionModel.Single) { - return ((SelectionModel.Single) selectionModel).deselect(row); - } else if (selectionModel instanceof SelectionModel.Multi) { - return ((SelectionModel.Multi) selectionModel) - .deselect(Collections.singleton(row)); - } else { - throw new IllegalStateException("Unsupported selection model"); - } - } - - /** - * Deselect all rows using the current selection model. - * - * @param row - * a row object - * @return true iff the current selection changed - * @throws IllegalStateException - * if the current selection model is not an instance of - * {@link SelectionModel.Single} or {@link SelectionModel.Multi} - */ - public boolean deselectAll() { - if (selectionModel instanceof SelectionModel.Single) { - Single single = ((SelectionModel.Single) selectionModel); - if (single.getSelectedRow() != null) { - return single.deselect(single.getSelectedRow()); - } else { - return false; - } - } else if (selectionModel instanceof SelectionModel.Multi) { - return ((SelectionModel.Multi) selectionModel).deselectAll(); - } else { - throw new IllegalStateException("Unsupported selection model"); - } - } - - /** - * Gets last selected row from the current SelectionModel. - *

- * Only selection models implementing {@link SelectionModel.Single} are - * valid for this method; for anything else, use the - * {@link Grid#getSelectedRows()} method. - * - * @return a selected row reference, or null, if no row is selected - * @throws IllegalStateException - * if the current selection model is not an instance of - * {@link SelectionModel.Single} - */ - public T getSelectedRow() { - if (selectionModel instanceof SelectionModel.Single) { - return ((SelectionModel.Single) selectionModel).getSelectedRow(); - } else { - throw new IllegalStateException( - "Unsupported selection model; can not get single selected row"); - } - } - - /** - * Gets currently selected rows from the current selection model. - * - * @return a non-null collection containing all currently selected rows. - */ - public Collection getSelectedRows() { - return selectionModel.getSelectedRows(); - } - - @Override - public HandlerRegistration addSelectionHandler( - final SelectionHandler handler) { - return addHandler(handler, SelectionEvent.getType()); - } - - /** - * Sets the current sort order using the fluid Sort API. Read the - * documentation for {@link Sort} for more information. - * - * @param s - * a sort instance - */ - public void sort(Sort s) { - setSortOrder(s.build()); - } - - /** - * Sorts the Grid data in ascending order along one column. - * - * @param column - * a grid column reference - */ - public void sort(Column column) { - sort(column, SortDirection.ASCENDING); - } - - /** - * Sorts the Grid data along one column. - * - * @param column - * a grid column reference - * @param direction - * a sort direction value - */ - public void sort(Column column, SortDirection direction) { - sort(Sort.by(column, direction)); - } - - /** - * Sets the sort order to use. Setting this causes the Grid to re-sort - * itself. - * - * @param order - * a sort order list. If set to null, the sort order is cleared. - */ - public void setSortOrder(List order) { - setSortOrder(order, false); - } - - /** - * Clears the sort order and indicators without re-sorting. - */ - private void clearSortOrder() { - sortOrder.clear(); - refreshHeader(); - } - - private void setSortOrder(List order, boolean userOriginated) { - if (order != sortOrder) { - sortOrder.clear(); - if (order != null) { - sortOrder.addAll(order); - } - } - sort(userOriginated); - } - - /** - * Get a copy of the current sort order array. - * - * @return a copy of the current sort order array - */ - public List getSortOrder() { - return Collections.unmodifiableList(sortOrder); - } - - /** - * Finds the sorting order for this column - */ - private SortOrder getSortOrder(Column column) { - for (SortOrder order : getSortOrder()) { - if (order.getColumn() == column) { - return order; - } - } - return null; - } - - /** - * Register a GWT event handler for a sorting event. This handler gets - * called whenever this Grid needs its data source to provide data sorted in - * a specific order. - * - * @param handler - * a sort event handler - * @return the registration for the event - */ - public HandlerRegistration addSortHandler(SortHandler handler) { - return addHandler(handler, SortEvent.getType()); - } - - /** - * Register a GWT event handler for a select all event. This handler gets - * called whenever Grid needs all rows selected. - * - * @param handler - * a select all event handler - */ - public HandlerRegistration addSelectAllHandler(SelectAllHandler handler) { - return addHandler(handler, SelectAllEvent.getType()); - } - - /** - * Register a GWT event handler for a data available event. This handler - * gets called whenever the {@link DataSource} for this Grid has new data - * available. - *

- * This handle will be fired with the current available data after - * registration is done. - * - * @param handler - * a data available event handler - * @return the registartion for the event - */ - public HandlerRegistration addDataAvailableHandler( - final DataAvailableHandler handler) { - // Deferred call to handler with current row range - Scheduler.get().scheduleFinally(new ScheduledCommand() { - @Override - public void execute() { - if (!dataIsBeingFetched) { - handler.onDataAvailable(new DataAvailableEvent( - currentDataAvailable)); - } - } - }); - return addHandler(handler, DataAvailableEvent.TYPE); - } - - /** - * Register a BodyKeyDownHandler to this Grid. The event for this handler is - * fired when a KeyDown event occurs while cell focus is in the Body of this - * Grid. - * - * @param handler - * the key handler to register - * @return the registration for the event - */ - public HandlerRegistration addBodyKeyDownHandler(BodyKeyDownHandler handler) { - return addHandler(handler, keyDown.getAssociatedType()); - } - - /** - * Register a BodyKeyUpHandler to this Grid. The event for this handler is - * fired when a KeyUp event occurs while cell focus is in the Body of this - * Grid. - * - * @param handler - * the key handler to register - * @return the registration for the event - */ - public HandlerRegistration addBodyKeyUpHandler(BodyKeyUpHandler handler) { - return addHandler(handler, keyUp.getAssociatedType()); - } - - /** - * Register a BodyKeyPressHandler to this Grid. The event for this handler - * is fired when a KeyPress event occurs while cell focus is in the Body of - * this Grid. - * - * @param handler - * the key handler to register - * @return the registration for the event - */ - public HandlerRegistration addBodyKeyPressHandler( - BodyKeyPressHandler handler) { - return addHandler(handler, keyPress.getAssociatedType()); - } - - /** - * Register a HeaderKeyDownHandler to this Grid. The event for this handler - * is fired when a KeyDown event occurs while cell focus is in the Header of - * this Grid. - * - * @param handler - * the key handler to register - * @return the registration for the event - */ - public HandlerRegistration addHeaderKeyDownHandler( - HeaderKeyDownHandler handler) { - return addHandler(handler, keyDown.getAssociatedType()); - } - - /** - * Register a HeaderKeyUpHandler to this Grid. The event for this handler is - * fired when a KeyUp event occurs while cell focus is in the Header of this - * Grid. - * - * @param handler - * the key handler to register - * @return the registration for the event - */ - public HandlerRegistration addHeaderKeyUpHandler(HeaderKeyUpHandler handler) { - return addHandler(handler, keyUp.getAssociatedType()); - } - - /** - * Register a HeaderKeyPressHandler to this Grid. The event for this handler - * is fired when a KeyPress event occurs while cell focus is in the Header - * of this Grid. - * - * @param handler - * the key handler to register - * @return the registration for the event - */ - public HandlerRegistration addHeaderKeyPressHandler( - HeaderKeyPressHandler handler) { - return addHandler(handler, keyPress.getAssociatedType()); - } - - /** - * Register a FooterKeyDownHandler to this Grid. The event for this handler - * is fired when a KeyDown event occurs while cell focus is in the Footer of - * this Grid. - * - * @param handler - * the key handler to register - * @return the registration for the event - */ - public HandlerRegistration addFooterKeyDownHandler( - FooterKeyDownHandler handler) { - return addHandler(handler, keyDown.getAssociatedType()); - } - - /** - * Register a FooterKeyUpHandler to this Grid. The event for this handler is - * fired when a KeyUp event occurs while cell focus is in the Footer of this - * Grid. - * - * @param handler - * the key handler to register - * @return the registration for the event - */ - public HandlerRegistration addFooterKeyUpHandler(FooterKeyUpHandler handler) { - return addHandler(handler, keyUp.getAssociatedType()); - } - - /** - * Register a FooterKeyPressHandler to this Grid. The event for this handler - * is fired when a KeyPress event occurs while cell focus is in the Footer - * of this Grid. - * - * @param handler - * the key handler to register - * @return the registration for the event - */ - public HandlerRegistration addFooterKeyPressHandler( - FooterKeyPressHandler handler) { - return addHandler(handler, keyPress.getAssociatedType()); - } - - /** - * Register a BodyClickHandler to this Grid. The event for this handler is - * fired when a Click event occurs in the Body of this Grid. - * - * @param handler - * the click handler to register - * @return the registration for the event - */ - public HandlerRegistration addBodyClickHandler(BodyClickHandler handler) { - return addHandler(handler, clickEvent.getAssociatedType()); - } - - /** - * Register a HeaderClickHandler to this Grid. The event for this handler is - * fired when a Click event occurs in the Header of this Grid. - * - * @param handler - * the click handler to register - * @return the registration for the event - */ - public HandlerRegistration addHeaderClickHandler(HeaderClickHandler handler) { - return addHandler(handler, clickEvent.getAssociatedType()); - } - - /** - * Register a FooterClickHandler to this Grid. The event for this handler is - * fired when a Click event occurs in the Footer of this Grid. - * - * @param handler - * the click handler to register - * @return the registration for the event - */ - public HandlerRegistration addFooterClickHandler(FooterClickHandler handler) { - return addHandler(handler, clickEvent.getAssociatedType()); - } - - /** - * Register a BodyDoubleClickHandler to this Grid. The event for this - * handler is fired when a double click event occurs in the Body of this - * Grid. - * - * @param handler - * the double click handler to register - * @return the registration for the event - */ - public HandlerRegistration addBodyDoubleClickHandler( - BodyDoubleClickHandler handler) { - return addHandler(handler, doubleClickEvent.getAssociatedType()); - } - - /** - * Register a HeaderDoubleClickHandler to this Grid. The event for this - * handler is fired when a double click event occurs in the Header of this - * Grid. - * - * @param handler - * the double click handler to register - * @return the registration for the event - */ - public HandlerRegistration addHeaderDoubleClickHandler( - HeaderDoubleClickHandler handler) { - return addHandler(handler, doubleClickEvent.getAssociatedType()); - } - - /** - * Register a FooterDoubleClickHandler to this Grid. The event for this - * handler is fired when a double click event occurs in the Footer of this - * Grid. - * - * @param handler - * the double click handler to register - * @return the registration for the event - */ - public HandlerRegistration addFooterDoubleClickHandler( - FooterDoubleClickHandler handler) { - return addHandler(handler, doubleClickEvent.getAssociatedType()); - } - - /** - * Register a column reorder handler to this Grid. The event for this - * handler is fired when the Grid's columns are reordered. - * - * @since 7.5.0 - * @param handler - * the handler for the event - * @return the registration for the event - */ - public HandlerRegistration addColumnReorderHandler( - ColumnReorderHandler handler) { - return addHandler(handler, ColumnReorderEvent.getType()); - } - - /** - * Register a column visibility change handler to this Grid. The event for - * this handler is fired when the Grid's columns change visibility. - * - * @since 7.5.0 - * @param handler - * the handler for the event - * @return the registration for the event - */ - public HandlerRegistration addColumnVisibilityChangeHandler( - ColumnVisibilityChangeHandler handler) { - return addHandler(handler, ColumnVisibilityChangeEvent.getType()); - } - - /** - * Register a column resize handler to this Grid. The event for this handler - * is fired when the Grid's columns are resized. - * - * @since 7.6 - * @param handler - * the handler for the event - * @return the registration for the event - */ - public HandlerRegistration addColumnResizeHandler( - ColumnResizeHandler handler) { - return addHandler(handler, ColumnResizeEvent.getType()); - } - - /** - * Apply sorting to data source. - */ - private void sort(boolean userOriginated) { - refreshHeader(); - fireEvent(new SortEvent(this, - Collections.unmodifiableList(sortOrder), userOriginated)); - } - - private int getLastVisibleRowIndex() { - int lastRowIndex = escalator.getVisibleRowRange().getEnd(); - int footerTop = escalator.getFooter().getElement().getAbsoluteTop(); - Element lastRow; - - do { - lastRow = escalator.getBody().getRowElement(--lastRowIndex); - } while (lastRow.getAbsoluteTop() > footerTop); - - return lastRowIndex; - } - - private int getFirstVisibleRowIndex() { - int firstRowIndex = escalator.getVisibleRowRange().getStart(); - int headerBottom = escalator.getHeader().getElement() - .getAbsoluteBottom(); - Element firstRow = escalator.getBody().getRowElement(firstRowIndex); - - while (firstRow.getAbsoluteBottom() < headerBottom) { - firstRow = escalator.getBody().getRowElement(++firstRowIndex); - } - - return firstRowIndex; - } - - /** - * Adds a scroll handler to this grid - * - * @param handler - * the scroll handler to add - * @return a handler registration for the registered scroll handler - */ - public HandlerRegistration addScrollHandler(ScrollHandler handler) { - return addHandler(handler, ScrollEvent.TYPE); - } - - @Override - public boolean isWorkPending() { - return escalator.isWorkPending() || dataIsBeingFetched - || autoColumnWidthsRecalculator.isScheduled() - || editor.isWorkPending(); - } - - /** - * Returns whether columns can be reordered with drag and drop. - * - * @since 7.5.0 - * @return true if columns can be reordered, false otherwise - */ - public boolean isColumnReorderingAllowed() { - return columnReorderingAllowed; - } - - /** - * Sets whether column reordering with drag and drop is allowed or not. - * - * @since 7.5.0 - * @param columnReorderingAllowed - * specifies whether column reordering is allowed - */ - public void setColumnReorderingAllowed(boolean columnReorderingAllowed) { - this.columnReorderingAllowed = columnReorderingAllowed; - } - - /** - * Sets a new column order for the grid. All columns which are not ordered - * here will remain in the order they were before as the last columns of - * grid. - * - * @param orderedColumns - * array of columns in wanted order - */ - public void setColumnOrder(Column... orderedColumns) { - ColumnConfiguration conf = getEscalator().getColumnConfiguration(); - - // Trigger ComplexRenderer.destroy for old content - conf.removeColumns(0, conf.getColumnCount()); - - List> newOrder = new ArrayList>(); - if (selectionColumn != null) { - newOrder.add(selectionColumn); - } - - int i = 0; - for (Column column : orderedColumns) { - if (columns.contains(column)) { - newOrder.add(column); - ++i; - } else { - throw new IllegalArgumentException("Given column at index " + i - + " does not exist in Grid"); - } - } - - if (columns.size() != newOrder.size()) { - columns.removeAll(newOrder); - newOrder.addAll(columns); - } - columns = newOrder; - - List> visibleColumns = getVisibleColumns(); - - // Do ComplexRenderer.init and render new content - conf.insertColumns(0, visibleColumns.size()); - - // Number of frozen columns should be kept same #16901 - updateFrozenColumns(); - - // Update column widths. - for (Column column : columns) { - column.reapplyWidth(); - } - - // Recalculate all the colspans - for (HeaderRow row : header.getRows()) { - row.calculateColspans(); - } - for (FooterRow row : footer.getRows()) { - row.calculateColspans(); - } - - columnHider.updateTogglesOrder(); - - fireEvent(new ColumnReorderEvent()); - } - - /** - * Sets the style generator that is used for generating styles for cells - * - * @param cellStyleGenerator - * the cell style generator to set, or null to - * remove a previously set generator - */ - public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) { - this.cellStyleGenerator = cellStyleGenerator; - refreshBody(); - } - - /** - * Gets the style generator that is used for generating styles for cells - * - * @return the cell style generator, or null if no generator is - * set - */ - public CellStyleGenerator getCellStyleGenerator() { - return cellStyleGenerator; - } - - /** - * Sets the style generator that is used for generating styles for rows - * - * @param rowStyleGenerator - * the row style generator to set, or null to remove - * a previously set generator - */ - public void setRowStyleGenerator(RowStyleGenerator rowStyleGenerator) { - this.rowStyleGenerator = rowStyleGenerator; - refreshBody(); - } - - /** - * Gets the style generator that is used for generating styles for rows - * - * @return the row style generator, or null if no generator is - * set - */ - public RowStyleGenerator getRowStyleGenerator() { - return rowStyleGenerator; - } - - private static void setCustomStyleName(Element element, String styleName) { - assert element != null; - - String oldStyleName = element - .getPropertyString(CUSTOM_STYLE_PROPERTY_NAME); - - if (!SharedUtil.equals(oldStyleName, styleName)) { - if (oldStyleName != null && !oldStyleName.isEmpty()) { - element.removeClassName(oldStyleName); - } - if (styleName != null && !styleName.isEmpty()) { - element.addClassName(styleName); - } - element.setPropertyString(CUSTOM_STYLE_PROPERTY_NAME, styleName); - } - - } - - /** - * Opens the editor over the row with the given index. - * - * @param rowIndex - * the index of the row to be edited - * - * @throws IllegalStateException - * if the editor is not enabled - * @throws IllegalStateException - * if the editor is already in edit mode - */ - public void editRow(int rowIndex) { - editor.editRow(rowIndex); - } - - /** - * Returns whether the editor is currently open on some row. - * - * @return {@code true} if the editor is active, {@code false} otherwise. - */ - public boolean isEditorActive() { - return editor.getState() != State.INACTIVE; - } - - /** - * Saves any unsaved changes in the editor to the data source. - * - * @throws IllegalStateException - * if the editor is not enabled - * @throws IllegalStateException - * if the editor is not in edit mode - */ - public void saveEditor() { - editor.save(); - } - - /** - * Cancels the currently active edit and hides the editor. Any changes that - * are not {@link #saveEditor() saved} are lost. - * - * @throws IllegalStateException - * if the editor is not enabled - * @throws IllegalStateException - * if the editor is not in edit mode - */ - public void cancelEditor() { - editor.cancel(); - } - - /** - * Returns the handler responsible for binding data and editor widgets to - * the editor. - * - * @return the editor handler or null if not set - */ - public EditorHandler getEditorHandler() { - return editor.getHandler(); - } - - /** - * Sets the handler responsible for binding data and editor widgets to the - * editor. - * - * @param rowHandler - * the new editor handler - * - * @throws IllegalStateException - * if the editor is currently in edit mode - */ - public void setEditorHandler(EditorHandler handler) { - editor.setHandler(handler); - } - - /** - * Returns the enabled state of the editor. - * - * @return true if editing is enabled, false otherwise - */ - public boolean isEditorEnabled() { - return editor.isEnabled(); - } - - /** - * Sets the enabled state of the editor. - * - * @param enabled - * true to enable editing, false to disable - * - * @throws IllegalStateException - * if in edit mode and trying to disable - * @throws IllegalStateException - * if the editor handler is not set - */ - public void setEditorEnabled(boolean enabled) { - editor.setEnabled(enabled); - } - - /** - * Returns the editor widget associated with the given column. If the editor - * is not active, returns null. - * - * @param column - * the column - * @return the widget if the editor is open, null otherwise - */ - public Widget getEditorWidget(Column column) { - return editor.getWidget(column); - } - - /** - * Sets the caption on the save button in the Grid editor. - * - * @param saveCaption - * the caption to set - * @throws IllegalArgumentException - * if {@code saveCaption} is {@code null} - */ - public void setEditorSaveCaption(String saveCaption) - throws IllegalArgumentException { - editor.setSaveCaption(saveCaption); - } - - /** - * Gets the current caption on the save button in the Grid editor. - * - * @return the current caption on the save button - */ - public String getEditorSaveCaption() { - return editor.getSaveCaption(); - } - - /** - * Sets the caption on the cancel button in the Grid editor. - * - * @param cancelCaption - * the caption to set - * @throws IllegalArgumentException - * if {@code cancelCaption} is {@code null} - */ - public void setEditorCancelCaption(String cancelCaption) - throws IllegalArgumentException { - editor.setCancelCaption(cancelCaption); - } - - /** - * Gets the caption on the cancel button in the Grid editor. - * - * @return the current caption on the cancel button - */ - public String getEditorCancelCaption() { - return editor.getCancelCaption(); - } - - @Override - protected void onAttach() { - super.onAttach(); - - if (getEscalator().getBody().getRowCount() == 0 && dataSource != null) { - setEscalatorSizeFromDataSource(); - } - - // Grid was just attached to DOM. Column widths should be calculated. - recalculateColumnWidths(); - } - - @Override - protected void onDetach() { - Set details = new HashSet(visibleDetails); - for (int row : details) { - setDetailsVisible(row, false); - } - - super.onDetach(); - } - - @Override - public void onResize() { - super.onResize(); - - /* - * Delay calculation to be deferred so Escalator can do it's magic. - */ - Scheduler.get().scheduleFinally(new ScheduledCommand() { - - @Override - public void execute() { - if (escalator.getInnerWidth() != autoColumnWidthsRecalculator.lastCalculatedInnerWidth) { - recalculateColumnWidths(); - } - - // Vertical resizing could make editor positioning invalid so it - // needs to be recalculated on resize - if (isEditorActive()) { - editor.updateVerticalScrollPosition(); - } - } - }); - } - - /** - * Grid does not support adding Widgets this way. - *

- * This method is implemented only because removing widgets from Grid (added - * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface. - * - * @param w - * irrelevant - * @throws UnsupportedOperationException - * always - */ - @Override - @Deprecated - public void add(Widget w) { - throw new UnsupportedOperationException( - "Cannot add widgets to Grid with this method"); - } - - /** - * Grid does not support clearing Widgets this way. - *

- * This method is implemented only because removing widgets from Grid (added - * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface. - * - * @throws UnsupportedOperationException - * always - */ - @Override - @Deprecated - public void clear() { - throw new UnsupportedOperationException( - "Cannot clear widgets from Grid this way"); - } - - /** - * Grid does not support iterating through Widgets this way. - *

- * This method is implemented only because removing widgets from Grid (added - * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface. - * - * @return never - * @throws UnsupportedOperationException - * always - */ - @Override - @Deprecated - public Iterator iterator() { - throw new UnsupportedOperationException( - "Cannot iterate through widgets in Grid this way"); - } - - /** - * Grid does not support removing Widgets this way. - *

- * This method is implemented only because removing widgets from Grid (added - * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface. - * - * @return always false - */ - @Override - @Deprecated - public boolean remove(Widget w) { - /* - * This is the method that is the sole reason to have Grid implement - * HasWidget - when Vaadin removes a Component from the hierarchy, the - * corresponding Widget will call removeFromParent() on itself. GWT will - * check there that its parent (i.e. Grid) implements HasWidgets, and - * will call this remove(Widget) method. - * - * tl;dr: all this song and dance to make sure GWT's sanity checks - * aren't triggered, even though they effectively do nothing interesting - * from Grid's perspective. - */ - return false; - } - - /** - * Accesses the package private method Widget#setParent() - * - * @param widget - * The widget to access - * @param parent - * The parent to set - */ - private static native final void setParent(Widget widget, Grid parent) - /*-{ - widget.@com.google.gwt.user.client.ui.Widget::setParent(Lcom/google/gwt/user/client/ui/Widget;)(parent); - }-*/; - - private static native final void onAttach(Widget widget) - /*-{ - widget.@Widget::onAttach()(); - }-*/; - - private static native final void onDetach(Widget widget) - /*-{ - widget.@Widget::onDetach()(); - }-*/; - - @Override - protected void doAttachChildren() { - if (sidebar.getParent() == this) { - onAttach(sidebar); - } - } - - @Override - protected void doDetachChildren() { - if (sidebar.getParent() == this) { - onDetach(sidebar); - } - } - - private void attachWidget(Widget w, Element parent) { - assert w.getParent() == null; - - parent.appendChild(w.getElement()); - setParent(w, this); - } - - private void detachWidget(Widget w) { - assert w.getParent() == this; - - setParent(w, null); - w.getElement().removeFromParent(); - } - - /** - * Resets all cached pixel sizes and reads new values from the DOM. This - * methods should be used e.g. when styles affecting the dimensions of - * elements in this grid have been changed. - */ - public void resetSizesFromDom() { - getEscalator().resetSizesFromDom(); - } - - /** - * Sets a new details generator for row details. - *

- * The currently opened row details will be re-rendered. - * - * @since 7.5.0 - * @param detailsGenerator - * the details generator to set - * @throws IllegalArgumentException - * if detailsGenerator is null; - */ - public void setDetailsGenerator(DetailsGenerator detailsGenerator) - throws IllegalArgumentException { - - if (detailsGenerator == null) { - throw new IllegalArgumentException( - "Details generator may not be null"); - } - - for (Integer index : visibleDetails) { - setDetailsVisible(index, false); - } - - this.detailsGenerator = detailsGenerator; - - // this will refresh all visible spacers - escalator.getBody().setSpacerUpdater(gridSpacerUpdater); - } - - /** - * Gets the current details generator for row details. - * - * @since 7.5.0 - * @return the detailsGenerator the current details generator - */ - public DetailsGenerator getDetailsGenerator() { - return detailsGenerator; - } - - /** - * Shows or hides the details for a specific row. - *

- * This method does nothing if trying to set show already-visible details, - * or hide already-hidden details. - * - * @since 7.5.0 - * @param rowIndex - * the index of the affected row - * @param visible - * true to show the details, or false - * to hide them - * @see #isDetailsVisible(int) - */ - public void setDetailsVisible(int rowIndex, boolean visible) { - if (DetailsGenerator.NULL.equals(detailsGenerator)) { - return; - } - - Integer rowIndexInteger = Integer.valueOf(rowIndex); - - /* - * We want to prevent opening a details row twice, so any subsequent - * openings (or closings) of details is a NOOP. - * - * When a details row is opened, it is given an arbitrary height - * (because Escalator requires a height upon opening). Only when it's - * opened, Escalator will ask the generator to generate a widget, which - * we then can measure. When measured, we correct the initial height by - * the original height. - * - * Without this check, we would override the measured height, and revert - * back to the initial, arbitrary, height which would most probably be - * wrong. - * - * see GridSpacerUpdater.init for implementation details. - */ - - boolean isVisible = isDetailsVisible(rowIndex); - if (visible && !isVisible) { - escalator.getBody().setSpacer(rowIndex, DETAILS_ROW_INITIAL_HEIGHT); - visibleDetails.add(rowIndexInteger); - } - - else if (!visible && isVisible) { - escalator.getBody().setSpacer(rowIndex, -1); - visibleDetails.remove(rowIndexInteger); - } - } - - /** - * Check whether the details for a row is visible or not. - * - * @since 7.5.0 - * @param rowIndex - * the index of the row for which to check details - * @return true iff the details for the given row is visible - * @see #setDetailsVisible(int, boolean) - */ - public boolean isDetailsVisible(int rowIndex) { - return visibleDetails.contains(Integer.valueOf(rowIndex)); - } - - /** - * Requests that the column widths should be recalculated. - *

- * The actual recalculation is not necessarily done immediately so you - * cannot rely on the columns being the correct width after the call - * returns. - * - * @since 7.4.1 - */ - public void recalculateColumnWidths() { - autoColumnWidthsRecalculator.schedule(); - } - - /** - * Gets the customizable menu bar that is by default used for toggling - * column hidability. The application developer is allowed to add their - * custom items to the end of the menu, but should try to avoid modifying - * the items in the beginning of the menu that control the column hiding if - * any columns are marked as hidable. A toggle for opening the menu will be - * displayed whenever the menu contains at least one item. - * - * @since 7.5.0 - * @return the menu bar - */ - public MenuBar getSidebarMenu() { - return sidebar.menuBar; - } - - /** - * Tests whether the sidebar menu is currently open. - * - * @since 7.5.0 - * @see #getSidebarMenu() - * @return true if the sidebar is open; false if - * it is closed - */ - public boolean isSidebarOpen() { - return sidebar.isOpen(); - } - - /** - * Sets whether the sidebar menu is open. - * - * - * @since 7.5.0 - * @see #getSidebarMenu() - * @see #isSidebarOpen() - * @param sidebarOpen - * true to open the sidebar; false to - * close it - */ - public void setSidebarOpen(boolean sidebarOpen) { - if (sidebarOpen) { - sidebar.open(); - } else { - sidebar.close(); - } - } - - @Override - public int getTabIndex() { - return FocusUtil.getTabIndex(this); - } - - @Override - public void setAccessKey(char key) { - FocusUtil.setAccessKey(this, key); - } - - @Override - public void setFocus(boolean focused) { - FocusUtil.setFocus(this, focused); - } - - @Override - public void setTabIndex(int index) { - FocusUtil.setTabIndex(this, index); - } - - @Override - public void focus() { - setFocus(true); - } - - /** - * Sets the buffered editor mode. - * - * @since 7.6 - * @param editorUnbuffered - * true to enable buffered editor, - * false to disable it - */ - public void setEditorBuffered(boolean editorBuffered) { - editor.setBuffered(editorBuffered); - } - - /** - * Gets the buffered editor mode. - * - * @since 7.6 - * @return true if buffered editor is enabled, - * false otherwise - */ - public boolean isEditorBuffered() { - return editor.isBuffered(); - } - - /** - * Returns the {@link EventCellReference} for the latest event fired from - * this Grid. - *

- * Note: This cell reference will be updated when firing the next event. - * - * @since 7.5 - * @return event cell reference - */ - public EventCellReference getEventCell() { - return eventCell; - } - - /** - * Returns a CellReference for the cell to which the given element belongs - * to. - * - * @since 7.6 - * @param element - * Element to find from the cell's content. - * @return CellReference or null if cell was not found. - */ - public CellReference getCellReference(Element element) { - RowContainer container = getEscalator().findRowContainer(element); - if (container != null) { - Cell cell = container.getCell(element); - if (cell != null) { - EventCellReference cellRef = new EventCellReference(this); - cellRef.set(cell, getSectionFromContainer(container)); - return cellRef; - } - } - return null; - } -} diff --git a/client/src/com/vaadin/client/widgets/Overlay.java b/client/src/com/vaadin/client/widgets/Overlay.java deleted file mode 100644 index a5e29c6774..0000000000 --- a/client/src/com/vaadin/client/widgets/Overlay.java +++ /dev/null @@ -1,1012 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.widgets; - -import com.google.gwt.animation.client.Animation; -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.IFrameElement; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.BorderStyle; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.AnimationUtil; -import com.vaadin.client.AnimationUtil.AnimationEndListener; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ComputedStyle; -import com.vaadin.client.WidgetUtil; - -/** - * Overlay widget extending the PopupPanel. Overlay is used to float elements on - * top of other elements temporarily. - *

- * Note: This class should always be constructed with - * {@link GWT#create(Class)}. - * - *

Shadow

- *

- * The separate shadow element underneath the main overlay element is - * deprecated, and should not be used for new overlay - * components. CSS box-shadow should be used instead of a separate shadow - * element. Remember to include any vendor-prefixed versions to support all - * browsers that you need to. To cover all possible browsers that Vaadin 7 - * supports, add -webkit-box-shadow and the standard - * box-shadow properties. - *

- * - *

- * For IE8, which doesn't support CSS box-shadow, you can use the proprietary - * DropShadow filter. It doesn't provide the exact same features as box-shadow, - * but it is suitable for graceful degradation. Other options are to use a - * border or a pseudo-element underneath the overlay which mimics a shadow, or - * any combination of these. - *

- * - *

- * Read more about the DropShadow filter from Microsoft Developer Network - *

- * - * @since 7.6.1 - */ -public class Overlay extends PopupPanel implements CloseHandler { - - @Override - protected void onAttach() { - // Move the overlay to the appropriate overlay container - final Overlay overlay = Overlay.current; - if (overlay != null) { - final Element e = overlay.getOverlayContainer(); - e.appendChild(getElement()); - } - - super.onAttach(); - } - - public static class PositionAndSize { - private int left, top, width, height; - - public PositionAndSize(int left, int top, int width, int height) { - super(); - setLeft(left); - setTop(top); - setWidth(width); - setHeight(height); - } - - public int getLeft() { - return left; - } - - public void setLeft(int left) { - this.left = left; - } - - public int getTop() { - return top; - } - - public void setTop(int top) { - this.top = top; - } - - public int getWidth() { - return width; - } - - public void setWidth(int width) { - if (width < 0) { - width = 0; - } - - this.width = width; - } - - public int getHeight() { - return height; - } - - public void setHeight(int height) { - if (height < 0) { - height = 0; - } - - this.height = height; - } - - public void setAnimationFromCenterProgress(double progress) { - left += (int) (width * (1.0 - progress) / 2.0); - top += (int) (height * (1.0 - progress) / 2.0); - width = (int) (width * progress); - height = (int) (height * progress); - } - } - - /* - * The z-index value from where all overlays live. This can be overridden in - * any extending class. - */ - public static int Z_INDEX = 20000; - - private static int leftFix = -1; - - private static int topFix = -1; - - /** - * Shadow element style. If an extending class wishes to use a different - * style of shadow, it can use setShadowStyle(String) to give the shadow - * element a new style name. - * - * @deprecated See main JavaDoc for Overlay - */ - @Deprecated - public static final String CLASSNAME_SHADOW = "v-shadow"; - - /** - * Style name for the overlay container element (see - * {@link #getOverlayContainer()} - */ - public static final String CLASSNAME_CONTAINER = "v-overlay-container"; - - /** - * @since 7.3 - */ - public static final String ADDITIONAL_CLASSNAME_ANIMATE_IN = "animate-in"; - /** - * @since 7.3 - */ - public static final String ADDITIONAL_CLASSNAME_ANIMATE_OUT = "animate-out"; - - /** - * The shadow element for this overlay. - * - * @deprecated See main JavaDoc for Overlay - * - */ - @Deprecated - private Element shadow; - - /* - * The creator of this Overlay (the widget that made the instance, not the - * layout parent) - */ - private Widget owner; - - /** - * The shim iframe behind the overlay, allowing PDFs and applets to be - * covered by overlays. - */ - private IFrameElement shimElement; - - /** - * The HTML snippet that is used to render the actual shadow. In consists of - * nine different DIV-elements with the following class names: - * - *
-     *   .v-shadow[-stylename]
-     *   ----------------------------------------------
-     *   | .top-left     |   .top    |     .top-right |
-     *   |---------------|-----------|----------------|
-     *   |               |           |                |
-     *   | .left         |  .center  |         .right |
-     *   |               |           |                |
-     *   |---------------|-----------|----------------|
-     *   | .bottom-left  |  .bottom  |  .bottom-right |
-     *   ----------------------------------------------
-     * 
- * - * See default theme 'shadow.css' for implementation example. - * - * @deprecated See main JavaDoc for Overlay - */ - @Deprecated - private static final String SHADOW_HTML = "
"; - - /** - * Matches {@link PopupPanel}.ANIMATION_DURATION - */ - private static final int POPUP_PANEL_ANIMATION_DURATION = 200; - - /** - * @deprecated See main JavaDoc for Overlay - */ - @Deprecated - private boolean sinkShadowEvents = false; - - public Overlay() { - super(); - adjustZIndex(); - } - - public Overlay(boolean autoHide) { - super(autoHide); - adjustZIndex(); - } - - public Overlay(boolean autoHide, boolean modal) { - super(autoHide, modal); - adjustZIndex(); - } - - /** - * @deprecated See main JavaDoc for Overlay. Use the other constructors - * without the showShadow parameter. - */ - @Deprecated - public Overlay(boolean autoHide, boolean modal, boolean showShadow) { - super(autoHide, modal); - setShadowEnabled(showShadow && useShadowDiv()); - adjustZIndex(); - } - - /** - * Return true if a separate shadow div should be used. Since Vaadin 7.3, - * shadows are implemented with CSS box-shadow. Thus, a shadow div is only - * used for IE8 by default. - * - * @deprecated See main JavaDoc for Overlay - * @since 7.3 - * @return true to use a shadow div - */ - @Deprecated - protected boolean useShadowDiv() { - return BrowserInfo.get().isIE8(); - } - - /** - * Method to control whether DOM elements for shadow are added. With this - * method subclasses can control displaying of shadow also after the - * constructor. - * - * @param enabled - * true if shadow should be displayed - * - * @deprecated See main JavaDoc for Overlay - */ - @Deprecated - protected void setShadowEnabled(boolean enabled) { - if (enabled != isShadowEnabled()) { - if (enabled) { - shadow = DOM.createDiv(); - shadow.setClassName(CLASSNAME_SHADOW); - shadow.setInnerHTML(SHADOW_HTML); - shadow.getStyle().setPosition(Position.ABSOLUTE); - addCloseHandler(this); - } else { - removeShadowIfPresent(); - shadow = null; - } - } - } - - /** - * @deprecated See main JavaDoc for Overlay - */ - @Deprecated - protected boolean isShadowEnabled() { - return shadow != null; - } - - protected boolean isShimElementEnabled() { - return shimElement != null; - } - - private void removeShimElement() { - if (shimElement != null) { - shimElement.removeFromParent(); - } - } - - /** - * @deprecated See main JavaDoc for Overlay - */ - @Deprecated - private void removeShadowIfPresent() { - if (isShadowAttached()) { - // Remove event listener from the shadow - unsinkShadowEvents(); - - shadow.removeFromParent(); - } - } - - /** - * @deprecated See main JavaDoc for Overlay - */ - @Deprecated - private boolean isShadowAttached() { - return isShadowEnabled() && shadow.getParentElement() != null; - } - - private void adjustZIndex() { - setZIndex(Z_INDEX); - } - - /** - * Set the z-index (visual stack position) for this overlay. - * - * @param zIndex - * The new z-index - */ - protected void setZIndex(int zIndex) { - getElement().getStyle().setZIndex(zIndex); - if (isShadowEnabled()) { - shadow.getStyle().setZIndex(zIndex); - } - } - - @Override - public void setPopupPosition(int left, int top) { - // TODO, this should in fact be part of - // Document.get().getBodyOffsetLeft/Top(). Would require overriding DOM - // for all permutations. Now adding fix as margin instead of fixing - // left/top because parent class saves the position. - Style style = getElement().getStyle(); - style.setMarginLeft(-adjustByRelativeLeftBodyMargin(), Unit.PX); - style.setMarginTop(-adjustByRelativeTopBodyMargin(), Unit.PX); - super.setPopupPosition(left, top); - positionOrSizeUpdated(isAnimationEnabled() ? 0 : 1); - } - - private IFrameElement getShimElement() { - if (shimElement == null && needsShimElement()) { - shimElement = Document.get().createIFrameElement(); - - // Insert shim iframe before the main overlay element. It does not - // matter if it is in front or behind the shadow as we cannot put a - // shim behind the shadow due to its transparency. - shimElement.getStyle().setPosition(Position.ABSOLUTE); - shimElement.getStyle().setBorderStyle(BorderStyle.NONE); - shimElement.setTabIndex(-1); - shimElement.setFrameBorder(0); - shimElement.setMarginHeight(0); - } - return shimElement; - } - - private int getActualTop() { - int y = getAbsoluteTop(); - - /* This is needed for IE7 at least */ - // Account for the difference between absolute position and the - // body's positioning context. - y -= Document.get().getBodyOffsetTop(); - y -= adjustByRelativeTopBodyMargin(); - - return y; - } - - private int getActualLeft() { - int x = getAbsoluteLeft(); - - /* This is needed for IE7 at least */ - // Account for the difference between absolute position and the - // body's positioning context. - x -= Document.get().getBodyOffsetLeft(); - x -= adjustByRelativeLeftBodyMargin(); - - return x; - } - - private static int adjustByRelativeTopBodyMargin() { - if (topFix == -1) { - topFix = detectRelativeBodyFixes("top"); - } - return topFix; - } - - private native static int detectRelativeBodyFixes(String axis) - /*-{ - try { - var b = $wnd.document.body; - var cstyle = b.currentStyle ? b.currentStyle : getComputedStyle(b); - if(cstyle && cstyle.position == 'relative') { - return b.getBoundingClientRect()[axis]; - } - } catch(e){} - return 0; - }-*/; - - private static int adjustByRelativeLeftBodyMargin() { - if (leftFix == -1) { - leftFix = detectRelativeBodyFixes("left"); - - } - return leftFix; - } - - /* - * A "thread local" of sorts, set temporarily so that OverlayImpl knows - * which Overlay is using it, so that it can be attached to the correct - * overlay container. - * - * TODO this is a strange pattern that we should get rid of when possible. - */ - protected static Overlay current; - - @Override - public void show() { - current = this; - - maybeShowWithAnimation(); - - if (isAnimationEnabled()) { - new ResizeAnimation().run(POPUP_PANEL_ANIMATION_DURATION); - } else { - positionOrSizeUpdated(1.0); - } - current = null; - } - - private JavaScriptObject animateInListener; - - private boolean maybeShowWithAnimation() { - boolean isAttached = isAttached() && isShowing(); - super.show(); - - // Don't animate if already visible or browser is IE8 or IE9 (no CSS - // animation support) - if (isAttached || BrowserInfo.get().isIE8() - || BrowserInfo.get().isIE9()) { - return false; - } else { - // Check if animations are used - setVisible(false); - addStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); - if (isShadowEnabled()) { - shadow.addClassName(CLASSNAME_SHADOW + "-" - + ADDITIONAL_CLASSNAME_ANIMATE_IN); - } - - ComputedStyle cs = new ComputedStyle(getElement()); - String animationName = AnimationUtil.getAnimationName(cs); - if (animationName == null) { - animationName = ""; - } - setVisible(true); - - if (animationName.contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) { - // Disable GWT PopupPanel animation if used - setAnimationEnabled(false); - animateInListener = AnimationUtil.addAnimationEndListener( - getElement(), new AnimationEndListener() { - @Override - public void onAnimationEnd(NativeEvent event) { - String animationName = AnimationUtil - .getAnimationName(event); - if (animationName - .contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) { - AnimationUtil.removeAnimationEndListener( - getElement(), animateInListener); - removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); - if (isShadowEnabled()) { - shadow.removeClassName(CLASSNAME_SHADOW - + "-" - + ADDITIONAL_CLASSNAME_ANIMATE_IN); - } - } - } - }); - return true; - } else { - removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); - if (isShadowEnabled()) { - shadow.removeClassName(CLASSNAME_SHADOW + "-" - + ADDITIONAL_CLASSNAME_ANIMATE_IN); - } - return false; - } - } - } - - @Override - protected void onDetach() { - super.onDetach(); - - // Always ensure shadow is removed when the overlay is removed. - removeShadowIfPresent(); - removeShimElement(); - } - - @Override - public void setVisible(boolean visible) { - super.setVisible(visible); - if (isShadowEnabled()) { - shadow.getStyle().setProperty("visibility", - visible ? "visible" : "hidden"); - } - if (isShimElementEnabled()) { - shimElement.getStyle().setProperty("visibility", - visible ? "visible" : "hidden"); - } - } - - @Override - public void setWidth(String width) { - super.setWidth(width); - positionOrSizeUpdated(1.0); - } - - @Override - public void setHeight(String height) { - super.setHeight(height); - positionOrSizeUpdated(1.0); - } - - /** - * Sets the shadow style for this overlay. Will override any previous style - * for the shadow. The default style name is defined by CLASSNAME_SHADOW. - * The given style will be prefixed with CLASSNAME_SHADOW. - * - * @param style - * The new style name for the shadow element. Will be prefixed by - * CLASSNAME_SHADOW, e.g. style=='foobar' -> actual style - * name=='v-shadow-foobar'. - * - * @deprecated See main JavaDoc for Overlay - */ - @Deprecated - protected void setShadowStyle(String style) { - if (isShadowEnabled()) { - shadow.setClassName(CLASSNAME_SHADOW + "-" + style); - } - } - - /** - * Extending classes should always call this method after they change the - * size of overlay without using normal 'setWidth(String)' and - * 'setHeight(String)' methods (if not calling super.setWidth/Height). - * - */ - public void positionOrSizeUpdated() { - positionOrSizeUpdated(1.0); - } - - /** - * @deprecated Call {@link #positionOrSizeUpdated()} instead. - */ - @Deprecated - protected void updateShadowSizeAndPosition() { - positionOrSizeUpdated(); - } - - /** - * Recalculates proper position and dimensions for the shadow and shim - * elements. Can be used to animate the related elements, using the - * 'progress' parameter (used to animate the shadow in sync with GWT - * PopupPanel's default animation 'PopupPanel.AnimationType.CENTER'). - * - * @param progress - * A value between 0.0 and 1.0, indicating the progress of the - * animation (0=start, 1=end). - */ - private void positionOrSizeUpdated(final double progress) { - // Don't do anything if overlay element is not attached - if (!isAttached()) { - return; - } - // Calculate proper z-index - int zIndex = -1; - try { - // Odd behaviour with Windows Hosted Mode forces us to use - // this redundant try/catch block (See dev.vaadin.com #2011) - zIndex = Integer.parseInt(getElement().getStyle().getZIndex()); - } catch (Exception ignore) { - // Ignored, will cause no harm - zIndex = 1000; - } - if (zIndex == -1) { - zIndex = Z_INDEX; - } - // Calculate position and size - if (BrowserInfo.get().isIE()) { - // Shake IE - getOffsetHeight(); - getOffsetWidth(); - } - - if (isShadowEnabled() || needsShimElement()) { - - PositionAndSize positionAndSize = new PositionAndSize( - getActualLeft(), getActualTop(), getOffsetWidth(), - getOffsetHeight()); - - // Animate the size - positionAndSize.setAnimationFromCenterProgress(progress); - - Element container = getElement().getParentElement(); - - if (isShadowEnabled()) { - updateShadowPosition(progress, zIndex, positionAndSize); - if (shadow.getParentElement() == null) { - container.insertBefore(shadow, getElement()); - sinkShadowEvents(); - } - } - - if (needsShimElement()) { - updateShimPosition(positionAndSize); - if (shimElement.getParentElement() == null) { - container.insertBefore(shimElement, getElement()); - } - } - } - // Fix for #14173 - // IE9 and IE10 have a bug, when resize an a element with box-shadow. - // IE9 and IE10 need explicit update to remove extra box-shadows - if (BrowserInfo.get().isIE9() || BrowserInfo.get().isIE10()) { - WidgetUtil.forceIERedraw(getElement()); - } - } - - /** - * @deprecated See main JavaDoc for Overlay - */ - @Deprecated - private void updateShadowPosition(final double progress, int zIndex, - PositionAndSize positionAndSize) { - // Opera needs some shaking to get parts of the shadow showing - // properly (ticket #2704) - if (BrowserInfo.get().isOpera()) { - // Clear the height of all middle elements - DOM.getChild(shadow, 3).getStyle().setProperty("height", "auto"); - DOM.getChild(shadow, 4).getStyle().setProperty("height", "auto"); - DOM.getChild(shadow, 5).getStyle().setProperty("height", "auto"); - } - - updatePositionAndSize(shadow, positionAndSize); - shadow.getStyle().setZIndex(zIndex); - shadow.getStyle().setProperty("display", progress < 0.9 ? "none" : ""); - - // Opera fix, part 2 (ticket #2704) - if (BrowserInfo.get().isOpera()) { - // We'll fix the height of all the middle elements - DOM.getChild(shadow, 3) - .getStyle() - .setPropertyPx("height", - DOM.getChild(shadow, 3).getOffsetHeight()); - DOM.getChild(shadow, 4) - .getStyle() - .setPropertyPx("height", - DOM.getChild(shadow, 4).getOffsetHeight()); - DOM.getChild(shadow, 5) - .getStyle() - .setPropertyPx("height", - DOM.getChild(shadow, 5).getOffsetHeight()); - } - } - - private void updateShimPosition(PositionAndSize positionAndSize) { - updatePositionAndSize(getShimElement(), positionAndSize); - } - - /** - * Returns true if we should add a shim iframe below the overlay to deal - * with zindex issues with PDFs and applets. Can be overriden to disable - * shim iframes if they are not needed. - * - * @return true if a shim iframe should be added, false otherwise - */ - protected boolean needsShimElement() { - BrowserInfo info = BrowserInfo.get(); - return info.isIE() && info.isBrowserVersionNewerOrEqual(8, 0); - } - - private void updatePositionAndSize(Element e, - PositionAndSize positionAndSize) { - e.getStyle().setLeft(positionAndSize.getLeft(), Unit.PX); - e.getStyle().setTop(positionAndSize.getTop(), Unit.PX); - e.getStyle().setWidth(positionAndSize.getWidth(), Unit.PX); - e.getStyle().setHeight(positionAndSize.getHeight(), Unit.PX); - } - - protected class ResizeAnimation extends Animation { - @Override - protected void onUpdate(double progress) { - positionOrSizeUpdated(progress); - } - } - - @Override - public void onClose(CloseEvent event) { - removeShadowIfPresent(); - } - - @Override - public void sinkEvents(int eventBitsToAdd) { - super.sinkEvents(eventBitsToAdd); - // Also sink events on the shadow if present - sinkShadowEvents(); - } - - /** - * @deprecated See main JavaDoc for Overlay - */ - @Deprecated - private void sinkShadowEvents() { - if (isSinkShadowEvents() && isShadowAttached()) { - // Sink the same events as the actual overlay has sunk - DOM.sinkEvents(shadow, DOM.getEventsSunk(getElement())); - // Send events to Overlay.onBrowserEvent - DOM.setEventListener(shadow, this); - } - } - - /** - * @deprecated See main JavaDoc for Overlay - */ - @Deprecated - private void unsinkShadowEvents() { - if (isShadowAttached()) { - DOM.setEventListener(shadow, null); - DOM.sinkEvents(shadow, 0); - } - } - - /** - * Enables or disables sinking the events of the shadow to the same - * onBrowserEvent as events to the actual overlay goes. - * - * Please note, that if you enable this, you can't assume that e.g. - * event.getEventTarget returns an element inside the DOM structure of the - * overlay - * - * @param sinkShadowEvents - * - * @deprecated See main JavaDoc for Overlay - */ - @Deprecated - protected void setSinkShadowEvents(boolean sinkShadowEvents) { - this.sinkShadowEvents = sinkShadowEvents; - if (sinkShadowEvents) { - sinkShadowEvents(); - } else { - unsinkShadowEvents(); - } - } - - /** - * @deprecated See main JavaDoc for Overlay - */ - @Deprecated - protected boolean isSinkShadowEvents() { - return sinkShadowEvents; - } - - /** - * Get owner (Widget that made this Overlay, not the layout parent) of - * Overlay - * - * @return Owner (creator) or null if not defined - */ - public Widget getOwner() { - return owner; - } - - /** - * Set owner (Widget that made this Overlay, not the layout parent) of - * Overlay - * - * @param owner - * Owner (creator) of Overlay - */ - public void setOwner(Widget owner) { - this.owner = owner; - } - - /** - * Gets the 'overlay container' element. - * - * @return the overlay container element - */ - public com.google.gwt.user.client.Element getOverlayContainer() { - return RootPanel.get().getElement(); - } - - @Override - public void center() { - super.center(); - - // Some devices can be zoomed in, we should center to the visual - // viewport for those devices - BrowserInfo b = BrowserInfo.get(); - if (b.isAndroid() || b.isIOS()) { - int left = (getVisualViewportWidth() - getOffsetWidth()) >> 1; - int top = (getVisualViewportHeight() - getOffsetHeight()) >> 1; - setPopupPosition(Math.max(Window.getScrollLeft() + left, 0), - Math.max(Window.getScrollTop() + top, 0)); - } - - } - - /** - * Gets the visual viewport width, which is useful for e.g iOS where the - * view can be zoomed in while keeping the layout viewport intact. - * - * Falls back to layout viewport; for those browsers/devices the difference - * is that the scrollbar with is included (if there is a scrollbar). - * - * @since 7.0.7 - * @return - */ - private int getVisualViewportWidth() { - int w = (int) getSubpixelInnerWidth(); - if (w < 0) { - return Window.getClientWidth(); - } else { - return w; - } - } - - /** - * Gets the visual viewport height, which is useful for e.g iOS where the - * view can be zoomed in while keeping the layout viewport intact. - * - * Falls back to layout viewport; for those browsers/devices the difference - * is that the scrollbar with is included (if there is a scrollbar). - * - * @since 7.0.7 - * @return - */ - private int getVisualViewportHeight() { - int h = (int) getSubpixelInnerHeight(); - if (h < 0) { - return Window.getClientHeight(); - } else { - return h; - } - } - - private native double getSubpixelInnerWidth() - /*-{ - return $wnd.innerWidth !== undefined ? $wnd.innerWidth : -1; - }-*/; - - private native double getSubpixelInnerHeight() - /*-{ - return $wnd.innerHeight !== undefined ? $wnd.innerHeight :-1; - }-*/; - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.PopupPanel#hide() - */ - @Override - public void hide() { - hide(false); - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.PopupPanel#hide(boolean) - */ - @Override - public void hide(final boolean autoClosed) { - hide(autoClosed, true, true); - } - - /** - * - * Hides the popup and detaches it from the page. This has no effect if it - * is not currently showing. Animation-in, animation-out can be - * enable/disabled for different use cases. - * - * @see com.google.gwt.user.client.ui.PopupPanel#hide(boolean) - * - * @param autoClosed - * the value that will be passed to - * {@link CloseHandler#onClose(CloseEvent)} when the popup is - * closed - * @param animateIn - * enable/disable animate-in animation - * @param animateOut - * enable/disable animate-out animation - * @since 7.3.7 - */ - public void hide(final boolean autoClosed, final boolean animateIn, - final boolean animateOut) { - if (BrowserInfo.get().isIE8() || BrowserInfo.get().isIE9()) { - super.hide(autoClosed); - } else { - if (animateIn - && getStyleName().contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) { - AnimationUtil.addAnimationEndListener(getElement(), - new AnimationEndListener() { - @Override - public void onAnimationEnd(NativeEvent event) { - if (AnimationUtil - .getAnimationName(event) - .contains( - ADDITIONAL_CLASSNAME_ANIMATE_IN)) { - Overlay.this.hide(autoClosed); - } - } - }); - } else { - // Check if animations are used - addStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_OUT); - if (isShadowEnabled()) { - shadow.addClassName(CLASSNAME_SHADOW + "-" - + ADDITIONAL_CLASSNAME_ANIMATE_OUT); - } - ComputedStyle cs = new ComputedStyle(getElement()); - String animationName = AnimationUtil.getAnimationName(cs); - if (animationName == null) { - animationName = ""; - } - - if (animateOut - && animationName - .contains(ADDITIONAL_CLASSNAME_ANIMATE_OUT)) { - // Disable GWT PopupPanel closing animation if used - setAnimationEnabled(false); - - AnimationUtil.addAnimationEndListener(getElement(), - new AnimationEndListener() { - @Override - public void onAnimationEnd(NativeEvent event) { - String animationName = AnimationUtil - .getAnimationName(event); - if (animationName - .contains(ADDITIONAL_CLASSNAME_ANIMATE_OUT)) { - AnimationUtil - .removeAllAnimationEndListeners(getElement()); - // Remove both animation styles just in - // case - removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); - removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_OUT); - if (isShadowEnabled()) { - shadow.removeClassName(CLASSNAME_SHADOW - + "-" - + ADDITIONAL_CLASSNAME_ANIMATE_IN); - shadow.removeClassName(CLASSNAME_SHADOW - + "-" - + ADDITIONAL_CLASSNAME_ANIMATE_OUT); - } - Overlay.super.hide(autoClosed); - } - } - }); - // No event previews should happen after the animation has - // started - Overlay.this.setPreviewingAllNativeEvents(false); - } else { - removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_OUT); - if (isShadowEnabled()) { - shadow.removeClassName(CLASSNAME_SHADOW + "-" - + ADDITIONAL_CLASSNAME_ANIMATE_OUT); - } - super.hide(autoClosed); - } - } - } - } - -} diff --git a/client/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml b/client/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml deleted file mode 100644 index f3cc2bca1c..0000000000 --- a/client/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/AnimationUtil.java b/client/src/main/java/com/vaadin/client/AnimationUtil.java new file mode 100644 index 0000000000..063a0a163e --- /dev/null +++ b/client/src/main/java/com/vaadin/client/AnimationUtil.java @@ -0,0 +1,179 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Style; + +/** + * Utility methods for working with CSS transitions and animations. + * + * @author Vaadin Ltd + * @since 7.3 + */ +public class AnimationUtil { + + /** + * For internal use only. May be removed or replaced in the future. + * + * Set the animation-duration CSS property. + * + * @param elem + * the element whose animation-duration to set + * @param duration + * the duration as a valid CSS value + */ + public static void setAnimationDuration(Element elem, String duration) { + Style style = elem.getStyle(); + style.setProperty(ANIMATION_PROPERTY_NAME + "Duration", duration); + } + + /** + * For internal use only. May be removed or replaced in the future. + * + * Set the animation-delay CSS property. + * + * @param elem + * the element whose animation-delay to set + * @param delay + * the delay as a valid CSS value + */ + public static void setAnimationDelay(Element elem, String delay) { + Style style = elem.getStyle(); + style.setProperty(ANIMATION_PROPERTY_NAME + "Delay", delay); + } + + /** For internal use only. May be removed or replaced in the future. */ + public static native JavaScriptObject addAnimationEndListener(Element elem, + AnimationEndListener listener) + /*-{ + var callbackFunc = $entry(function(e) { + listener.@com.vaadin.client.AnimationUtil.AnimationEndListener::onAnimationEnd(Lcom/google/gwt/dom/client/NativeEvent;)(e); + }); + + elem.addEventListener(@com.vaadin.client.AnimationUtil::ANIMATION_END_EVENT_NAME, callbackFunc, false); + + // Store function reference for later removal + if(!elem._vaadin_animationend_callbacks) { + elem._vaadin_animationend_callbacks = []; + } + elem._vaadin_animationend_callbacks.push(callbackFunc); + + return callbackFunc; + }-*/; + + /** For internal use only. May be removed or replaced in the future. */ + public static native void removeAnimationEndListener(Element elem, + JavaScriptObject listener) + /*-{ + elem.removeEventListener(@com.vaadin.client.AnimationUtil::ANIMATION_END_EVENT_NAME, listener, false); + }-*/; + + /** For internal use only. May be removed or replaced in the future. */ + public static native void removeAllAnimationEndListeners(Element elem) + /*-{ + if(elem._vaadin_animationend_callbacks) { + var callbacks = elem._vaadin_animationend_callbacks; + for(var i=0; i < callbacks.length; i++) { + elem.removeEventListener(@com.vaadin.client.AnimationUtil::ANIMATION_END_EVENT_NAME, callbacks[i], false); + } + } + }-*/; + + /** For internal use only. May be removed or replaced in the future. */ + public interface AnimationEndListener { + public void onAnimationEnd(NativeEvent event); + } + + /** For internal use only. May be removed or replaced in the future. */ + public static native String getAnimationName(NativeEvent event) + /*-{ + if(event.webkitAnimationName) + return event.webkitAnimationName; + else if(event.animationName) + return event.animationName; + else if(event.mozAnimationName) + return event.mozAnimationName; + else if(event.oAnimationName) + return event.oAnimationName; + + return ""; + }-*/; + + /** For internal use only. May be removed or replaced in the future. */ + public static native String getAnimationName(ComputedStyle cstyle) + /*-{ + var cs = cstyle.@com.vaadin.client.ComputedStyle::computedStyle; + + if(!cs.getPropertyValue) + return ""; + + if(cs.getPropertyValue("-webkit-animation-name")) + return cs.getPropertyValue("-webkit-animation-name"); + + else if(cs.getPropertyValue("animation-name")) + return cs.getPropertyValue("animation-name"); + + else if(cs.getPropertyValue("-moz-animation-name")) + return cs.getPropertyValue("-moz-animation-name"); + + else if(cs.getPropertyValue("-o-animation-name")) + return cs.getPropertyValue("-o-animation-name"); + + return ""; + }-*/; + + private static final String ANIMATION_END_EVENT_NAME = whichAnimationEndEvent(); + + private static native String whichAnimationEndEvent() + /*-{ + var el = document.createElement('fakeelement'); + var anims = { + 'animationName': 'animationend', + 'OAnimationName': 'oAnimationEnd', + 'MozAnimation': 'animationend', + 'WebkitAnimation': 'webkitAnimationEnd' + } + + for(var a in anims){ + if( el.style[a] !== undefined ){ + return anims[a]; + } + } + }-*/; + + private static final String ANIMATION_PROPERTY_NAME = whichAnimationProperty(); + + private static native String whichAnimationProperty() + /*-{ + var el = document.createElement('fakeelement'); + var anims = [ + 'animation', + 'oAnimation', + 'mozAnimation', + 'webkitAnimation' + ] + + for(var i=0; i < anims.length; i++) { + if( el.style[anims[i]] !== undefined ){ + return anims[i]; + } + } + }-*/; + +} diff --git a/client/src/main/java/com/vaadin/client/ApplicationConfiguration.java b/client/src/main/java/com/vaadin/client/ApplicationConfiguration.java new file mode 100644 index 0000000000..f14802c931 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ApplicationConfiguration.java @@ -0,0 +1,876 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.GWT.UncaughtExceptionHandler; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArrayString; +import com.google.gwt.core.client.RunAsyncCallback; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.core.client.ScriptInjector; +import com.google.gwt.dom.client.Element; +import com.google.gwt.logging.client.LogConfiguration; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Window; +import com.vaadin.client.debug.internal.ErrorNotificationHandler; +import com.vaadin.client.debug.internal.HierarchySection; +import com.vaadin.client.debug.internal.InfoSection; +import com.vaadin.client.debug.internal.LogSection; +import com.vaadin.client.debug.internal.NetworkSection; +import com.vaadin.client.debug.internal.ProfilerSection; +import com.vaadin.client.debug.internal.Section; +import com.vaadin.client.debug.internal.TestBenchSection; +import com.vaadin.client.debug.internal.VDebugWindow; +import com.vaadin.client.debug.internal.theme.DebugWindowStyles; +import com.vaadin.client.event.PointerEventSupport; +import com.vaadin.client.metadata.BundleLoadCallback; +import com.vaadin.client.metadata.ConnectorBundleLoader; +import com.vaadin.client.metadata.NoDataException; +import com.vaadin.client.metadata.TypeData; +import com.vaadin.client.ui.UnknownComponentConnector; +import com.vaadin.client.ui.ui.UIConnector; +import com.vaadin.shared.ApplicationConstants; +import com.vaadin.shared.ui.ui.UIConstants; + +public class ApplicationConfiguration implements EntryPoint { + + /** + * Helper class for reading configuration options from the bootstap + * javascript + * + * @since 7.0 + */ + private static class JsoConfiguration extends JavaScriptObject { + protected JsoConfiguration() { + // JSO Constructor + } + + /** + * Reads a configuration parameter as a string. Please note that the + * javascript value of the parameter should also be a string, or else an + * undefined exception may be thrown. + * + * @param name + * name of the configuration parameter + * @return value of the configuration parameter, or null if + * not defined + */ + private native String getConfigString(String name) + /*-{ + var value = this.getConfig(name); + if (value === null || value === undefined) { + return null; + } else { + return value +""; + } + }-*/; + + /** + * Reads a configuration parameter as a boolean object. Please note that + * the javascript value of the parameter should also be a boolean, or + * else an undefined exception may be thrown. + * + * @param name + * name of the configuration parameter + * @return boolean value of the configuration paramter, or + * null if no value is defined + */ + private native Boolean getConfigBoolean(String name) + /*-{ + var value = this.getConfig(name); + if (value === null || value === undefined) { + return null; + } else { + // $entry not needed as function is not exported + return @java.lang.Boolean::valueOf(Z)(value); + } + }-*/; + + /** + * Reads a configuration parameter as an integer object. Please note + * that the javascript value of the parameter should also be an integer, + * or else an undefined exception may be thrown. + * + * @param name + * name of the configuration parameter + * @return integer value of the configuration paramter, or + * null if no value is defined + */ + private native Integer getConfigInteger(String name) + /*-{ + var value = this.getConfig(name); + if (value === null || value === undefined) { + return null; + } else { + // $entry not needed as function is not exported + return @java.lang.Integer::valueOf(I)(value); + } + }-*/; + + /** + * Reads a configuration parameter as an {@link ErrorMessage} object. + * Please note that the javascript value of the parameter should also be + * an object with appropriate fields, or else an undefined exception may + * be thrown when calling this method or when calling methods on the + * returned object. + * + * @param name + * name of the configuration parameter + * @return error message with the given name, or null if no + * value is defined + */ + private native ErrorMessage getConfigError(String name) + /*-{ + return this.getConfig(name); + }-*/; + + /** + * Returns a native javascript object containing version information + * from the server. + * + * @return a javascript object with the version information + */ + private native JavaScriptObject getVersionInfoJSObject() + /*-{ + return this.getConfig("versionInfo"); + }-*/; + + /** + * Gets the version of the Vaadin framework used on the server. + * + * @return a string with the version + * + * @see com.vaadin.server.VaadinServlet#VERSION + */ + private native String getVaadinVersion() + /*-{ + return this.getConfig("versionInfo").vaadinVersion; + }-*/; + + /** + * Gets the version of the Atmosphere framework. + * + * @return a string with the version + * + * @see org.atmosphere.util#getRawVersion() + */ + private native String getAtmosphereVersion() + /*-{ + return this.getConfig("versionInfo").atmosphereVersion; + }-*/; + + /** + * Gets the JS version used in the Atmosphere framework. + * + * @return a string with the version + */ + private native String getAtmosphereJSVersion() + /*-{ + if ($wnd.jQueryVaadin != undefined){ + return $wnd.jQueryVaadin.atmosphere.version; + } + else { + return null; + } + }-*/; + + private native String getUIDL() + /*-{ + return this.getConfig("uidl"); + }-*/; + } + + /** + * Wraps a native javascript object containing fields for an error message + * + * @since 7.0 + */ + public static final class ErrorMessage extends JavaScriptObject { + + protected ErrorMessage() { + // JSO constructor + } + + public final native String getCaption() + /*-{ + return this.caption; + }-*/; + + public final native String getMessage() + /*-{ + return this.message; + }-*/; + + public final native String getUrl() + /*-{ + return this.url; + }-*/; + } + + private static WidgetSet widgetSet = GWT.create(WidgetSet.class); + + private String id; + /** + * The URL to the VAADIN directory containing themes and widgetsets. Should + * always end with a slash (/). + */ + private String vaadinDirUrl; + private String serviceUrl; + private int uiId; + private boolean standalone; + private ErrorMessage communicationError; + private ErrorMessage authorizationError; + private ErrorMessage sessionExpiredError; + private int heartbeatInterval; + + private HashMap unknownComponents; + + private Map> classes = new HashMap>(); + + private boolean widgetsetVersionSent = false; + private static boolean moduleLoaded = false; + + static// TODO consider to make this hashmap per application + LinkedList callbacks = new LinkedList(); + + private static int dependenciesLoading; + + private static ArrayList runningApplications = new ArrayList(); + + private Map componentInheritanceMap = new HashMap(); + private Map tagToServerSideClassName = new HashMap(); + + /** + * Checks whether path info in requests to the server-side service should be + * in a request parameter (named v-resourcePath) or appended to + * the end of the service URL. + * + * @see #getServiceUrl() + * + * @return true if path info should be a request parameter; + * false if the path info goes after the service URL + */ + public boolean useServiceUrlPathParam() { + return getServiceUrlParameterName() != null; + } + + /** + * Return the name of the parameter used to to send data to the service url. + * This method should only be called if {@link #useServiceUrlPathParam()} is + * true. + * + * @since 7.1.6 + * @return The parameter name, by default v-resourcePath + */ + public String getServiceUrlParameterName() { + return getJsoConfiguration(id).getConfigString( + ApplicationConstants.SERVICE_URL_PARAMETER_NAME); + } + + public String getRootPanelId() { + return id; + } + + /** + * Gets the URL to the server-side VaadinService. If + * {@link #useServiceUrlPathParam()} return true, the requested + * path info should be in the v-resourcePath query parameter; + * else the path info should be appended to the end of the URL. + * + * @see #useServiceUrlPathParam() + * + * @return the URL to the server-side service as a string + */ + public String getServiceUrl() { + return serviceUrl; + } + + /** + * @return the theme name used when initializing the application + * @deprecated as of 7.3. Use {@link UIConnector#getActiveTheme()} to get + * the theme currently in use + */ + @Deprecated + public String getThemeName() { + return getJsoConfiguration(id).getConfigString("theme"); + } + + /** + * Gets the URL of the VAADIN directory on the server. + * + * @return the URL of the VAADIN directory + */ + public String getVaadinDirUrl() { + return vaadinDirUrl; + } + + public void setAppId(String appId) { + id = appId; + } + + /** + * Gets the initial UIDL from the DOM, if it was provided during the init + * process. + * + * @return + */ + public String getUIDL() { + return getJsoConfiguration(id).getUIDL(); + } + + /** + * @return true if the application is served by std. Vaadin servlet and is + * considered to be the only or main content of the host page. + */ + public boolean isStandalone() { + return standalone; + } + + /** + * Gets the UI id of the server-side UI associated with this client-side + * instance. The UI id should be included in every request originating from + * this instance in order to associate the request with the right UI + * instance on the server. + * + * @return the UI id + */ + public int getUIId() { + return uiId; + } + + /** + * @return The interval in seconds between heartbeat requests, or a + * non-positive number if heartbeat is disabled. + */ + public int getHeartbeatInterval() { + return heartbeatInterval; + } + + public JavaScriptObject getVersionInfoJSObject() { + return getJsoConfiguration(id).getVersionInfoJSObject(); + } + + public ErrorMessage getCommunicationError() { + return communicationError; + } + + public ErrorMessage getAuthorizationError() { + return authorizationError; + } + + public ErrorMessage getSessionExpiredError() { + return sessionExpiredError; + } + + /** + * Reads the configuration values defined by the bootstrap javascript. + */ + private void loadFromDOM() { + JsoConfiguration jsoConfiguration = getJsoConfiguration(id); + serviceUrl = jsoConfiguration + .getConfigString(ApplicationConstants.SERVICE_URL); + if (serviceUrl == null || "".equals(serviceUrl)) { + /* + * Use the current url without query parameters and fragment as the + * default value. + */ + serviceUrl = Window.Location.getHref().replaceFirst("[?#].*", ""); + } else { + /* + * Resolve potentially relative URLs to ensure they point to the + * desired locations even if the base URL of the page changes later + * (e.g. with pushState) + */ + serviceUrl = WidgetUtil.getAbsoluteUrl(serviceUrl); + } + // Ensure there's an ending slash (to make appending e.g. UIDL work) + if (!useServiceUrlPathParam() && !serviceUrl.endsWith("/")) { + serviceUrl += '/'; + } + + vaadinDirUrl = WidgetUtil.getAbsoluteUrl(jsoConfiguration + .getConfigString(ApplicationConstants.VAADIN_DIR_URL)); + uiId = jsoConfiguration.getConfigInteger(UIConstants.UI_ID_PARAMETER) + .intValue(); + + // null -> false + standalone = jsoConfiguration.getConfigBoolean("standalone") == Boolean.TRUE; + + heartbeatInterval = jsoConfiguration + .getConfigInteger("heartbeatInterval"); + + communicationError = jsoConfiguration.getConfigError("comErrMsg"); + authorizationError = jsoConfiguration.getConfigError("authErrMsg"); + sessionExpiredError = jsoConfiguration.getConfigError("sessExpMsg"); + } + + /** + * Starts the application with a given id by reading the configuration + * options stored by the bootstrap javascript. + * + * @param applicationId + * id of the application to load, this is also the id of the html + * element into which the application should be rendered. + */ + public static void startApplication(final String applicationId) { + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + + @Override + public void execute() { + Profiler.enter("ApplicationConfiguration.startApplication"); + ApplicationConfiguration appConf = getConfigFromDOM(applicationId); + ApplicationConnection a = GWT + .create(ApplicationConnection.class); + a.init(widgetSet, appConf); + runningApplications.add(a); + Profiler.leave("ApplicationConfiguration.startApplication"); + + a.start(); + } + }); + } + + public static List getRunningApplications() { + return runningApplications; + } + + /** + * Gets the configuration object for a specific application from the + * bootstrap javascript. + * + * @param appId + * the id of the application to get configuration data for + * @return a native javascript object containing the configuration data + */ + private native static JsoConfiguration getJsoConfiguration(String appId) + /*-{ + return $wnd.vaadin.getApp(appId); + }-*/; + + public static ApplicationConfiguration getConfigFromDOM(String appId) { + ApplicationConfiguration conf = new ApplicationConfiguration(); + conf.setAppId(appId); + conf.loadFromDOM(); + return conf; + } + + public String getServletVersion() { + return getJsoConfiguration(id).getVaadinVersion(); + } + + /** + * Return Atmosphere version. + * + * @since 7.4 + * + * @return Atmosphere version. + */ + public String getAtmosphereVersion() { + return getJsoConfiguration(id).getAtmosphereVersion(); + } + + /** + * Return Atmosphere JS version. + * + * @since 7.4 + * + * @return Atmosphere JS version. + */ + public String getAtmosphereJSVersion() { + return getJsoConfiguration(id).getAtmosphereJSVersion(); + } + + public Class getConnectorClassByEncodedTag( + int tag) { + Class type = classes.get(tag); + if (type == null && !classes.containsKey(tag)) { + // Initialize if not already loaded + Integer currentTag = Integer.valueOf(tag); + while (type == null && currentTag != null) { + String serverSideClassNameForTag = getServerSideClassNameForTag(currentTag); + if (TypeData.hasIdentifier(serverSideClassNameForTag)) { + try { + type = (Class) TypeData + .getClass(serverSideClassNameForTag); + } catch (NoDataException e) { + throw new RuntimeException(e); + } + } + currentTag = getParentTag(currentTag.intValue()); + } + if (type == null) { + type = UnknownComponentConnector.class; + if (unknownComponents == null) { + unknownComponents = new HashMap(); + } + unknownComponents.put(tag, getServerSideClassNameForTag(tag)); + } + classes.put(tag, type); + } + return type; + } + + public void addComponentInheritanceInfo(ValueMap valueMap) { + JsArrayString keyArray = valueMap.getKeyArray(); + for (int i = 0; i < keyArray.length(); i++) { + String key = keyArray.get(i); + int value = valueMap.getInt(key); + componentInheritanceMap.put(Integer.parseInt(key), value); + } + } + + public void addComponentMappings(ValueMap valueMap, WidgetSet widgetSet) { + JsArrayString keyArray = valueMap.getKeyArray(); + for (int i = 0; i < keyArray.length(); i++) { + String key = keyArray.get(i).intern(); + int value = valueMap.getInt(key); + tagToServerSideClassName.put(value, key); + } + + for (int i = 0; i < keyArray.length(); i++) { + String key = keyArray.get(i).intern(); + int value = valueMap.getInt(key); + widgetSet.ensureConnectorLoaded(value, this); + } + } + + /** + * Returns all tags for given class. Tags are used in + * {@link ApplicationConfiguration} to keep track of different classes and + * their hierarchy + * + * @since 7.2 + * @param classname + * name of class which tags we want + * @return Integer array of tags pointing to this classname + */ + public Integer[] getTagsForServerSideClassName(String classname) { + List tags = new ArrayList(); + + for (Map.Entry entry : tagToServerSideClassName + .entrySet()) { + if (classname.equals(entry.getValue())) { + tags.add(entry.getKey()); + } + } + + Integer[] out = new Integer[tags.size()]; + return tags.toArray(out); + } + + public Integer getParentTag(int tag) { + return componentInheritanceMap.get(tag); + } + + public String getServerSideClassNameForTag(Integer tag) { + return tagToServerSideClassName.get(tag); + } + + String getUnknownServerClassNameByTag(int tag) { + if (unknownComponents != null) { + String className = unknownComponents.get(tag); + if (className == null) { + className = "unknown class with id " + tag; + } + return className; + } + return null; + } + + /** + * @since 7.6 + * @param c + */ + public static void runWhenDependenciesLoaded(Command c) { + if (dependenciesLoading == 0) { + c.execute(); + } else { + callbacks.add(c); + } + } + + static void startDependencyLoading() { + dependenciesLoading++; + } + + static void endDependencyLoading() { + dependenciesLoading--; + if (dependenciesLoading == 0 && !callbacks.isEmpty()) { + for (Command cmd : callbacks) { + cmd.execute(); + } + callbacks.clear(); + } else if (dependenciesLoading == 0 + && !ConnectorBundleLoader.get().isBundleLoaded( + ConnectorBundleLoader.DEFERRED_BUNDLE_NAME)) { + ConnectorBundleLoader.get().loadBundle( + ConnectorBundleLoader.DEFERRED_BUNDLE_NAME, + new BundleLoadCallback() { + @Override + public void loaded() { + // Nothing to do + } + + @Override + public void failed(Throwable reason) { + getLogger().log(Level.SEVERE, + "Error loading deferred bundle", reason); + } + }); + } + } + + private boolean vaadinBootstrapLoaded() { + Element window = ScriptInjector.TOP_WINDOW.cast(); + return window.getPropertyJSO("vaadin") != null; + } + + @Override + public void onModuleLoad() { + + // Don't run twice if the module has been inherited several times, + // and don't continue if vaadinBootstrap was not executed. + if (moduleLoaded || !vaadinBootstrapLoaded()) { + getLogger() + .log(Level.WARNING, + "vaadinBootstrap.js was not loaded, skipping vaadin application configuration."); + return; + } + moduleLoaded = true; + + Profiler.initialize(); + Profiler.enter("ApplicationConfiguration.onModuleLoad"); + + BrowserInfo browserInfo = BrowserInfo.get(); + + // Enable iOS6 cast fix (see #10460) + if (browserInfo.isIOS6() && browserInfo.isWebkit()) { + enableIOS6castFix(); + } + + // Enable IE prompt fix (#13367) + if (browserInfo.isIE() && browserInfo.getBrowserMajorVersion() >= 10) { + enableIEPromptFix(); + } + + // Register pointer events (must be done before any events are used) + PointerEventSupport.init(); + + // Prepare the debugging window + if (isDebugMode()) { + /* + * XXX Lots of implementation details here right now. This should be + * cleared up when an API for extending the debug window is + * implemented. + */ + VDebugWindow window = VDebugWindow.get(); + + if (LogConfiguration.loggingIsEnabled()) { + window.addSection((Section) GWT.create(LogSection.class)); + } + window.addSection((Section) GWT.create(InfoSection.class)); + window.addSection((Section) GWT.create(HierarchySection.class)); + window.addSection((Section) GWT.create(NetworkSection.class)); + window.addSection((Section) GWT.create(TestBenchSection.class)); + if (Profiler.isEnabled()) { + window.addSection((Section) GWT.create(ProfilerSection.class)); + } + + if (isQuietDebugMode()) { + window.close(); + } else { + // Load debug window styles asynchronously + GWT.runAsync(new RunAsyncCallback() { + @Override + public void onSuccess() { + DebugWindowStyles dws = GWT + .create(DebugWindowStyles.class); + dws.css().ensureInjected(); + } + + @Override + public void onFailure(Throwable reason) { + Window.alert("Failed to load Vaadin debug window styles"); + } + }); + + window.init(); + } + + // Connect to the legacy API + VConsole.setImplementation(window); + + Handler errorNotificationHandler = GWT + .create(ErrorNotificationHandler.class); + Logger.getLogger("").addHandler(errorNotificationHandler); + } + + if (LogConfiguration.loggingIsEnabled()) { + GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { + + @Override + public void onUncaughtException(Throwable e) { + /* + * If the debug window is not enabled (?debug), this will + * not show anything to normal users. "a1 is not an object" + * style errors helps nobody, especially end user. It does + * not work tells just as much. + */ + getLogger().log(Level.SEVERE, e.getMessage(), e); + } + }); + + if (isProductionMode()) { + // Disable all logging if in production mode + Logger.getLogger("").setLevel(Level.OFF); + } + } + Profiler.leave("ApplicationConfiguration.onModuleLoad"); + + if (SuperDevMode.enableBasedOnParameter()) { + // Do not start any application as super dev mode will refresh the + // page once done compiling + return; + } + registerCallback(GWT.getModuleName()); + } + + /** + * Fix to iOS6 failing when comparing with 0 directly after the kind of + * comparison done by GWT when a double or float is cast to an int. Forcing + * another trivial operation (other than a compare to 0) after the dangerous + * comparison makes the issue go away. See #10460. + */ + private static native void enableIOS6castFix() + /*-{ + Math.max = function(a,b) {return (a > b === 1 < 2)? a : b} + Math.min = function(a,b) {return (a < b === 1 < 2)? a : b} + }-*/; + + /** + * Make Metro versions of IE suggest switching to the desktop when + * window.prompt is called. + */ + private static native void enableIEPromptFix() + /*-{ + var prompt = $wnd.prompt; + $wnd.prompt = function () { + var result = prompt.apply($wnd, Array.prototype.slice.call(arguments)); + if (result === undefined) { + // force the browser to suggest desktop mode + showModalDialog(); + return null; + } else { + return result; + } + }; + }-*/; + + /** + * Registers that callback that the bootstrap javascript uses to start + * applications once the widgetset is loaded and all required information is + * available + * + * @param widgetsetName + * the name of this widgetset + */ + public native static void registerCallback(String widgetsetName) + /*-{ + var callbackHandler = $entry(@com.vaadin.client.ApplicationConfiguration::startApplication(Ljava/lang/String;)); + $wnd.vaadin.registerWidgetset(widgetsetName, callbackHandler); + }-*/; + + /** + * Checks if client side is in debug mode. Practically this is invoked by + * adding ?debug parameter to URI. Please note that debug mode is always + * disabled if production mode is enabled, but disabling production mode + * does not automatically enable debug mode. + * + * @see #isProductionMode() + * + * @return true if client side is currently been debugged + */ + public static boolean isDebugMode() { + return isDebugAvailable() + && Window.Location.getParameter("debug") != null; + } + + /** + * Checks if production mode is enabled. When production mode is enabled, + * client-side logging is disabled. There may also be other performance + * optimizations. + * + * @since 7.1.2 + * @return true if production mode is enabled; otherwise + * false. + */ + public static boolean isProductionMode() { + return !isDebugAvailable(); + } + + private native static boolean isDebugAvailable() + /*-{ + if($wnd.vaadin.debug) { + return true; + } else { + return false; + } + }-*/; + + /** + * Checks whether debug logging should be quiet + * + * @return true if debug logging should be quiet + */ + public static boolean isQuietDebugMode() { + String debugParameter = Window.Location.getParameter("debug"); + return isDebugAvailable() && debugParameter != null + && debugParameter.startsWith("q"); + } + + /** + * Checks whether the widget set version has been sent to the server. It is + * sent in the first UIDL request. + * + * @return true if browser information has already been sent + */ + public boolean isWidgetsetVersionSent() { + return widgetsetVersionSent; + } + + /** + * Registers that the widget set version has been sent to the server. + */ + public void setWidgetsetVersionSent() { + widgetsetVersionSent = true; + } + + private static final Logger getLogger() { + return Logger.getLogger(ApplicationConfiguration.class.getName()); + } +} diff --git a/client/src/main/java/com/vaadin/client/ApplicationConnection.java b/client/src/main/java/com/vaadin/client/ApplicationConnection.java new file mode 100644 index 0000000000..ce55c13ce5 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ApplicationConnection.java @@ -0,0 +1,1642 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import com.google.gwt.aria.client.LiveValue; +import com.google.gwt.aria.client.RelevantValue; +import com.google.gwt.aria.client.Roles; +import com.google.gwt.core.client.Duration; +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArrayString; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.shared.EventBus; +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.event.shared.GwtEvent; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.event.shared.HasHandlers; +import com.google.gwt.event.shared.SimpleEventBus; +import com.google.gwt.http.client.URL; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.HasWidgets; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConfiguration.ErrorMessage; +import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; +import com.vaadin.client.ResourceLoader.ResourceLoadEvent; +import com.vaadin.client.ResourceLoader.ResourceLoadListener; +import com.vaadin.client.communication.ConnectionStateHandler; +import com.vaadin.client.communication.Heartbeat; +import com.vaadin.client.communication.MessageHandler; +import com.vaadin.client.communication.MessageSender; +import com.vaadin.client.communication.RpcManager; +import com.vaadin.client.communication.ServerRpcQueue; +import com.vaadin.client.componentlocator.ComponentLocator; +import com.vaadin.client.metadata.ConnectorBundleLoader; +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.FontIcon; +import com.vaadin.client.ui.Icon; +import com.vaadin.client.ui.ImageIcon; +import com.vaadin.client.ui.VContextMenu; +import com.vaadin.client.ui.VNotification; +import com.vaadin.client.ui.VOverlay; +import com.vaadin.client.ui.ui.UIConnector; +import com.vaadin.shared.VaadinUriResolver; +import com.vaadin.shared.Version; +import com.vaadin.shared.communication.LegacyChangeVariablesInvocation; +import com.vaadin.shared.util.SharedUtil; + +/** + * This is the client side communication "engine", managing client-server + * communication with its server side counterpart + * com.vaadin.server.VaadinService. + * + * Client-side connectors receive updates from the corresponding server-side + * connector (typically component) as state updates or RPC calls. The connector + * has the possibility to communicate back with its server side counter part + * through RPC calls. + * + * TODO document better + * + * Entry point classes (widgetsets) define onModuleLoad(). + */ +public class ApplicationConnection implements HasHandlers { + + @Deprecated + public static final String MODIFIED_CLASSNAME = StyleConstants.MODIFIED; + + @Deprecated + public static final String DISABLED_CLASSNAME = StyleConstants.DISABLED; + + @Deprecated + public static final String REQUIRED_CLASSNAME = StyleConstants.REQUIRED; + + @Deprecated + public static final String REQUIRED_CLASSNAME_EXT = StyleConstants.REQUIRED_EXT; + + @Deprecated + public static final String ERROR_CLASSNAME_EXT = StyleConstants.ERROR_EXT; + + /** + * A string that, if found in a non-JSON response to a UIDL request, will + * cause the browser to refresh the page. If followed by a colon, optional + * whitespace, and a URI, causes the browser to synchronously load the URI. + * + *

+ * This allows, for instance, a servlet filter to redirect the application + * to a custom login page when the session expires. For example: + *

+ * + *
+     * if (sessionExpired) {
+     *     response.setHeader("Content-Type", "text/html");
+     *     response.getWriter().write(
+     *             myLoginPageHtml + "<!-- Vaadin-Refresh: "
+     *                     + request.getContextPath() + " -->");
+     * }
+     * 
+ */ + public static final String UIDL_REFRESH_TOKEN = "Vaadin-Refresh"; + + private final HashMap resourcesMap = new HashMap(); + + private WidgetSet widgetSet; + + private VContextMenu contextMenu = null; + + private final UIConnector uIConnector; + + protected boolean cssLoaded = false; + + /** Parameters for this application connection loaded from the web-page */ + private ApplicationConfiguration configuration; + + private final LayoutManager layoutManager; + + private final RpcManager rpcManager; + + /** Event bus for communication events */ + private EventBus eventBus = GWT.create(SimpleEventBus.class); + + public enum ApplicationState { + INITIALIZING, RUNNING, TERMINATED; + } + + private ApplicationState applicationState = ApplicationState.INITIALIZING; + + /** + * The communication handler methods are called at certain points during + * communication with the server. This allows for making add-ons that keep + * track of different aspects of the communication. + */ + public interface CommunicationHandler extends EventHandler { + void onRequestStarting(RequestStartingEvent e); + + void onResponseHandlingStarted(ResponseHandlingStartedEvent e); + + void onResponseHandlingEnded(ResponseHandlingEndedEvent e); + } + + public static class RequestStartingEvent extends ApplicationConnectionEvent { + + public static Type TYPE = new Type(); + + public RequestStartingEvent(ApplicationConnection connection) { + super(connection); + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(CommunicationHandler handler) { + handler.onRequestStarting(this); + } + } + + public static class ResponseHandlingEndedEvent extends + ApplicationConnectionEvent { + + public static Type TYPE = new Type(); + + public ResponseHandlingEndedEvent(ApplicationConnection connection) { + super(connection); + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(CommunicationHandler handler) { + handler.onResponseHandlingEnded(this); + } + } + + public static abstract class ApplicationConnectionEvent extends + GwtEvent { + + private ApplicationConnection connection; + + protected ApplicationConnectionEvent(ApplicationConnection connection) { + this.connection = connection; + } + + public ApplicationConnection getConnection() { + return connection; + } + + } + + public static class ResponseHandlingStartedEvent extends + ApplicationConnectionEvent { + + public ResponseHandlingStartedEvent(ApplicationConnection connection) { + super(connection); + } + + public static Type TYPE = new Type(); + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(CommunicationHandler handler) { + handler.onResponseHandlingStarted(this); + } + } + + /** + * Event triggered when a application is stopped by calling + * {@link ApplicationConnection#setApplicationRunning(false)}. + * + * To listen for the event add a {@link ApplicationStoppedHandler} by + * invoking + * {@link ApplicationConnection#addHandler(ApplicationConnection.ApplicationStoppedEvent.Type, ApplicationStoppedHandler)} + * to the {@link ApplicationConnection} + * + * @since 7.1.8 + * @author Vaadin Ltd + */ + public static class ApplicationStoppedEvent extends + GwtEvent { + + public static Type TYPE = new Type(); + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(ApplicationStoppedHandler listener) { + listener.onApplicationStopped(this); + } + } + + /** + * Allows custom handling of communication errors. + */ + public interface CommunicationErrorHandler { + /** + * Called when a communication error has occurred. Returning + * true from this method suppresses error handling. + * + * @param details + * A string describing the error. + * @param statusCode + * The HTTP status code (e.g. 404, etc). + * @return true if the error reporting should be suppressed, false to + * perform normal error reporting. + */ + public boolean onError(String details, int statusCode); + } + + /** + * A listener for listening to application stopped events. The listener can + * be added to a {@link ApplicationConnection} by invoking + * {@link ApplicationConnection#addHandler(ApplicationStoppedEvent.Type, ApplicationStoppedHandler)} + * + * @since 7.1.8 + * @author Vaadin Ltd + */ + public interface ApplicationStoppedHandler extends EventHandler { + + /** + * Triggered when the {@link ApplicationConnection} marks a previously + * running application as stopped by invoking + * {@link ApplicationConnection#setApplicationRunning(false)} + * + * @param event + * the event triggered by the {@link ApplicationConnection} + */ + void onApplicationStopped(ApplicationStoppedEvent event); + } + + private CommunicationErrorHandler communicationErrorDelegate = null; + + private VLoadingIndicator loadingIndicator; + + private Heartbeat heartbeat = GWT.create(Heartbeat.class); + + private boolean tooltipInitialized = false; + + private final VaadinUriResolver uriResolver = new VaadinUriResolver() { + @Override + protected String getVaadinDirUrl() { + return getConfiguration().getVaadinDirUrl(); + } + + @Override + protected String getServiceUrlParameterName() { + return getConfiguration().getServiceUrlParameterName(); + } + + @Override + protected String getServiceUrl() { + return getConfiguration().getServiceUrl(); + } + + @Override + protected String getThemeUri() { + return ApplicationConnection.this.getThemeUri(); + } + + @Override + protected String encodeQueryStringParameterValue(String queryString) { + return URL.encodeQueryString(queryString); + } + }; + + public static class MultiStepDuration extends Duration { + private int previousStep = elapsedMillis(); + + public void logDuration(String message) { + logDuration(message, 0); + } + + public void logDuration(String message, int minDuration) { + int currentTime = elapsedMillis(); + int stepDuration = currentTime - previousStep; + if (stepDuration >= minDuration) { + getLogger().info(message + ": " + stepDuration + " ms"); + } + previousStep = currentTime; + } + } + + public ApplicationConnection() { + // Assuming UI data is eagerly loaded + ConnectorBundleLoader.get().loadBundle( + ConnectorBundleLoader.EAGER_BUNDLE_NAME, null); + uIConnector = GWT.create(UIConnector.class); + rpcManager = GWT.create(RpcManager.class); + layoutManager = GWT.create(LayoutManager.class); + tooltip = GWT.create(VTooltip.class); + loadingIndicator = GWT.create(VLoadingIndicator.class); + serverRpcQueue = GWT.create(ServerRpcQueue.class); + connectionStateHandler = GWT.create(ConnectionStateHandler.class); + messageHandler = GWT.create(MessageHandler.class); + messageSender = GWT.create(MessageSender.class); + } + + public void init(WidgetSet widgetSet, ApplicationConfiguration cnf) { + getLogger().info("Starting application " + cnf.getRootPanelId()); + getLogger().info("Using theme: " + cnf.getThemeName()); + + getLogger().info( + "Vaadin application servlet version: " + + cnf.getServletVersion()); + + if (!cnf.getServletVersion().equals(Version.getFullVersion())) { + getLogger() + .severe("Warning: your widget set seems to be built with a different " + + "version than the one used on server. Unexpected " + + "behavior may occur."); + } + + this.widgetSet = widgetSet; + configuration = cnf; + + layoutManager.setConnection(this); + loadingIndicator.setConnection(this); + serverRpcQueue.setConnection(this); + messageHandler.setConnection(this); + messageSender.setConnection(this); + + ComponentLocator componentLocator = new ComponentLocator(this); + + String appRootPanelName = cnf.getRootPanelId(); + // remove the end (window name) of autogenerated rootpanel id + appRootPanelName = appRootPanelName.replaceFirst("-\\d+$", ""); + + initializeTestbenchHooks(componentLocator, appRootPanelName); + + initializeClientHooks(); + + uIConnector.init(cnf.getRootPanelId(), this); + + // Connection state handler preloads the reconnect dialog, which uses + // overlay container. This in turn depends on VUI being attached + // (done in uiConnector.init) + connectionStateHandler.setConnection(this); + + tooltip.setOwner(uIConnector.getWidget()); + + getLoadingIndicator().show(); + + heartbeat.init(this); + + // Ensure the overlay container is added to the dom and set as a live + // area for assistive devices + Element overlayContainer = VOverlay.getOverlayContainer(this); + Roles.getAlertRole().setAriaLiveProperty(overlayContainer, + LiveValue.ASSERTIVE); + VOverlay.setOverlayContainerLabel(this, + getUIConnector().getState().overlayContainerLabel); + Roles.getAlertRole().setAriaRelevantProperty(overlayContainer, + RelevantValue.ADDITIONS); + } + + /** + * Starts this application. Don't call this method directly - it's called by + * {@link ApplicationConfiguration#startNextApplication()}, which should be + * called once this application has started (first response received) or + * failed to start. This ensures that the applications are started in order, + * to avoid session-id problems. + * + */ + public void start() { + String jsonText = configuration.getUIDL(); + if (jsonText == null) { + // initial UIDL not in DOM, request from server + getMessageSender().resynchronize(); + } else { + // initial UIDL provided in DOM, continue as if returned by request + + // Hack to avoid logging an error in endRequest() + getMessageSender().startRequest(); + getMessageHandler().handleMessage( + MessageHandler.parseJson(jsonText)); + } + + // Tooltip can't be created earlier because the + // necessary fields are not setup to add it in the + // correct place in the DOM + if (!tooltipInitialized) { + tooltipInitialized = true; + ApplicationConfiguration.runWhenDependenciesLoaded(new Command() { + @Override + public void execute() { + getVTooltip().initializeAssistiveTooltips(); + } + }); + } + } + + /** + * Checks if there is some work to be done on the client side + * + * @return true if the client has some work to be done, false otherwise + */ + private boolean isActive() { + return !getMessageHandler().isInitialUidlHandled() || isWorkPending() + || getMessageSender().hasActiveRequest() + || isExecutingDeferredCommands(); + } + + private native void initializeTestbenchHooks( + ComponentLocator componentLocator, String TTAppId) + /*-{ + var ap = this; + var client = {}; + client.isActive = $entry(function() { + return ap.@com.vaadin.client.ApplicationConnection::isActive()(); + }); + var vi = ap.@com.vaadin.client.ApplicationConnection::getVersionInfo()(); + if (vi) { + client.getVersionInfo = function() { + return vi; + } + } + + client.getProfilingData = $entry(function() { + var smh = ap.@com.vaadin.client.ApplicationConnection::getMessageHandler(); + var pd = [ + smh.@com.vaadin.client.communication.MessageHandler::lastProcessingTime, + smh.@com.vaadin.client.communication.MessageHandler::totalProcessingTime + ]; + pd = pd.concat(smh.@com.vaadin.client.communication.MessageHandler::serverTimingInfo); + pd[pd.length] = smh.@com.vaadin.client.communication.MessageHandler::bootstrapTime; + return pd; + }); + + client.getElementByPath = $entry(function(id) { + return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementByPath(Ljava/lang/String;)(id); + }); + client.getElementByPathStartingAt = $entry(function(id, element) { + return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementByPathStartingAt(Ljava/lang/String;Lcom/google/gwt/dom/client/Element;)(id, element); + }); + client.getElementsByPath = $entry(function(id) { + return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementsByPath(Ljava/lang/String;)(id); + }); + client.getElementsByPathStartingAt = $entry(function(id, element) { + return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementsByPathStartingAt(Ljava/lang/String;Lcom/google/gwt/dom/client/Element;)(id, element); + }); + client.getPathForElement = $entry(function(element) { + return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getPathForElement(Lcom/google/gwt/dom/client/Element;)(element); + }); + client.initializing = false; + + $wnd.vaadin.clients[TTAppId] = client; + }-*/; + + /** + * Helper for tt initialization + */ + private JavaScriptObject getVersionInfo() { + return configuration.getVersionInfoJSObject(); + } + + /** + * Publishes a JavaScript API for mash-up applications. + *
    + *
  • vaadin.forceSync() sends pending variable changes, in + * effect synchronizing the server and client state. This is done for all + * applications on host page.
  • + *
  • vaadin.postRequestHooks is a map of functions which gets + * called after each XHR made by vaadin application. Note, that it is + * attaching js functions responsibility to create the variable like this: + * + *
    +     * if(!vaadin.postRequestHooks) {vaadin.postRequestHooks = new Object();}
    +     * postRequestHooks.myHook = function(appId) {
    +     *          if(appId == "MyAppOfInterest") {
    +     *                  // do the staff you need on xhr activity
    +     *          }
    +     * }
    +     * 
    First parameter passed to these functions is the identifier + * of Vaadin application that made the request. + *
+ * + * TODO make this multi-app aware + */ + private native void initializeClientHooks() + /*-{ + var app = this; + var oldSync; + if ($wnd.vaadin.forceSync) { + oldSync = $wnd.vaadin.forceSync; + } + $wnd.vaadin.forceSync = $entry(function() { + if (oldSync) { + oldSync(); + } + app.@com.vaadin.client.ApplicationConnection::sendPendingVariableChanges()(); + }); + var oldForceLayout; + if ($wnd.vaadin.forceLayout) { + oldForceLayout = $wnd.vaadin.forceLayout; + } + $wnd.vaadin.forceLayout = $entry(function() { + if (oldForceLayout) { + oldForceLayout(); + } + app.@com.vaadin.client.ApplicationConnection::forceLayout()(); + }); + }-*/; + + /** + * Requests an analyze of layouts, to find inconsistencies. Exclusively used + * for debugging during development. + * + * @deprecated as of 7.1. Replaced by {@link UIConnector#analyzeLayouts()} + */ + @Deprecated + public void analyzeLayouts() { + getUIConnector().analyzeLayouts(); + } + + /** + * Sends a request to the server to print details to console that will help + * the developer to locate the corresponding server-side connector in the + * source code. + * + * @param serverConnector + * @deprecated as of 7.1. Replaced by + * {@link UIConnector#showServerDebugInfo(ServerConnector)} + */ + @Deprecated + void highlightConnector(ServerConnector serverConnector) { + getUIConnector().showServerDebugInfo(serverConnector); + } + + int cssWaits = 0; + + protected ServerRpcQueue serverRpcQueue; + protected ConnectionStateHandler connectionStateHandler; + protected MessageHandler messageHandler; + protected MessageSender messageSender; + + static final int MAX_CSS_WAITS = 100; + + public void executeWhenCSSLoaded(final Command c) { + if (!isCSSLoaded() && cssWaits < MAX_CSS_WAITS) { + (new Timer() { + @Override + public void run() { + executeWhenCSSLoaded(c); + } + }).schedule(50); + + // Show this message just once + if (cssWaits++ == 0) { + getLogger().warning( + "Assuming CSS loading is not complete, " + + "postponing render phase. " + + "(.v-loading-indicator height == 0)"); + } + } else { + cssLoaded = true; + if (cssWaits >= MAX_CSS_WAITS) { + getLogger().severe("CSS files may have not loaded properly."); + } + + c.execute(); + } + } + + /** + * Checks whether or not the CSS is loaded. By default checks the size of + * the loading indicator element. + * + * @return + */ + protected boolean isCSSLoaded() { + return cssLoaded + || getLoadingIndicator().getElement().getOffsetHeight() != 0; + } + + /** + * Shows the communication error notification. + * + * @param details + * Optional details. + * @param statusCode + * The status code returned for the request + * + */ + public void showCommunicationError(String details, int statusCode) { + getLogger().severe("Communication error: " + details); + showError(details, configuration.getCommunicationError()); + } + + /** + * Shows the authentication error notification. + * + * @param details + * Optional details. + */ + public void showAuthenticationError(String details) { + getLogger().severe("Authentication error: " + details); + showError(details, configuration.getAuthorizationError()); + } + + /** + * Shows the session expiration notification. + * + * @param details + * Optional details. + */ + public void showSessionExpiredError(String details) { + getLogger().severe("Session expired: " + details); + showError(details, configuration.getSessionExpiredError()); + } + + /** + * Shows an error notification. + * + * @param details + * Optional details. + * @param message + * An ErrorMessage describing the error. + */ + protected void showError(String details, ErrorMessage message) { + VNotification.showError(this, message.getCaption(), + message.getMessage(), details, message.getUrl()); + } + + /** + * Checks if the client has running or scheduled commands + */ + private boolean isWorkPending() { + ConnectorMap connectorMap = getConnectorMap(); + JsArrayObject connectors = connectorMap + .getConnectorsAsJsArray(); + int size = connectors.size(); + for (int i = 0; i < size; i++) { + ServerConnector conn = connectors.get(i); + if (isWorkPending(conn)) { + return true; + } + + if (conn instanceof ComponentConnector) { + ComponentConnector compConn = (ComponentConnector) conn; + if (isWorkPending(compConn.getWidget())) { + return true; + } + } + } + return false; + } + + private static boolean isWorkPending(Object object) { + return object instanceof DeferredWorker + && ((DeferredWorker) object).isWorkPending(); + } + + /** + * Checks if deferred commands are (potentially) still being executed as a + * result of an update from the server. Returns true if a deferred command + * might still be executing, false otherwise. This will not work correctly + * if a deferred command is added in another deferred command. + *

+ * Used by the native "client.isActive" function. + *

+ * + * @return true if deferred commands are (potentially) being executed, false + * otherwise + */ + private boolean isExecutingDeferredCommands() { + Scheduler s = Scheduler.get(); + if (s instanceof VSchedulerImpl) { + return ((VSchedulerImpl) s).hasWorkQueued(); + } else { + return false; + } + } + + /** + * Returns the loading indicator used by this ApplicationConnection + * + * @return The loading indicator for this ApplicationConnection + */ + public VLoadingIndicator getLoadingIndicator() { + return loadingIndicator; + } + + /** + * Determines whether or not the loading indicator is showing. + * + * @return true if the loading indicator is visible + * @deprecated As of 7.1. Use {@link #getLoadingIndicator()} and + * {@link VLoadingIndicator#isVisible()}.isVisible() instead. + */ + @Deprecated + public boolean isLoadingIndicatorVisible() { + return getLoadingIndicator().isVisible(); + } + + public void loadStyleDependencies(JsArrayString dependencies) { + // Assuming no reason to interpret in a defined order + ResourceLoadListener resourceLoadListener = new ResourceLoadListener() { + @Override + public void onLoad(ResourceLoadEvent event) { + ApplicationConfiguration.endDependencyLoading(); + } + + @Override + public void onError(ResourceLoadEvent event) { + getLogger() + .severe(event.getResourceUrl() + + " could not be loaded, or the load detection failed because the stylesheet is empty."); + // The show must go on + onLoad(event); + } + }; + ResourceLoader loader = ResourceLoader.get(); + for (int i = 0; i < dependencies.length(); i++) { + String url = translateVaadinUri(dependencies.get(i)); + ApplicationConfiguration.startDependencyLoading(); + loader.loadStylesheet(url, resourceLoadListener); + } + } + + public void loadScriptDependencies(final JsArrayString dependencies) { + if (dependencies.length() == 0) { + return; + } + + // Listener that loads the next when one is completed + ResourceLoadListener resourceLoadListener = new ResourceLoadListener() { + @Override + public void onLoad(ResourceLoadEvent event) { + if (dependencies.length() != 0) { + String url = translateVaadinUri(dependencies.shift()); + ApplicationConfiguration.startDependencyLoading(); + // Load next in chain (hopefully already preloaded) + event.getResourceLoader().loadScript(url, this); + } + // Call start for next before calling end for current + ApplicationConfiguration.endDependencyLoading(); + } + + @Override + public void onError(ResourceLoadEvent event) { + getLogger().severe( + event.getResourceUrl() + " could not be loaded."); + // The show must go on + onLoad(event); + } + }; + + ResourceLoader loader = ResourceLoader.get(); + + // Start chain by loading first + String url = translateVaadinUri(dependencies.shift()); + ApplicationConfiguration.startDependencyLoading(); + loader.loadScript(url, resourceLoadListener); + + if (ResourceLoader.supportsInOrderScriptExecution()) { + for (int i = 0; i < dependencies.length(); i++) { + String preloadUrl = translateVaadinUri(dependencies.get(i)); + loader.loadScript(preloadUrl, null); + } + } else { + // Preload all remaining + for (int i = 0; i < dependencies.length(); i++) { + String preloadUrl = translateVaadinUri(dependencies.get(i)); + loader.preloadResource(preloadUrl, null); + } + } + } + + private void addVariableToQueue(String connectorId, String variableName, + Object value, boolean immediate) { + boolean lastOnly = !immediate; + // note that type is now deduced from value + serverRpcQueue.add(new LegacyChangeVariablesInvocation(connectorId, + variableName, value), lastOnly); + if (immediate) { + serverRpcQueue.flush(); + } + } + + /** + * @deprecated as of 7.6, use {@link ServerRpcQueue#flush()} + */ + @Deprecated + public void sendPendingVariableChanges() { + serverRpcQueue.flush(); + } + + /** + * Sends a new value for the given paintables given variable to the server. + *

+ * The update is actually queued to be sent at a suitable time. If immediate + * is true, the update is sent as soon as possible. If immediate is false, + * the update will be sent along with the next immediate update. + *

+ * + * @param paintableId + * the id of the paintable that owns the variable + * @param variableName + * the name of the variable + * @param newValue + * the new value to be sent + * @param immediate + * true if the update is to be sent as soon as possible + */ + public void updateVariable(String paintableId, String variableName, + ServerConnector newValue, boolean immediate) { + addVariableToQueue(paintableId, variableName, newValue, immediate); + } + + /** + * Sends a new value for the given paintables given variable to the server. + *

+ * The update is actually queued to be sent at a suitable time. If immediate + * is true, the update is sent as soon as possible. If immediate is false, + * the update will be sent along with the next immediate update. + *

+ * + * @param paintableId + * the id of the paintable that owns the variable + * @param variableName + * the name of the variable + * @param newValue + * the new value to be sent + * @param immediate + * true if the update is to be sent as soon as possible + */ + + public void updateVariable(String paintableId, String variableName, + String newValue, boolean immediate) { + addVariableToQueue(paintableId, variableName, newValue, immediate); + } + + /** + * Sends a new value for the given paintables given variable to the server. + *

+ * The update is actually queued to be sent at a suitable time. If immediate + * is true, the update is sent as soon as possible. If immediate is false, + * the update will be sent along with the next immediate update. + *

+ * + * @param paintableId + * the id of the paintable that owns the variable + * @param variableName + * the name of the variable + * @param newValue + * the new value to be sent + * @param immediate + * true if the update is to be sent as soon as possible + */ + + public void updateVariable(String paintableId, String variableName, + int newValue, boolean immediate) { + addVariableToQueue(paintableId, variableName, newValue, immediate); + } + + /** + * Sends a new value for the given paintables given variable to the server. + *

+ * The update is actually queued to be sent at a suitable time. If immediate + * is true, the update is sent as soon as possible. If immediate is false, + * the update will be sent along with the next immediate update. + *

+ * + * @param paintableId + * the id of the paintable that owns the variable + * @param variableName + * the name of the variable + * @param newValue + * the new value to be sent + * @param immediate + * true if the update is to be sent as soon as possible + */ + + public void updateVariable(String paintableId, String variableName, + long newValue, boolean immediate) { + addVariableToQueue(paintableId, variableName, newValue, immediate); + } + + /** + * Sends a new value for the given paintables given variable to the server. + *

+ * The update is actually queued to be sent at a suitable time. If immediate + * is true, the update is sent as soon as possible. If immediate is false, + * the update will be sent along with the next immediate update. + *

+ * + * @param paintableId + * the id of the paintable that owns the variable + * @param variableName + * the name of the variable + * @param newValue + * the new value to be sent + * @param immediate + * true if the update is to be sent as soon as possible + */ + + public void updateVariable(String paintableId, String variableName, + float newValue, boolean immediate) { + addVariableToQueue(paintableId, variableName, newValue, immediate); + } + + /** + * Sends a new value for the given paintables given variable to the server. + *

+ * The update is actually queued to be sent at a suitable time. If immediate + * is true, the update is sent as soon as possible. If immediate is false, + * the update will be sent along with the next immediate update. + *

+ * + * @param paintableId + * the id of the paintable that owns the variable + * @param variableName + * the name of the variable + * @param newValue + * the new value to be sent + * @param immediate + * true if the update is to be sent as soon as possible + */ + + public void updateVariable(String paintableId, String variableName, + double newValue, boolean immediate) { + addVariableToQueue(paintableId, variableName, newValue, immediate); + } + + /** + * Sends a new value for the given paintables given variable to the server. + *

+ * The update is actually queued to be sent at a suitable time. If immediate + * is true, the update is sent as soon as possible. If immediate is false, + * the update will be sent along with the next immediate update. + *

+ * + * @param paintableId + * the id of the paintable that owns the variable + * @param variableName + * the name of the variable + * @param newValue + * the new value to be sent + * @param immediate + * true if the update is to be sent as soon as possible + */ + + public void updateVariable(String paintableId, String variableName, + boolean newValue, boolean immediate) { + addVariableToQueue(paintableId, variableName, newValue, immediate); + } + + /** + * Sends a new value for the given paintables given variable to the server. + *

+ * The update is actually queued to be sent at a suitable time. If immediate + * is true, the update is sent as soon as possible. If immediate is false, + * the update will be sent along with the next immediate update. + *

+ * + * @param paintableId + * the id of the paintable that owns the variable + * @param variableName + * the name of the variable + * @param map + * the new values to be sent + * @param immediate + * true if the update is to be sent as soon as possible + */ + public void updateVariable(String paintableId, String variableName, + Map map, boolean immediate) { + addVariableToQueue(paintableId, variableName, map, immediate); + } + + /** + * Sends a new value for the given paintables given variable to the server. + *

+ * The update is actually queued to be sent at a suitable time. If immediate + * is true, the update is sent as soon as possible. If immediate is false, + * the update will be sent along with the next immediate update. + *

+ * A null array is sent as an empty array. + * + * @param paintableId + * the id of the paintable that owns the variable + * @param variableName + * the name of the variable + * @param values + * the new value to be sent + * @param immediate + * true if the update is to be sent as soon as possible + */ + public void updateVariable(String paintableId, String variableName, + String[] values, boolean immediate) { + addVariableToQueue(paintableId, variableName, values, immediate); + } + + /** + * Sends a new value for the given paintables given variable to the server. + *

+ * The update is actually queued to be sent at a suitable time. If immediate + * is true, the update is sent as soon as possible. If immediate is false, + * the update will be sent along with the next immediate update. + *

+ * A null array is sent as an empty array. + * + * @param paintableId + * the id of the paintable that owns the variable + * @param variableName + * the name of the variable + * @param values + * the new value to be sent + * @param immediate + * true if the update is to be sent as soon as possible + */ + public void updateVariable(String paintableId, String variableName, + Object[] values, boolean immediate) { + addVariableToQueue(paintableId, variableName, values, immediate); + } + + /** + * Does absolutely nothing. Replaced by {@link LayoutManager}. + * + * @param container + * @deprecated As of 7.0, serves no purpose + */ + @Deprecated + public void runDescendentsLayout(HasWidgets container) { + } + + /** + * This will cause re-layouting of all components. Mainly used for + * development. Published to JavaScript. + */ + public void forceLayout() { + Duration duration = new Duration(); + + layoutManager.forceLayout(); + + getLogger().info("forceLayout in " + duration.elapsedMillis() + " ms"); + } + + /** + * Returns false + * + * @param paintable + * @return false, always + * @deprecated As of 7.0, serves no purpose + */ + @Deprecated + private boolean handleComponentRelativeSize(ComponentConnector paintable) { + return false; + } + + /** + * Returns false + * + * @param paintable + * @return false, always + * @deprecated As of 7.0, serves no purpose + */ + @Deprecated + public boolean handleComponentRelativeSize(Widget widget) { + return handleComponentRelativeSize(connectorMap.getConnector(widget)); + + } + + @Deprecated + public ComponentConnector getPaintable(UIDL uidl) { + // Non-component connectors shouldn't be painted from legacy connectors + return (ComponentConnector) getConnector(uidl.getId(), + Integer.parseInt(uidl.getTag())); + } + + /** + * Get either an existing ComponentConnector or create a new + * ComponentConnector with the given type and id. + * + * If a ComponentConnector with the given id already exists, returns it. + * Otherwise creates and registers a new ComponentConnector of the given + * type. + * + * @param connectorId + * Id of the paintable + * @param connectorType + * Type of the connector, as passed from the server side + * + * @return Either an existing ComponentConnector or a new ComponentConnector + * of the given type + */ + public ServerConnector getConnector(String connectorId, int connectorType) { + if (!connectorMap.hasConnector(connectorId)) { + return createAndRegisterConnector(connectorId, connectorType); + } + return connectorMap.getConnector(connectorId); + } + + /** + * Creates a new ServerConnector with the given type and id. + * + * Creates and registers a new ServerConnector of the given type. Should + * never be called with the connector id of an existing connector. + * + * @param connectorId + * Id of the new connector + * @param connectorType + * Type of the connector, as passed from the server side + * + * @return A new ServerConnector of the given type + */ + private ServerConnector createAndRegisterConnector(String connectorId, + int connectorType) { + Profiler.enter("ApplicationConnection.createAndRegisterConnector"); + + // Create and register a new connector with the given type + ServerConnector p = widgetSet.createConnector(connectorType, + configuration); + connectorMap.registerConnector(connectorId, p); + p.doInit(connectorId, this); + + Profiler.leave("ApplicationConnection.createAndRegisterConnector"); + return p; + } + + /** + * Gets a resource that has been pre-loaded via UIDL, such as custom + * layouts. + * + * @param name + * identifier of the resource to get + * @return the resource + */ + public String getResource(String name) { + return resourcesMap.get(name); + } + + /** + * Sets a resource that has been pre-loaded via UIDL, such as custom + * layouts. + * + * @since 7.6 + * @param name + * identifier of the resource to Set + * @param resource + * the resource + */ + public void setResource(String name, String resource) { + resourcesMap.put(name, resource); + } + + /** + * Singleton method to get instance of app's context menu. + * + * @return VContextMenu object + */ + public VContextMenu getContextMenu() { + if (contextMenu == null) { + contextMenu = new VContextMenu(); + contextMenu.setOwner(uIConnector.getWidget()); + DOM.setElementProperty(contextMenu.getElement(), "id", + "PID_VAADIN_CM"); + } + return contextMenu; + } + + /** + * Gets an {@link Icon} instance corresponding to a URI. + * + * @since 7.2 + * @param uri + * @return Icon object + */ + public Icon getIcon(String uri) { + Icon icon; + if (uri == null) { + return null; + } else if (FontIcon.isFontIconUri(uri)) { + icon = GWT.create(FontIcon.class); + } else { + icon = GWT.create(ImageIcon.class); + } + icon.setUri(translateVaadinUri(uri)); + return icon; + } + + /** + * Translates custom protocols in UIDL URI's to be recognizable by browser. + * All uri's from UIDL should be routed via this method before giving them + * to browser due URI's in UIDL may contain custom protocols like theme://. + * + * @param uidlUri + * Vaadin URI from uidl + * @return translated URI ready for browser + */ + public String translateVaadinUri(String uidlUri) { + return uriResolver.resolveVaadinUri(uidlUri); + } + + /** + * Gets the URI for the current theme. Can be used to reference theme + * resources. + * + * @return URI to the current theme + */ + public String getThemeUri() { + return configuration.getVaadinDirUrl() + "themes/" + + getUIConnector().getActiveTheme(); + } + + /* Extended title handling */ + + private final VTooltip tooltip; + + private ConnectorMap connectorMap = GWT.create(ConnectorMap.class); + + /** + * Use to notify that the given component's caption has changed; layouts may + * have to be recalculated. + * + * @param component + * the Paintable whose caption has changed + * @deprecated As of 7.0.2, has not had any effect for a long time + */ + @Deprecated + public void captionSizeUpdated(Widget widget) { + // This doesn't do anything, it's just kept here for compatibility + } + + /** + * Gets the main view + * + * @return the main view + */ + public UIConnector getUIConnector() { + return uIConnector; + } + + /** + * Gets the {@link ApplicationConfiguration} for the current application. + * + * @see ApplicationConfiguration + * @return the configuration for this application + */ + public ApplicationConfiguration getConfiguration() { + return configuration; + } + + /** + * Checks if there is a registered server side listener for the event. The + * list of events which has server side listeners is updated automatically + * before the component is updated so the value is correct if called from + * updatedFromUIDL. + * + * @param connector + * The connector to register event listeners for + * @param eventIdentifier + * The identifier for the event + * @return true if at least one listener has been registered on server side + * for the event identified by eventIdentifier. + * @deprecated As of 7.0. Use + * {@link AbstractConnector#hasEventListener(String)} instead + */ + @Deprecated + public boolean hasEventListeners(ComponentConnector connector, + String eventIdentifier) { + return connector.hasEventListener(eventIdentifier); + } + + /** + * Adds the get parameters to the uri and returns the new uri that contains + * the parameters. + * + * @param uri + * The uri to which the parameters should be added. + * @param extraParams + * One or more parameters in the format "a=b" or "c=d&e=f". An + * empty string is allowed but will not modify the url. + * @return The modified URI with the get parameters in extraParams added. + * @deprecated Use {@link SharedUtil#addGetParameters(String,String)} + * instead + */ + @Deprecated + public static String addGetParameters(String uri, String extraParams) { + return SharedUtil.addGetParameters(uri, extraParams); + } + + ConnectorMap getConnectorMap() { + return connectorMap; + } + + /** + * @deprecated As of 7.0. No longer serves any purpose. + */ + @Deprecated + public void unregisterPaintable(ServerConnector p) { + getLogger().info( + "unregisterPaintable (unnecessarily) called for " + + Util.getConnectorString(p)); + } + + /** + * Get VTooltip instance related to application connection + * + * @return VTooltip instance + */ + public VTooltip getVTooltip() { + return tooltip; + } + + /** + * Method provided for backwards compatibility. Duties previously done by + * this method is now handled by the state change event handler in + * AbstractComponentConnector. The only function this method has is to + * return true if the UIDL is a "cached" update. + * + * @param component + * @param uidl + * @param manageCaption + * @deprecated As of 7.0, no longer serves any purpose + * @return + */ + @Deprecated + public boolean updateComponent(Widget component, UIDL uidl, + boolean manageCaption) { + ComponentConnector connector = getConnectorMap() + .getConnector(component); + if (!AbstractComponentConnector.isRealUpdate(uidl)) { + return true; + } + + if (!manageCaption) { + getLogger() + .warning( + Util.getConnectorString(connector) + + " called updateComponent with manageCaption=false. The parameter was ignored - override delegateCaption() to return false instead. It is however not recommended to use caption this way at all."); + } + return false; + } + + /** + * @deprecated As of 7.0. Use + * {@link AbstractComponentConnector#hasEventListener(String)} + * instead + */ + @Deprecated + public boolean hasEventListeners(Widget widget, String eventIdentifier) { + ComponentConnector connector = getConnectorMap().getConnector(widget); + if (connector == null) { + /* + * No connector will exist in cases where Vaadin widgets have been + * re-used without implementing server<->client communication. + */ + return false; + } + + return hasEventListeners(getConnectorMap().getConnector(widget), + eventIdentifier); + } + + LayoutManager getLayoutManager() { + return layoutManager; + } + + /** + * Schedules a heartbeat request to occur after the configured heartbeat + * interval elapses if the interval is a positive number. Otherwise, does + * nothing. + * + * @deprecated as of 7.2, use {@link Heartbeat#schedule()} instead + */ + @Deprecated + protected void scheduleHeartbeat() { + heartbeat.schedule(); + } + + /** + * Sends a heartbeat request to the server. + *

+ * Heartbeat requests are used to inform the server that the client-side is + * still alive. If the client page is closed or the connection lost, the + * server will eventually close the inactive UI. + * + * @deprecated as of 7.2, use {@link Heartbeat#send()} instead + */ + @Deprecated + protected void sendHeartbeat() { + heartbeat.send(); + } + + public void handleCommunicationError(String details, int statusCode) { + boolean handled = false; + if (communicationErrorDelegate != null) { + handled = communicationErrorDelegate.onError(details, statusCode); + + } + + if (!handled) { + showCommunicationError(details, statusCode); + } + + } + + /** + * Sets the delegate that is called whenever a communication error occurrs. + * + * @param delegate + * the delegate. + */ + public void setCommunicationErrorDelegate(CommunicationErrorHandler delegate) { + communicationErrorDelegate = delegate; + } + + public void setApplicationRunning(boolean applicationRunning) { + if (getApplicationState() == ApplicationState.TERMINATED) { + if (applicationRunning) { + getLogger() + .severe("Tried to restart a terminated application. This is not supported"); + } else { + getLogger() + .warning( + "Tried to stop a terminated application. This should not be done"); + } + return; + } else if (getApplicationState() == ApplicationState.INITIALIZING) { + if (applicationRunning) { + applicationState = ApplicationState.RUNNING; + } else { + getLogger() + .warning( + "Tried to stop the application before it has started. This should not be done"); + } + } else if (getApplicationState() == ApplicationState.RUNNING) { + if (!applicationRunning) { + applicationState = ApplicationState.TERMINATED; + eventBus.fireEvent(new ApplicationStoppedEvent()); + } else { + getLogger() + .warning( + "Tried to start an already running application. This should not be done"); + } + } + } + + /** + * Checks if the application is in the {@link ApplicationState#RUNNING} + * state. + * + * @since 7.6 + * @return true if the application is in the running state, false otherwise + */ + public boolean isApplicationRunning() { + return applicationState == ApplicationState.RUNNING; + } + + public HandlerRegistration addHandler( + GwtEvent.Type type, H handler) { + return eventBus.addHandler(type, handler); + } + + @Override + public void fireEvent(GwtEvent event) { + eventBus.fireEvent(event); + } + + /** + * Calls {@link ComponentConnector#flush()} on the active connector. Does + * nothing if there is no active (focused) connector. + */ + public void flushActiveConnector() { + ComponentConnector activeConnector = getActiveConnector(); + if (activeConnector == null) { + return; + } + activeConnector.flush(); + } + + /** + * Gets the active connector for focused element in browser. + * + * @return Connector for focused element or null. + */ + private ComponentConnector getActiveConnector() { + Element focusedElement = WidgetUtil.getFocusedElement(); + if (focusedElement == null) { + return null; + } + return Util.getConnectorForElement(this, getUIConnector().getWidget(), + focusedElement); + } + + private static Logger getLogger() { + return Logger.getLogger(ApplicationConnection.class.getName()); + } + + /** + * Returns the hearbeat instance. + */ + public Heartbeat getHeartbeat() { + return heartbeat; + } + + /** + * Returns the state of this application. An application state goes from + * "initializing" to "running" to "stopped". There is no way for an + * application to go back to a previous state, i.e. a stopped application + * can never be re-started + * + * @since 7.6 + * @return the current state of this application + */ + public ApplicationState getApplicationState() { + return applicationState; + } + + /** + * Gets the server RPC queue for this application + * + * @since 7.6 + * @return the server RPC queue + */ + public ServerRpcQueue getServerRpcQueue() { + return serverRpcQueue; + } + + /** + * Gets the communication error handler for this application + * + * @since 7.6 + * @return the server RPC queue + */ + public ConnectionStateHandler getConnectionStateHandler() { + return connectionStateHandler; + } + + /** + * Gets the (server to client) message handler for this application + * + * @since 7.6 + * @return the message handler + */ + public MessageHandler getMessageHandler() { + return messageHandler; + } + + /** + * Gets the server rpc manager for this application + * + * @since 7.6 + * @return the server rpc manager + */ + public RpcManager getRpcManager() { + return rpcManager; + } + + /** + * Gets the (client to server) message sender for this application + * + * @since 7.6 + * @return the message sender + */ + public MessageSender getMessageSender() { + return messageSender; + } + + /** + * @since 7.6 + * @return the widget set + */ + public WidgetSet getWidgetSet() { + return widgetSet; + } + + public int getLastSeenServerSyncId() { + return getMessageHandler().getLastSeenServerSyncId(); + } + +} diff --git a/client/src/main/java/com/vaadin/client/BrowserInfo.java b/client/src/main/java/com/vaadin/client/BrowserInfo.java new file mode 100644 index 0000000000..8dcefddcf5 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/BrowserInfo.java @@ -0,0 +1,511 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client; + +import com.google.gwt.user.client.ui.RootPanel; +import com.vaadin.shared.VBrowserDetails; + +/** + * Class used to query information about web browser. + * + * Browser details are detected only once and those are stored in this singleton + * class. + * + */ +public class BrowserInfo { + + private static final String BROWSER_OPERA = "op"; + private static final String BROWSER_IE = "ie"; + private static final String BROWSER_EDGE = "edge"; + private static final String BROWSER_FIREFOX = "ff"; + private static final String BROWSER_SAFARI = "sa"; + + public static final String ENGINE_GECKO = "gecko"; + public static final String ENGINE_WEBKIT = "webkit"; + public static final String ENGINE_PRESTO = "presto"; + public static final String ENGINE_TRIDENT = "trident"; + + private static final String OS_WINDOWS = "win"; + private static final String OS_LINUX = "lin"; + private static final String OS_MACOSX = "mac"; + private static final String OS_ANDROID = "android"; + private static final String OS_IOS = "ios"; + + // Common CSS class for all touch devices + private static final String UI_TOUCH = "touch"; + + private static BrowserInfo instance; + + private static String cssClass = null; + + static { + // Add browser dependent v-* classnames to body to help css hacks + String browserClassnames = get().getCSSClass(); + RootPanel.get().addStyleName(browserClassnames); + } + + /** + * Singleton method to get BrowserInfo object. + * + * @return instance of BrowserInfo object + */ + public static BrowserInfo get() { + if (instance == null) { + instance = new BrowserInfo(); + } + return instance; + } + + private VBrowserDetails browserDetails; + private boolean touchDevice; + + private BrowserInfo() { + browserDetails = new VBrowserDetails(getBrowserString()); + if (browserDetails.isIE()) { + // Use document mode instead user agent to accurately detect how we + // are rendering + int documentMode = getIEDocumentMode(); + if (documentMode != -1) { + browserDetails.setIEMode(documentMode); + } + } + + if (browserDetails.isChrome()) { + touchDevice = detectChromeTouchDevice(); + } else if (browserDetails.isIE()) { + touchDevice = detectIETouchDevice(); + } else { + // PhantomJS pretends to be a touch device which breaks some UI + // tests + touchDevice = !browserDetails.isPhantomJS() && detectTouchDevice(); + } + } + + private native boolean detectTouchDevice() + /*-{ + try { document.createEvent("TouchEvent");return true;} catch(e){return false;}; + }-*/; + + private native boolean detectChromeTouchDevice() + /*-{ + return ("ontouchstart" in window); + }-*/; + + private native boolean detectIETouchDevice() + /*-{ + return !!navigator.msMaxTouchPoints; + }-*/; + + private native int getIEDocumentMode() + /*-{ + var mode = $wnd.document.documentMode; + if (!mode) + return -1; + return mode; + }-*/; + + /** + * Returns a string representing the browser in use, for use in CSS + * classnames. The classnames will be space separated abbreviations, + * optionally with a version appended. + * + * Abbreviations: Firefox: ff Internet Explorer: ie Safari: sa Opera: op + * + * Browsers that CSS-wise behave like each other will get the same + * abbreviation (this usually depends on the rendering engine). + * + * This is quite simple at the moment, more heuristics will be added when + * needed. + * + * Examples: Internet Explorer 6: ".v-ie .v-ie6 .v-ie60", Firefox 3.0.4: + * ".v-ff .v-ff3 .v-ff30", Opera 9.60: ".v-op .v-op9 .v-op960", Opera 10.10: + * ".v-op .v-op10 .v-op1010" + * + * @return + */ + public String getCSSClass() { + String prefix = "v-"; + + if (cssClass == null) { + String browserIdentifier = ""; + String majorVersionClass = ""; + String minorVersionClass = ""; + String browserEngineClass = ""; + + if (browserDetails.isFirefox()) { + browserIdentifier = BROWSER_FIREFOX; + majorVersionClass = browserIdentifier + + getBrowserMajorVersion(); + minorVersionClass = majorVersionClass + + browserDetails.getBrowserMinorVersion(); + browserEngineClass = ENGINE_GECKO; + } else if (browserDetails.isChrome()) { + // TODO update when Chrome is more stable + browserIdentifier = BROWSER_SAFARI; + majorVersionClass = "ch"; + browserEngineClass = ENGINE_WEBKIT; + } else if (browserDetails.isSafari()) { + browserIdentifier = BROWSER_SAFARI; + majorVersionClass = browserIdentifier + + getBrowserMajorVersion(); + minorVersionClass = majorVersionClass + + browserDetails.getBrowserMinorVersion(); + browserEngineClass = ENGINE_WEBKIT; + } else if (browserDetails.isIE()) { + browserIdentifier = BROWSER_IE; + majorVersionClass = browserIdentifier + + getBrowserMajorVersion(); + minorVersionClass = majorVersionClass + + browserDetails.getBrowserMinorVersion(); + browserEngineClass = ENGINE_TRIDENT; + } else if (browserDetails.isEdge()) { + browserIdentifier = BROWSER_EDGE; + majorVersionClass = browserIdentifier + + getBrowserMajorVersion(); + minorVersionClass = majorVersionClass + + browserDetails.getBrowserMinorVersion(); + browserEngineClass = ""; + } else if (browserDetails.isOpera()) { + browserIdentifier = BROWSER_OPERA; + majorVersionClass = browserIdentifier + + getBrowserMajorVersion(); + minorVersionClass = majorVersionClass + + browserDetails.getBrowserMinorVersion(); + browserEngineClass = ENGINE_PRESTO; + } + + cssClass = prefix + browserIdentifier; + if (!"".equals(majorVersionClass)) { + cssClass = cssClass + " " + prefix + majorVersionClass; + } + if (!"".equals(minorVersionClass)) { + cssClass = cssClass + " " + prefix + minorVersionClass; + } + if (!"".equals(browserEngineClass)) { + cssClass = cssClass + " " + prefix + browserEngineClass; + } + String osClass = getOperatingSystemClass(); + if (osClass != null) { + cssClass = cssClass + " " + osClass; + } + if (isTouchDevice()) { + cssClass = cssClass + " " + prefix + UI_TOUCH; + } + } + + return cssClass; + } + + private String getOperatingSystemClass() { + String prefix = "v-"; + + if (browserDetails.isAndroid()) { + return prefix + OS_ANDROID; + } else if (browserDetails.isIOS()) { + String iosClass = prefix + OS_IOS; + return iosClass + " " + iosClass + getOperatingSystemMajorVersion(); + } else if (browserDetails.isWindows()) { + return prefix + OS_WINDOWS; + } else if (browserDetails.isLinux()) { + return prefix + OS_LINUX; + } else if (browserDetails.isMacOSX()) { + return prefix + OS_MACOSX; + } + // Unknown OS + return null; + } + + public boolean isIE() { + return browserDetails.isIE(); + } + + public boolean isEdge() { + return browserDetails.isEdge(); + } + + public boolean isFirefox() { + return browserDetails.isFirefox(); + } + + public boolean isSafari() { + return browserDetails.isSafari(); + } + + public boolean isIE8() { + return isIE() && getBrowserMajorVersion() == 8; + } + + public boolean isIE9() { + return isIE() && getBrowserMajorVersion() == 9; + } + + public boolean isIE10() { + return isIE() && getBrowserMajorVersion() == 10; + } + + public boolean isIE11() { + return isIE() && getBrowserMajorVersion() == 11; + } + + public boolean isChrome() { + return browserDetails.isChrome(); + } + + public boolean isGecko() { + return browserDetails.isGecko(); + } + + public boolean isWebkit() { + return browserDetails.isWebKit(); + } + + /** + * Returns the Gecko version if the browser is Gecko based. The Gecko + * version for Firefox 2 is 1.8 and 1.9 for Firefox 3. + * + * @return The Gecko version or -1 if the browser is not Gecko based + */ + public float getGeckoVersion() { + if (!browserDetails.isGecko()) { + return -1; + } + + return browserDetails.getBrowserEngineVersion(); + } + + /** + * Returns the WebKit version if the browser is WebKit based. The WebKit + * version returned is the major version e.g., 523. + * + * @return The WebKit version or -1 if the browser is not WebKit based + */ + public float getWebkitVersion() { + if (!browserDetails.isWebKit()) { + return -1; + } + + return browserDetails.getBrowserEngineVersion(); + } + + public float getIEVersion() { + if (!browserDetails.isIE()) { + return -1; + } + + return getBrowserMajorVersion(); + } + + public float getOperaVersion() { + if (!browserDetails.isOpera()) { + return -1; + } + + return getBrowserMajorVersion(); + } + + public boolean isOpera() { + return browserDetails.isOpera(); + } + + public boolean isOpera10() { + return browserDetails.isOpera() && getBrowserMajorVersion() == 10; + } + + public boolean isOpera11() { + return browserDetails.isOpera() && getBrowserMajorVersion() == 11; + } + + public native static String getBrowserString() + /*-{ + return $wnd.navigator.userAgent; + }-*/; + + public native int getScreenWidth() + /*-{ + return $wnd.screen.width; + }-*/; + + public native int getScreenHeight() + /*-{ + return $wnd.screen.height; + }-*/; + + /** + * @return true if the browser runs on a touch based device. + */ + public boolean isTouchDevice() { + return touchDevice; + } + + /** + * Indicates whether the browser might require juggling to properly update + * sizes inside elements with overflow: auto. + * + * @return true if the browser requires the workaround, + * otherwise false + */ + public boolean requiresOverflowAutoFix() { + return (getWebkitVersion() > 0 || getOperaVersion() >= 11 + || getIEVersion() >= 10 || isFirefox()) + && WidgetUtil.getNativeScrollbarSize() > 0; + } + + /** + * Indicates whether the browser might require juggling to properly update + * sizes inside elements with overflow: auto when adjusting absolutely + * positioned elements. + *

+ * See https://bugs.webkit.org/show_bug.cgi?id=123958 and + * http://code.google.com/p/chromium/issues/detail?id=316549 + * + * @since 7.1.8 + * @return true if the browser requires the workaround, + * otherwise false + */ + public boolean requiresPositionAbsoluteOverflowAutoFix() { + return (getWebkitVersion() > 0) + && WidgetUtil.getNativeScrollbarSize() > 0; + } + + /** + * Checks if the browser is run on iOS + * + * @return true if the browser is run on iOS, false otherwise + */ + public boolean isIOS() { + return browserDetails.isIOS(); + } + + /** + * Checks if the browser is run on iOS 6. + * + * @since 7.1.1 + * @return true if the browser is run on iOS 6, false otherwise + */ + public boolean isIOS6() { + return isIOS() && getOperatingSystemMajorVersion() == 6; + } + + /** + * Checks if the browser is run on Android + * + * @return true if the browser is run on Android, false otherwise + */ + public boolean isAndroid() { + return browserDetails.isAndroid(); + } + + /** + * Checks if the browser is capable of handling scrolling natively or if a + * touch scroll helper is needed for scrolling. + * + * @return true if browser needs a touch scroll helper, false if the browser + * can handle scrolling natively + */ + public boolean requiresTouchScrollDelegate() { + if (!isTouchDevice()) { + return false; + } + // TODO Should test other Android browsers, especially Chrome + if (isAndroid() && isWebkit() && getWebkitVersion() >= 534) { + return false; + } + // iOS 6+ Safari supports native scrolling; iOS 5 suffers from #8792 + // TODO Should test other iOS browsers + if (isIOS() && isWebkit() && getOperatingSystemMajorVersion() >= 6) { + return false; + } + + if (isIE()) { + return false; + } + + return true; + } + + /** + * Tests if this is an Android devices with a broken scrollTop + * implementation + * + * @return true if scrollTop cannot be trusted on this device, false + * otherwise + */ + public boolean isAndroidWithBrokenScrollTop() { + return isAndroid() + && (getOperatingSystemMajorVersion() == 3 || getOperatingSystemMajorVersion() == 4); + } + + public boolean isAndroid23() { + return isAndroid() && getOperatingSystemMajorVersion() == 2 + && getOperatingSystemMinorVersion() == 3; + } + + private int getOperatingSystemMajorVersion() { + return browserDetails.getOperatingSystemMajorVersion(); + } + + private int getOperatingSystemMinorVersion() { + return browserDetails.getOperatingSystemMinorVersion(); + } + + /** + * Returns the browser major version e.g., 3 for Firefox 3.5, 4 for Chrome + * 4, 8 for Internet Explorer 8. + *

+ * Note that Internet Explorer 8 and newer will return the document mode so + * IE8 rendering as IE7 will return 7. + *

+ * + * @return The major version of the browser. + */ + public int getBrowserMajorVersion() { + return browserDetails.getBrowserMajorVersion(); + } + + /** + * Returns the browser minor version e.g., 5 for Firefox 3.5. + * + * @see #getBrowserMajorVersion() + * + * @return The minor version of the browser, or -1 if not known/parsed. + */ + public int getBrowserMinorVersion() { + return browserDetails.getBrowserMinorVersion(); + } + + /** + * Checks if the browser version is newer or equal to the given major+minor + * version. + * + * @param majorVersion + * The major version to check for + * @param minorVersion + * The minor version to check for + * @return true if the browser version is newer or equal to the given + * version + */ + public boolean isBrowserVersionNewerOrEqual(int majorVersion, + int minorVersion) { + if (getBrowserMajorVersion() == majorVersion) { + // Same major + return (getBrowserMinorVersion() >= minorVersion); + } + + // Older or newer major + return (getBrowserMajorVersion() > majorVersion); + } +} diff --git a/client/src/main/java/com/vaadin/client/CSSRule.java b/client/src/main/java/com/vaadin/client/CSSRule.java new file mode 100644 index 0000000000..a1ddce6d0a --- /dev/null +++ b/client/src/main/java/com/vaadin/client/CSSRule.java @@ -0,0 +1,132 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import com.google.gwt.core.client.JavaScriptObject; + +/** + * Utility class for fetching CSS properties from DOM StyleSheets JS object. + */ +public class CSSRule { + + private final String selector; + private JavaScriptObject rules = null; + + /** + * + * @param selector + * the CSS selector to search for in the stylesheets + * @param deep + * should the search follow any @import statements? + */ + public CSSRule(final String selector, final boolean deep) { + this.selector = selector; + fetchRule(selector, deep); + } + + // TODO how to find the right LINK-element? We should probably give the + // stylesheet a name. + private native void fetchRule(final String selector, final boolean deep) + /*-{ + var sheets = $doc.styleSheets; + for(var i = 0; i < sheets.length; i++) { + var sheet = sheets[i]; + if(sheet.href && sheet.href.indexOf("VAADIN/themes")>-1) { + // $entry not needed as function is not exported + this.@com.vaadin.client.CSSRule::rules = @com.vaadin.client.CSSRule::searchForRule(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;Z)(sheet, selector, deep); + return; + } + } + this.@com.vaadin.client.CSSRule::rules = []; + }-*/; + + /* + * Loops through all current style rules and collects all matching to + * 'rules' array. The array is reverse ordered (last one found is first). + */ + private static native JavaScriptObject searchForRule( + final JavaScriptObject sheet, final String selector, + final boolean deep) + /*-{ + if(!$doc.styleSheets) + return null; + + selector = selector.toLowerCase(); + + var allMatches = []; + + // IE handles imported sheet differently + if(deep && sheet.imports && sheet.imports.length > 0) { + for(var i=0; i < sheet.imports.length; i++) { + // $entry not needed as function is not exported + var imports = @com.vaadin.client.CSSRule::searchForRule(Lcom/google/gwt/core/client/JavaScriptObject;Ljava/lang/String;Z)(sheet.imports[i], selector, deep); + allMatches.concat(imports); + } + } + + var theRules = new Array(); + if (sheet.cssRules) + theRules = sheet.cssRules + else if (sheet.rules) + theRules = sheet.rules + + var j = theRules.length; + for(var i=0; itrue if the width of this paintable is currently + * undefined. If the width is undefined, the actual width of the paintable + * is defined by its contents. + * + * @return true if the width is undefined, else + * false + */ + public boolean isUndefinedWidth(); + + /** + * Returns true if the height of this paintable is currently + * undefined. If the height is undefined, the actual height of the paintable + * is defined by its contents. + * + * @return true if the height is undefined, else + * false + */ + public boolean isUndefinedHeight(); + + /** + * Returns true if the width of this paintable is currently + * relative. If the width is relative, the actual width of the paintable is + * a percentage of the size allocated to it by its parent. + * + * @return true if the width is undefined, else + * false + */ + public boolean isRelativeWidth(); + + /** + * Returns true if the height of this paintable is currently + * relative. If the height is relative, the actual height of the paintable + * is a percentage of the size allocated to it by its parent. + * + * @return true if the width is undefined, else + * false + */ + public boolean isRelativeHeight(); + + /** + * Checks if the connector is read only. + * + * @deprecated This belongs in AbstractFieldConnector, see #8514 + * @return true + */ + @Deprecated + public boolean isReadOnly(); + + /** + * Return true if parent handles caption, false if the paintable handles the + * caption itself. + * + *

+ * This should always return true and all components should let the parent + * handle the caption and use other attributes for internal texts in the + * component + *

+ * + * @return true if caption handling is delegated to the parent, false if + * parent should not be allowed to render caption + */ + public boolean delegateCaptionHandling(); + + /** + * Sets the enabled state of the widget associated to this connector. + * + * @param widgetEnabled + * true if the widget should be enabled, false otherwise + */ + public void setWidgetEnabled(boolean widgetEnabled); + + /** + * Gets the tooltip info for the given element. + *

+ * When overriding this method, {@link #hasTooltip()} should also be + * overridden to return true in all situations where this + * method might return a non-empty result. + *

+ * + * @param element + * The element to lookup a tooltip for + * @return The tooltip for the element or null if no tooltip is defined for + * this element. + */ + public TooltipInfo getTooltipInfo(Element element); + + /** + * Check whether there might be a tooltip for this component. The framework + * will only add event listeners for automatically handling tooltips (using + * {@link #getTooltipInfo(Element)}) if this method returns true. + *

+ * This is only done to optimize performance, so in cases where the status + * is not known, it's safer to return true so that there will + * be a tooltip handler even though it might not be needed in all cases. + * + * @return true if some part of the component might have a + * tooltip, otherwise false + */ + public boolean hasTooltip(); + + /** + * Called for the active (focused) connector when a situation occurs that + * the focused connector might have buffered changes which need to be + * processed before other activity takes place. + *

+ * This is currently called when the user changes the fragment using the + * back/forward button in the browser and allows the focused field to submit + * its value to the server before the fragment change event takes place. + *

+ */ + public void flush(); + +} diff --git a/client/src/main/java/com/vaadin/client/ComponentDetail.java b/client/src/main/java/com/vaadin/client/ComponentDetail.java new file mode 100644 index 0000000000..9e5e2a82a8 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ComponentDetail.java @@ -0,0 +1,78 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import java.util.HashMap; + +class ComponentDetail { + + private TooltipInfo tooltipInfo = new TooltipInfo(); + + private ServerConnector connector; + + public ComponentDetail() { + + } + + /** + * Returns a TooltipInfo assosiated with Component. If element is given, + * returns an additional TooltipInfo. + * + * @param key + * @return the tooltipInfo + */ + public TooltipInfo getTooltipInfo(Object key) { + if (key == null) { + return tooltipInfo; + } else { + if (additionalTooltips != null) { + return additionalTooltips.get(key); + } else { + return null; + } + } + } + + /** + * @param tooltipInfo + * the tooltipInfo to set + */ + public void setTooltipInfo(TooltipInfo tooltipInfo) { + this.tooltipInfo = tooltipInfo; + } + + private HashMap additionalTooltips; + + public void putAdditionalTooltip(Object key, TooltipInfo tooltip) { + if (tooltip == null && additionalTooltips != null) { + additionalTooltips.remove(key); + } else { + if (additionalTooltips == null) { + additionalTooltips = new HashMap(); + } + additionalTooltips.put(key, tooltip); + } + } + + public ServerConnector getConnector() { + return connector; + } + + public void setConnector(ServerConnector connector) { + this.connector = connector; + } + +} diff --git a/client/src/main/java/com/vaadin/client/ComponentDetailMap.java b/client/src/main/java/com/vaadin/client/ComponentDetailMap.java new file mode 100644 index 0000000000..c99ebd2738 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ComponentDetailMap.java @@ -0,0 +1,99 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import java.util.ArrayList; +import java.util.Collection; + +import com.google.gwt.core.client.JavaScriptObject; + +final class ComponentDetailMap extends JavaScriptObject { + + protected ComponentDetailMap() { + } + + static ComponentDetailMap create() { + return (ComponentDetailMap) JavaScriptObject.createObject(); + } + + boolean isEmpty() { + return size() == 0; + } + + final native boolean containsKey(String key) + /*-{ + return this.hasOwnProperty(key); + }-*/; + + final native ComponentDetail get(String key) + /*-{ + return this[key]; + }-*/; + + final native void put(String id, ComponentDetail value) + /*-{ + this[id] = value; + }-*/; + + final native void remove(String id) + /*-{ + delete this[id]; + }-*/; + + final native int size() + /*-{ + var count = 0; + for(var key in this) { + count++; + } + return count; + }-*/; + + final native void clear() + /*-{ + for(var key in this) { + if(this.hasOwnProperty(key)) { + delete this[key]; + } + } + }-*/; + + private final native void fillWithValues(Collection list) + /*-{ + for(var key in this) { + // $entry not needed as function is not exported + list.@java.util.Collection::add(Ljava/lang/Object;)(this[key]); + } + }-*/; + + final Collection values() { + ArrayList list = new ArrayList(); + fillWithValues(list); + return list; + } + + public native JsArrayObject valuesAsJsArray() + /*-{ + var result = []; + for(var key in this) { + if (this.hasOwnProperty(key)) { + result.push(this[key]); + } + } + return result; + }-*/; + +} diff --git a/client/src/main/java/com/vaadin/client/ComputedStyle.java b/client/src/main/java/com/vaadin/client/ComputedStyle.java new file mode 100644 index 0000000000..65a6e69019 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ComputedStyle.java @@ -0,0 +1,362 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.dom.client.Element; + +public class ComputedStyle { + + protected final JavaScriptObject computedStyle; + private final Element elem; + + /** + * Gets this element's computed style object which can be used to gather + * information about the current state of the rendered node. + *

+ * Note that this method is expensive. Wherever possible, reuse the returned + * object. + * + * @param elem + * the element + * @return the computed style + */ + public ComputedStyle(Element elem) { + computedStyle = getComputedStyle(elem); + this.elem = elem; + } + + private static native JavaScriptObject getComputedStyle(Element elem) + /*-{ + if(elem.nodeType != 1) { + return {}; + } + + if($wnd.document.defaultView && $wnd.document.defaultView.getComputedStyle) { + return $wnd.document.defaultView.getComputedStyle(elem, null); + } + + if(elem.currentStyle) { + return elem.currentStyle; + } + }-*/; + + /** + * + * @param name + * name of the CSS property in camelCase + * @return the value of the property, normalized for across browsers (each + * browser returns pixel values whenever possible). + */ + public final native String getProperty(String name) + /*-{ + var cs = this.@com.vaadin.client.ComputedStyle::computedStyle; + var elem = this.@com.vaadin.client.ComputedStyle::elem; + + // Border values need to be checked separately. The width might have a + // meaningful value even if the border style is "none". In that case the + // value should be 0. + if(name.indexOf("border") > -1 && name.indexOf("Width") > -1) { + var borderStyleProp = name.substring(0,name.length-5) + "Style"; + if(cs.getPropertyValue) + var borderStyle = cs.getPropertyValue(borderStyleProp); + else // IE + var borderStyle = cs[borderStyleProp]; + if(borderStyle == "none") + return "0px"; + } + + if(cs.getPropertyValue) { + + // Convert name to dashed format + name = name.replace(/([A-Z])/g, "-$1").toLowerCase(); + var ret = cs.getPropertyValue(name); + + } else { + + var ret = cs[name]; + var style = elem.style; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { + // Remember the original values + var left = style.left, rsLeft = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = cs.left; + style.left = ret || 0; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + elem.runtimeStyle.left = rsLeft; + } + + } + + // Normalize margin values. This is not totally valid, but in most cases + // it is what the user wants to know. + if(name.indexOf("margin") > -1 && ret == "auto") { + return "0px"; + } + + // Some browsers return undefined width and height values as "auto", so + // we need to retrieve those ourselves. + if (name == "width" && ret == "auto") { + ret = elem.clientWidth + "px"; + } else if (name == "height" && ret == "auto") { + ret = elem.clientHeight + "px"; + } + + return ret; + + }-*/; + + /** + * Retrieves the given computed property as an integer + * + * Returns 0 if the property cannot be converted to an integer + * + * @param name + * the property to retrieve + * @return the integer value of the property or 0 + */ + public final int getIntProperty(String name) { + Profiler.enter("ComputedStyle.getIntProperty"); + String value = getProperty(name); + int result = parseIntNative(value); + Profiler.leave("ComputedStyle.getIntProperty"); + return result; + } + + /** + * Retrieves the given computed property as a double + * + * Returns NaN if the property cannot be converted to a double + * + * @since 7.5.1 + * @param name + * the property to retrieve + * @return the double value of the property + */ + public final double getDoubleProperty(String name) { + Profiler.enter("ComputedStyle.getDoubleProperty"); + String value = getProperty(name); + double result = parseDoubleNative(value); + Profiler.leave("ComputedStyle.getDoubleProperty"); + return result; + } + + /** + * Get current margin values from the DOM. The array order is the default + * CSS order: top, right, bottom, left. + */ + public final int[] getMargin() { + int[] margin = { 0, 0, 0, 0 }; + margin[0] = getIntProperty("marginTop"); + margin[1] = getIntProperty("marginRight"); + margin[2] = getIntProperty("marginBottom"); + margin[3] = getIntProperty("marginLeft"); + return margin; + } + + /** + * Get current padding values from the DOM. The array order is the default + * CSS order: top, right, bottom, left. + */ + public final int[] getPadding() { + int[] padding = { 0, 0, 0, 0 }; + padding[0] = getIntProperty("paddingTop"); + padding[1] = getIntProperty("paddingRight"); + padding[2] = getIntProperty("paddingBottom"); + padding[3] = getIntProperty("paddingLeft"); + return padding; + } + + /** + * Get current border values from the DOM. The array order is the default + * CSS order: top, right, bottom, left. + */ + public final int[] getBorder() { + int[] border = { 0, 0, 0, 0 }; + border[0] = getIntProperty("borderTopWidth"); + border[1] = getIntProperty("borderRightWidth"); + border[2] = getIntProperty("borderBottomWidth"); + border[3] = getIntProperty("borderLeftWidth"); + return border; + } + + /** + * Returns the current width from the DOM. + * + * @since 7.5.1 + * @return the computed width + */ + public double getWidth() { + return getDoubleProperty("width"); + } + + /** + * Returns the current height from the DOM. + * + * @since 7.5.1 + * @return the computed height + */ + public double getHeight() { + return getDoubleProperty("height"); + } + + /** + * Takes a String value e.g. "12px" and parses that to Integer 12. + * + * @param String + * a value starting with a number + * @return Integer the value from the string before any non-numeric + * characters. If the value cannot be parsed to a number, returns + * null. + * + * @deprecated Since 7.1.4, the method {@link #parseIntNative(String)} is + * used internally and this method does not belong in the public + * API of {@link ComputedStyle}. {@link #parseInt(String)} might + * be removed or moved to a utility class in future versions. + */ + @Deprecated + public static native Integer parseInt(final String value) + /*-{ + var number = parseInt(value, 10); + if (isNaN(number)) + return null; + else + // $entry not needed as function is not exported + return @java.lang.Integer::valueOf(I)(number); + }-*/; + + /** + * Takes a String value e.g. "12px" and parses that to int 12. + * + *

+ * This method returns 0 for NaN. + * + * @param String + * a value starting with a number + * @return int the value from the string before any non-numeric characters. + * If the value cannot be parsed to a number, returns 0. + */ + private static native int parseIntNative(final String value) + /*-{ + var number = parseInt(value, 10); + if (isNaN(number)) + return 0; + else + return number; + }-*/; + + /** + * Takes a String value e.g. "12.3px" and parses that to a double, 12.3. + * + * @param String + * a value starting with a number + * @return the value from the string before any non-numeric characters or + * NaN if the value cannot be parsed as a number + */ + private static native double parseDoubleNative(final String value) + /*-{ + return parseFloat(value); + }-*/; + + /** + * Returns the sum of the top and bottom border width + * + * @since 7.5.3 + * @return the sum of the top and bottom border + */ + public double getBorderHeight() { + double borderHeight = getDoubleProperty("borderTopWidth"); + borderHeight += getDoubleProperty("borderBottomWidth"); + + return borderHeight; + } + + /** + * Returns the sum of the left and right border width + * + * @since 7.5.3 + * @return the sum of the left and right border + */ + public double getBorderWidth() { + double borderWidth = getDoubleProperty("borderLeftWidth"); + borderWidth += getDoubleProperty("borderRightWidth"); + + return borderWidth; + } + + /** + * Returns the sum of the top and bottom padding + * + * @since 7.5.3 + * @return the sum of the top and bottom padding + */ + public double getPaddingHeight() { + double paddingHeight = getDoubleProperty("paddingTop"); + paddingHeight += getDoubleProperty("paddingBottom"); + + return paddingHeight; + } + + /** + * Returns the sum of the top and bottom padding + * + * @since 7.5.3 + * @return the sum of the left and right padding + */ + public double getPaddingWidth() { + double paddingWidth = getDoubleProperty("paddingLeft"); + paddingWidth += getDoubleProperty("paddingRight"); + + return paddingWidth; + } + + /** + * Returns the sum of the top and bottom margin + * + * @since 7.5.6 + * @return the sum of the top and bottom margin + */ + public double getMarginHeight() { + double marginHeight = getDoubleProperty("marginTop"); + marginHeight += getDoubleProperty("marginBottom"); + + return marginHeight; + } + + /** + * Returns the sum of the top and bottom margin + * + * @since 7.5.6 + * @return the sum of the left and right margin + */ + public double getMarginWidth() { + double marginWidth = getDoubleProperty("marginLeft"); + marginWidth += getDoubleProperty("marginRight"); + + return marginWidth; + } + +} diff --git a/client/src/main/java/com/vaadin/client/ConnectorHierarchyChangeEvent.java b/client/src/main/java/com/vaadin/client/ConnectorHierarchyChangeEvent.java new file mode 100644 index 0000000000..267b3d3bfd --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ConnectorHierarchyChangeEvent.java @@ -0,0 +1,103 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import java.io.Serializable; +import java.util.List; + +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler; +import com.vaadin.client.communication.AbstractServerConnectorEvent; + +/** + * Event for containing data related to a change in the {@link ServerConnector} + * hierarchy. A {@link ConnectorHierarchyChangedEvent} is fired when an update + * from the server has been fully processed and all hierarchy updates have been + * completed. + * + * @author Vaadin Ltd + * @since 7.0.0 + * + */ +public class ConnectorHierarchyChangeEvent extends + AbstractServerConnectorEvent { + /** + * Type of this event, used by the event bus. + */ + public static final Type TYPE = new Type(); + + List oldChildren; + + public ConnectorHierarchyChangeEvent() { + } + + /** + * Returns a collection of the old children for the connector. This was the + * state before the update was received from the server. + * + * @return A collection of old child connectors. Never returns null. + */ + public List getOldChildren() { + return oldChildren; + } + + /** + * Sets the collection of the old children for the connector. + * + * @param oldChildren + * The old child connectors. Must not be null. + */ + public void setOldChildren(List oldChildren) { + this.oldChildren = oldChildren; + } + + /** + * Returns the {@link HasComponentsConnector} for which this event occurred. + * + * @return The {@link HasComponentsConnector} whose child collection has + * changed. Never returns null. + */ + public HasComponentsConnector getParent() { + return (HasComponentsConnector) getConnector(); + } + + @Override + public void setConnector(ServerConnector connector) { + assert connector instanceof HasComponentsConnector : "A ConnectorHierarchyChangeEvent " + + "can only occur for connectors implementing HasComponentsConnector. " + + connector.getClass().getName() + " does not"; + + super.setConnector(connector); + } + + public interface ConnectorHierarchyChangeHandler extends Serializable, + EventHandler { + public void onConnectorHierarchyChange( + ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent); + } + + @Override + public void dispatch(ConnectorHierarchyChangeHandler handler) { + handler.onConnectorHierarchyChange(this); + } + + @Override + public GwtEvent.Type getAssociatedType() { + return TYPE; + } + +} diff --git a/client/src/main/java/com/vaadin/client/ConnectorMap.java b/client/src/main/java/com/vaadin/client/ConnectorMap.java new file mode 100644 index 0000000000..3200dd6ab4 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ConnectorMap.java @@ -0,0 +1,306 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.logging.Logger; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.ui.Widget; + +public class ConnectorMap { + + public static ConnectorMap get(ApplicationConnection applicationConnection) { + return applicationConnection.getConnectorMap(); + } + + @Deprecated + private final ComponentDetailMap idToComponentDetail = ComponentDetailMap + .create(); + + /** + * Returns a {@link ServerConnector} by its id + * + * @param id + * The connector id + * @return A connector or null if a connector with the given id has not been + * registered + */ + public ServerConnector getConnector(String connectorId) { + ComponentDetail componentDetail = idToComponentDetail.get(connectorId); + if (componentDetail == null) { + return null; + } else { + return componentDetail.getConnector(); + } + } + + /** + * Returns a {@link ComponentConnector} element by its root element. + * + * @param element + * Root element of the {@link ComponentConnector} + * @return A connector or null if a connector with the given id has not been + * registered + */ + public ComponentConnector getConnector(Element element) { + ServerConnector connector = getConnector(getConnectorId(element)); + if (!(connector instanceof ComponentConnector)) { + // This can happen at least if element is not part of this + // application but is part of another application and the connector + // id happens to map to e.g. an extension in this application + return null; + } + + // Ensure this connector is really connected to the element. We cannot + // be sure of this otherwise as the id comes from the DOM and could be + // part of another application. + ComponentConnector cc = (ComponentConnector) connector; + if (cc.getWidget() == null || cc.getWidget().getElement() != element) { + return null; + } + + return cc; + } + + /** + * FIXME: What does this even do and why? + * + * @param pid + * @return + */ + public boolean isDragAndDropPaintable(String pid) { + return (pid.startsWith("DD")); + } + + /** + * Checks if a connector with the given id has been registered. + * + * @param connectorId + * The id to check for + * @return true if a connector has been registered with the given id, false + * otherwise + */ + public boolean hasConnector(String connectorId) { + return idToComponentDetail.containsKey(connectorId); + } + + /** + * Removes all registered connectors + */ + public void clear() { + idToComponentDetail.clear(); + } + + /** + * Retrieves the connector whose widget matches the parameter. + * + * @param widget + * The widget + * @return A connector with {@literal widget} as its root widget or null if + * no connector was found + */ + public ComponentConnector getConnector(Widget widget) { + return widget == null ? null : getConnector(widget.getElement()); + } + + public void registerConnector(String id, ServerConnector connector) { + Profiler.enter("ConnectorMap.registerConnector"); + ComponentDetail componentDetail = GWT.create(ComponentDetail.class); + idToComponentDetail.put(id, componentDetail); + componentDetail.setConnector(connector); + if (connector instanceof ComponentConnector) { + ComponentConnector pw = (ComponentConnector) connector; + Widget widget = pw.getWidget(); + Profiler.enter("ConnectorMap.setConnectorId"); + setConnectorId(widget.getElement(), id); + Profiler.leave("ConnectorMap.setConnectorId"); + } + Profiler.leave("ConnectorMap.registerConnector"); + } + + private static native void setConnectorId(Element el, String id) + /*-{ + el.tkPid = id; + }-*/; + + /** + * Gets the connector id using a DOM element - the element should be the + * root element for a connector, otherwise no id will be found. Use + * {@link #getConnectorId(ServerConnector)} instead whenever possible. + * + * @see #getConnectorId(ServerConnector) + * @param el + * element of the connector whose id is desired + * @return the id of the element's connector, if it's a connector + */ + native static final String getConnectorId(Element el) + /*-{ + return el.tkPid; + }-*/; + + /** + * Gets the main element for the connector with the given id. The reverse of + * {@link #getConnectorId(Element)}. + * + * @param connectorId + * the id of the widget whose element is desired + * @return the element for the connector corresponding to the id + */ + public Element getElement(String connectorId) { + ServerConnector p = getConnector(connectorId); + if (p instanceof ComponentConnector) { + return ((ComponentConnector) p).getWidget().getElement(); + } + + return null; + } + + /** + * Unregisters the given connector; always use after removing a connector. + * This method does not remove the connector from the DOM, but marks the + * connector so that ApplicationConnection may clean up its references to + * it. Removing the widget from DOM is component containers responsibility. + * + * @param connector + * the connector to remove + */ + public void unregisterConnector(ServerConnector connector) { + if (connector == null) { + getLogger().severe("Trying to unregister null connector"); + return; + } + + String connectorId = connector.getConnectorId(); + + idToComponentDetail.remove(connectorId); + connector.onUnregister(); + + for (ServerConnector child : connector.getChildren()) { + if (child.getParent() == connector) { + /* + * Only unregister children that are actually connected to this + * parent. For instance when moving connectors from one layout + * to another and removing the first layout it will still + * contain references to its old children, which are now + * attached to another connector. + */ + unregisterConnector(child); + } + } + } + + /** + * Gets all registered {@link ComponentConnector} instances + * + * @return An array of all registered {@link ComponentConnector} instances + * + * @deprecated As of 7.0.1, use {@link #getComponentConnectorsAsJsArray()} + * for better performance. + */ + @Deprecated + public ComponentConnector[] getComponentConnectors() { + ArrayList result = new ArrayList(); + + JsArrayObject connectors = getConnectorsAsJsArray(); + int size = connectors.size(); + + for (int i = 0; i < size; i++) { + ServerConnector connector = connectors.get(i); + if (connector instanceof ComponentConnector) { + result.add((ComponentConnector) connector); + } + } + + return result.toArray(new ComponentConnector[result.size()]); + } + + public JsArrayObject getComponentConnectorsAsJsArray() { + JsArrayObject result = JavaScriptObject + .createArray().cast(); + + JsArrayObject connectors = getConnectorsAsJsArray(); + int size = connectors.size(); + for (int i = 0; i < size; i++) { + ServerConnector connector = connectors.get(i); + if (connector instanceof ComponentConnector) { + result.add((ComponentConnector) connector); + } + } + + return result; + } + + @Deprecated + private ComponentDetail getComponentDetail( + ComponentConnector componentConnector) { + return idToComponentDetail.get(componentConnector.getConnectorId()); + } + + public int size() { + return idToComponentDetail.size(); + } + + /** + * @return + * + * @deprecated As of 7.0.1, use {@link #getConnectorsAsJsArray()} for + * improved performance. + */ + @Deprecated + public Collection getConnectors() { + Collection values = idToComponentDetail.values(); + ArrayList arrayList = new ArrayList( + values.size()); + for (ComponentDetail componentDetail : values) { + arrayList.add(componentDetail.getConnector()); + } + return arrayList; + } + + public JsArrayObject getConnectorsAsJsArray() { + JsArrayObject componentDetails = idToComponentDetail + .valuesAsJsArray(); + JsArrayObject connectors = JavaScriptObject + .createArray().cast(); + + int size = componentDetails.size(); + for (int i = 0; i < size; i++) { + connectors.add(componentDetails.get(i).getConnector()); + } + + return connectors; + } + + /** + * Tests if the widget is the root widget of a {@link ComponentConnector}. + * + * @param widget + * The widget to test + * @return true if the widget is the root widget of a + * {@link ComponentConnector}, false otherwise + */ + public boolean isConnector(Widget w) { + return getConnectorId(w.getElement()) != null; + } + + private static Logger getLogger() { + return Logger.getLogger(ConnectorMap.class.getName()); + } +} diff --git a/client/src/main/java/com/vaadin/client/ContainerResizedListener.java b/client/src/main/java/com/vaadin/client/ContainerResizedListener.java new file mode 100644 index 0000000000..702542f0ac --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ContainerResizedListener.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client; + +/** + * ContainerResizedListener interface is useful for Widgets that support + * relative sizes and who need some additional sizing logic. + * + * @deprecated As of 7.0, serves no purpose. Use {@link LayoutManager} and its + * methods instead. + */ +@Deprecated +public interface ContainerResizedListener { + /** + * This function is run when container box has been resized. Object + * implementing ContainerResizedListener is responsible to call the same + * function on its ancestors that implement NeedsLayout in case their + * container has resized. runAnchestorsLayout(HasWidgets parent) function + * from Util class may be a good helper for this. + * + * @deprecated As of 7.0, this method is never called by the framework. + * + */ + @Deprecated + public void iLayout(); +} diff --git a/client/src/main/java/com/vaadin/client/DateTimeService.java b/client/src/main/java/com/vaadin/client/DateTimeService.java new file mode 100644 index 0000000000..04006d85fb --- /dev/null +++ b/client/src/main/java/com/vaadin/client/DateTimeService.java @@ -0,0 +1,504 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client; + +import java.util.Date; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.i18n.client.LocaleInfo; +import com.vaadin.shared.ui.datefield.Resolution; + +/** + * This class provides date/time parsing services to all components on the + * client side. + * + * @author Vaadin Ltd. + * + */ +@SuppressWarnings("deprecation") +public class DateTimeService { + + private String currentLocale; + + private static int[] maxDaysInMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30, + 31, 30, 31 }; + + /** + * Creates a new date time service with the application default locale. + */ + public DateTimeService() { + currentLocale = LocaleService.getDefaultLocale(); + } + + /** + * Creates a new date time service with a given locale. + * + * @param locale + * e.g. fi, en etc. + * @throws LocaleNotLoadedException + */ + public DateTimeService(String locale) throws LocaleNotLoadedException { + setLocale(locale); + } + + public void setLocale(String locale) throws LocaleNotLoadedException { + if (LocaleService.getAvailableLocales().contains(locale)) { + currentLocale = locale; + } else { + throw new LocaleNotLoadedException(locale); + } + } + + public String getLocale() { + return currentLocale; + } + + public String getMonth(int month) { + try { + return LocaleService.getMonthNames(currentLocale)[month]; + } catch (final LocaleNotLoadedException e) { + getLogger().log(Level.SEVERE, "Error in getMonth", e); + return null; + } + } + + public String getShortMonth(int month) { + try { + return LocaleService.getShortMonthNames(currentLocale)[month]; + } catch (final LocaleNotLoadedException e) { + getLogger().log(Level.SEVERE, "Error in getShortMonth", e); + return null; + } + } + + public String getDay(int day) { + try { + return LocaleService.getDayNames(currentLocale)[day]; + } catch (final LocaleNotLoadedException e) { + getLogger().log(Level.SEVERE, "Error in getDay", e); + return null; + } + } + + public String getShortDay(int day) { + try { + return LocaleService.getShortDayNames(currentLocale)[day]; + } catch (final LocaleNotLoadedException e) { + getLogger().log(Level.SEVERE, "Error in getShortDay", e); + return null; + } + } + + public int getFirstDayOfWeek() { + try { + return LocaleService.getFirstDayOfWeek(currentLocale); + } catch (final LocaleNotLoadedException e) { + getLogger().log(Level.SEVERE, "Error in getFirstDayOfWeek", e); + return 0; + } + } + + public boolean isTwelveHourClock() { + try { + return LocaleService.isTwelveHourClock(currentLocale); + } catch (final LocaleNotLoadedException e) { + getLogger().log(Level.SEVERE, "Error in isTwelveHourClock", e); + return false; + } + } + + public String getClockDelimeter() { + try { + return LocaleService.getClockDelimiter(currentLocale); + } catch (final LocaleNotLoadedException e) { + getLogger().log(Level.SEVERE, "Error in getClockDelimiter", e); + return ":"; + } + } + + private static final String[] DEFAULT_AMPM_STRINGS = { "AM", "PM" }; + + public String[] getAmPmStrings() { + try { + return LocaleService.getAmPmStrings(currentLocale); + } catch (final LocaleNotLoadedException e) { + // TODO can this practically even happen? Should die instead? + getLogger().log(Level.SEVERE, + "Locale not loaded, using fallback : AM/PM", e); + return DEFAULT_AMPM_STRINGS; + } + } + + public int getStartWeekDay(Date date) { + final Date dateForFirstOfThisMonth = new Date(date.getYear(), + date.getMonth(), 1); + int firstDay; + try { + firstDay = LocaleService.getFirstDayOfWeek(currentLocale); + } catch (final LocaleNotLoadedException e) { + getLogger().log(Level.SEVERE, + "Locale not loaded, using fallback 0", e); + firstDay = 0; + } + int start = dateForFirstOfThisMonth.getDay() - firstDay; + if (start < 0) { + start = 6; + } + return start; + } + + public static void setMilliseconds(Date date, int ms) { + date.setTime(date.getTime() / 1000 * 1000 + ms); + } + + public static int getMilliseconds(Date date) { + if (date == null) { + return 0; + } + + return (int) (date.getTime() - date.getTime() / 1000 * 1000); + } + + public static int getNumberOfDaysInMonth(Date date) { + final int month = date.getMonth(); + if (month == 1 && true == isLeapYear(date)) { + return 29; + } + return maxDaysInMonth[month]; + } + + public static boolean isLeapYear(Date date) { + // Instantiate the date for 1st March of that year + final Date firstMarch = new Date(date.getYear(), 2, 1); + + // Go back 1 day + final long firstMarchTime = firstMarch.getTime(); + final long lastDayTimeFeb = firstMarchTime - (24 * 60 * 60 * 1000); // NUM_MILLISECS_A_DAY + + // Instantiate new Date with this time + final Date febLastDay = new Date(lastDayTimeFeb); + + // Check for date in this new instance + return (29 == febLastDay.getDate()) ? true : false; + } + + public static boolean isSameDay(Date d1, Date d2) { + return (getDayInt(d1) == getDayInt(d2)); + } + + public static boolean isInRange(Date date, Date rangeStart, Date rangeEnd, + Resolution resolution) { + Date s; + Date e; + if (rangeStart.after(rangeEnd)) { + s = rangeEnd; + e = rangeStart; + } else { + e = rangeEnd; + s = rangeStart; + } + long start = s.getYear() * 10000000000l; + long end = e.getYear() * 10000000000l; + long target = date.getYear() * 10000000000l; + + if (resolution == Resolution.YEAR) { + return (start <= target && end >= target); + } + start += s.getMonth() * 100000000l; + end += e.getMonth() * 100000000l; + target += date.getMonth() * 100000000l; + if (resolution == Resolution.MONTH) { + return (start <= target && end >= target); + } + start += s.getDate() * 1000000l; + end += e.getDate() * 1000000l; + target += date.getDate() * 1000000l; + if (resolution == Resolution.DAY) { + return (start <= target && end >= target); + } + start += s.getHours() * 10000l; + end += e.getHours() * 10000l; + target += date.getHours() * 10000l; + if (resolution == Resolution.HOUR) { + return (start <= target && end >= target); + } + start += s.getMinutes() * 100l; + end += e.getMinutes() * 100l; + target += date.getMinutes() * 100l; + if (resolution == Resolution.MINUTE) { + return (start <= target && end >= target); + } + start += s.getSeconds(); + end += e.getSeconds(); + target += date.getSeconds(); + return (start <= target && end >= target); + + } + + private static int getDayInt(Date date) { + final int y = date.getYear(); + final int m = date.getMonth(); + final int d = date.getDate(); + + return ((y + 1900) * 10000 + m * 100 + d) * 1000000000; + } + + /** + * Returns the ISO-8601 week number of the given date. + * + * @param date + * The date for which the week number should be resolved + * @return The ISO-8601 week number for {@literal date} + */ + public static int getISOWeekNumber(Date date) { + final long MILLISECONDS_PER_DAY = 24 * 3600 * 1000; + int dayOfWeek = date.getDay(); // 0 == sunday + + // ISO 8601 use weeks that start on monday so we use + // mon=1,tue=2,...sun=7; + if (dayOfWeek == 0) { + dayOfWeek = 7; + } + // Find nearest thursday (defines the week in ISO 8601). The week number + // for the nearest thursday is the same as for the target date. + int nearestThursdayDiff = 4 - dayOfWeek; // 4 is thursday + Date nearestThursday = new Date(date.getTime() + nearestThursdayDiff + * MILLISECONDS_PER_DAY); + + Date firstOfJanuary = new Date(nearestThursday.getYear(), 0, 1); + long timeDiff = nearestThursday.getTime() - firstOfJanuary.getTime(); + + // Rounding the result, as the division doesn't result in an integer + // when the given date is inside daylight saving time period. + int daysSinceFirstOfJanuary = (int) Math.round((double) timeDiff + / MILLISECONDS_PER_DAY); + + int weekNumber = (daysSinceFirstOfJanuary) / 7 + 1; + + return weekNumber; + } + + /** + * Check if format contains the month name. If it does we manually convert + * it to the month name since DateTimeFormat.format always uses the current + * locale and will replace the month name wrong if current locale is + * different from the locale set for the DateField. + * + * MMMM is converted into long month name, MMM is converted into short month + * name. '' are added around the name to avoid that DateTimeFormat parses + * the month name as a pattern. + * + * @param date + * The date to convert + * @param formatStr + * The format string that might contain MMM or MMMM + * @param dateTimeService + * Reference to the Vaadin DateTimeService + * @return + */ + public String formatDate(Date date, String formatStr) { + /* + * Format month and day names separately when locale for the + * DateTimeService is not the same as the browser locale + */ + formatStr = formatMonthNames(date, formatStr); + formatStr = formatDayNames(date, formatStr); + + // Format uses the browser locale + DateTimeFormat format = DateTimeFormat.getFormat(formatStr); + + String result = format.format(date); + + return result; + } + + private String formatDayNames(Date date, String formatStr) { + if (formatStr.contains("EEEE")) { + String dayName = getDay(date.getDay()); + + if (dayName != null) { + /* + * Replace 4 or more E:s with the quoted day name. Also + * concatenate generated string with any other string prepending + * or following the EEEE pattern, i.e. 'EEEE'ta ' becomes 'DAYta + * ' and not 'DAY''ta ', 'ab'EEEE becomes 'abDAY', 'x'EEEE'y' + * becomes 'xDAYy'. + */ + formatStr = formatStr.replaceAll("'([E]{4,})'", dayName); + formatStr = formatStr.replaceAll("([E]{4,})'", "'" + dayName); + formatStr = formatStr.replaceAll("'([E]{4,})", dayName + "'"); + formatStr = formatStr + .replaceAll("[E]{4,}", "'" + dayName + "'"); + } + } + + if (formatStr.contains("EEE")) { + + String dayName = getShortDay(date.getDay()); + + if (dayName != null) { + /* + * Replace 3 or more E:s with the quoted month name. Also + * concatenate generated string with any other string prepending + * or following the EEE pattern, i.e. 'EEE'ta ' becomes 'DAYta ' + * and not 'DAY''ta ', 'ab'EEE becomes 'abDAY', 'x'EEE'y' + * becomes 'xDAYy'. + */ + formatStr = formatStr.replaceAll("'([E]{3,})'", dayName); + formatStr = formatStr.replaceAll("([E]{3,})'", "'" + dayName); + formatStr = formatStr.replaceAll("'([E]{3,})", dayName + "'"); + formatStr = formatStr + .replaceAll("[E]{3,}", "'" + dayName + "'"); + } + } + + return formatStr; + } + + private String formatMonthNames(Date date, String formatStr) { + if (formatStr.contains("MMMM")) { + String monthName = getMonth(date.getMonth()); + + if (monthName != null) { + /* + * Replace 4 or more M:s with the quoted month name. Also + * concatenate generated string with any other string prepending + * or following the MMMM pattern, i.e. 'MMMM'ta ' becomes + * 'MONTHta ' and not 'MONTH''ta ', 'ab'MMMM becomes 'abMONTH', + * 'x'MMMM'y' becomes 'xMONTHy'. + */ + formatStr = formatStr.replaceAll("'([M]{4,})'", monthName); + formatStr = formatStr.replaceAll("([M]{4,})'", "'" + monthName); + formatStr = formatStr.replaceAll("'([M]{4,})", monthName + "'"); + formatStr = formatStr.replaceAll("[M]{4,}", "'" + monthName + + "'"); + } + } + + if (formatStr.contains("MMM")) { + + String monthName = getShortMonth(date.getMonth()); + + if (monthName != null) { + /* + * Replace 3 or more M:s with the quoted month name. Also + * concatenate generated string with any other string prepending + * or following the MMM pattern, i.e. 'MMM'ta ' becomes 'MONTHta + * ' and not 'MONTH''ta ', 'ab'MMM becomes 'abMONTH', 'x'MMM'y' + * becomes 'xMONTHy'. + */ + formatStr = formatStr.replaceAll("'([M]{3,})'", monthName); + formatStr = formatStr.replaceAll("([M]{3,})'", "'" + monthName); + formatStr = formatStr.replaceAll("'([M]{3,})", monthName + "'"); + formatStr = formatStr.replaceAll("[M]{3,}", "'" + monthName + + "'"); + } + } + + return formatStr; + } + + /** + * Replaces month names in the entered date with the name in the current + * browser locale. + * + * @param enteredDate + * Date string e.g. "5 May 2010" + * @param formatString + * Format string e.g. "d M yyyy" + * @return The date string where the month names have been replaced by the + * browser locale version + */ + private String parseMonthName(String enteredDate, String formatString) { + LocaleInfo browserLocale = LocaleInfo.getCurrentLocale(); + if (browserLocale.getLocaleName().equals(getLocale())) { + // No conversion needs to be done when locales match + return enteredDate; + } + String[] browserMonthNames = browserLocale.getDateTimeConstants() + .months(); + String[] browserShortMonthNames = browserLocale.getDateTimeConstants() + .shortMonths(); + + if (formatString.contains("MMMM")) { + // Full month name + for (int i = 0; i < 12; i++) { + enteredDate = enteredDate.replaceAll(getMonth(i), + browserMonthNames[i]); + } + } + if (formatString.contains("MMM")) { + // Short month name + for (int i = 0; i < 12; i++) { + enteredDate = enteredDate.replaceAll(getShortMonth(i), + browserShortMonthNames[i]); + } + } + + return enteredDate; + } + + /** + * Parses the given date string using the given format string and the locale + * set in this DateTimeService instance. + * + * @param dateString + * Date string e.g. "1 February 2010" + * @param formatString + * Format string e.g. "d MMMM yyyy" + * @param lenient + * true to use lenient parsing, false to use strict parsing + * @return A Date object representing the dateString. Never returns null. + * @throws IllegalArgumentException + * if the parsing fails + * + */ + public Date parseDate(String dateString, String formatString, + boolean lenient) throws IllegalArgumentException { + /* DateTimeFormat uses the browser's locale */ + DateTimeFormat format = DateTimeFormat.getFormat(formatString); + + /* + * Parse month names separately when locale for the DateTimeService is + * not the same as the browser locale + */ + dateString = parseMonthName(dateString, formatString); + + Date date; + + if (lenient) { + date = format.parse(dateString); + } else { + date = format.parseStrict(dateString); + } + + // Some version of Firefox sets the timestamp to 0 if parsing fails. + if (date != null && date.getTime() == 0) { + throw new IllegalArgumentException("Parsing of '" + dateString + + "' failed"); + } + + return date; + + } + + private static Logger getLogger() { + return Logger.getLogger(DateTimeService.class.getName()); + } +} diff --git a/client/src/main/java/com/vaadin/client/DeferredWorker.java b/client/src/main/java/com/vaadin/client/DeferredWorker.java new file mode 100644 index 0000000000..cc22cda2a2 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/DeferredWorker.java @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +/** + * Give widgets and connectors the possibility to indicate to the framework that + * there is work scheduled to be executed in the near future and that the + * framework should wait for this work to complete before assuming the UI has + * reached a steady state. + * + * @since 7.3 + * @author Vaadin Ltd + */ +public interface DeferredWorker { + /** + * Checks whether there are operations pending for this widget or connector + * that must be executed before reaching a steady state. + * + * @returns true iff there are operations pending which must be + * executed before reaching a steady state + */ + public boolean isWorkPending(); +} diff --git a/client/src/main/java/com/vaadin/client/DirectionalManagedLayout.java b/client/src/main/java/com/vaadin/client/DirectionalManagedLayout.java new file mode 100644 index 0000000000..eab9811082 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/DirectionalManagedLayout.java @@ -0,0 +1,24 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import com.vaadin.client.ui.ManagedLayout; + +public interface DirectionalManagedLayout extends ManagedLayout { + public void layoutVertically(); + + public void layoutHorizontally(); +} diff --git a/client/src/main/java/com/vaadin/client/EventHelper.java b/client/src/main/java/com/vaadin/client/EventHelper.java new file mode 100644 index 0000000000..1ee252af0f --- /dev/null +++ b/client/src/main/java/com/vaadin/client/EventHelper.java @@ -0,0 +1,147 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import static com.vaadin.shared.EventId.BLUR; +import static com.vaadin.shared.EventId.FOCUS; + +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.DomEvent.Type; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.Widget; + +/** + * Helper class for attaching/detaching handlers for Vaadin client side + * components, based on identifiers in UIDL. Helpers expect Paintables to be + * both listeners and sources for events. This helper cannot be used for more + * complex widgets. + *

+ * Possible current registration is given as parameter. The returned + * registration (possibly the same as given, should be store for next update. + *

+ * Pseudocode what helpers do: + * + *

+ * 
+ * if paintable has event listener in UIDL
+ *      if registration is null
+ *              register paintable as as handler for event
+ *      return the registration
+ * else 
+ *      if registration is not null
+ *              remove the handler from paintable
+ *      return null
+ * 
+ * 
+ * 
+ */ +public class EventHelper { + + /** + * Adds or removes a focus handler depending on if the connector has focus + * listeners on the server side or not. + * + * @param connector + * The connector to update. Must implement focusHandler. + * @param handlerRegistration + * The old registration reference or null if no handler has been + * registered previously + * @return a new registration handler that can be used to unregister the + * handler later + */ + public static HandlerRegistration updateFocusHandler( + T connector, HandlerRegistration handlerRegistration) { + return updateHandler(connector, connector, FOCUS, handlerRegistration, + FocusEvent.getType(), connector.getWidget()); + } + + /** + * Adds or removes a focus handler depending on if the connector has focus + * listeners on the server side or not. + * + * @param connector + * The connector to update. Must implement focusHandler. + * @param handlerRegistration + * The old registration reference or null if no handler has been + * registered previously + * @param widget + * The widget which emits focus events + * @return a new registration handler that can be used to unregister the + * handler later + */ + public static HandlerRegistration updateFocusHandler( + T connector, HandlerRegistration handlerRegistration, Widget widget) { + return updateHandler(connector, connector, FOCUS, handlerRegistration, + FocusEvent.getType(), widget); + } + + /** + * Adds or removes a blur handler depending on if the connector has blur + * listeners on the server side or not. + * + * @param connector + * The connector to update. Must implement BlurHandler. + * @param handlerRegistration + * The old registration reference or null if no handler has been + * registered previously + * @return a new registration handler that can be used to unregister the + * handler later + */ + public static HandlerRegistration updateBlurHandler( + T connector, HandlerRegistration handlerRegistration) { + return updateHandler(connector, connector, BLUR, handlerRegistration, + BlurEvent.getType(), connector.getWidget()); + } + + /** + * Adds or removes a blur handler depending on if the connector has blur + * listeners on the server side or not. + * + * @param connector + * The connector to update. Must implement BlurHandler. + * @param handlerRegistration + * The old registration reference or null if no handler has been + * registered previously + * @param widget + * The widget which emits blur events + * + * @return a new registration handler that can be used to unregister the + * handler later + */ + public static HandlerRegistration updateBlurHandler( + T connector, HandlerRegistration handlerRegistration, Widget widget) { + return updateHandler(connector, connector, BLUR, handlerRegistration, + BlurEvent.getType(), widget); + } + + public static HandlerRegistration updateHandler( + ComponentConnector connector, H handler, String eventIdentifier, + HandlerRegistration handlerRegistration, Type type, Widget widget) { + if (connector.hasEventListener(eventIdentifier)) { + if (handlerRegistration == null) { + handlerRegistration = widget.addDomHandler(handler, type); + } + } else if (handlerRegistration != null) { + handlerRegistration.removeHandler(); + handlerRegistration = null; + } + return handlerRegistration; + } +} diff --git a/client/src/main/java/com/vaadin/client/FastStringMap.java b/client/src/main/java/com/vaadin/client/FastStringMap.java new file mode 100644 index 0000000000..ba3d07025b --- /dev/null +++ b/client/src/main/java/com/vaadin/client/FastStringMap.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArrayString; + +public final class FastStringMap extends JavaScriptObject { + + protected FastStringMap() { + // JSO constructor + } + + public native void put(String key, T value) + /*-{ + this[key] = value; + }-*/; + + public native T get(String key) + /*-{ + return this[key]; + }-*/; + + public native boolean containsKey(String key) + /*-{ + //Can't use this.hasOwnProperty in case that key is used + return Object.hasOwnProperty.call(this, key); + }-*/; + + public native void remove(String key) + /*-{ + delete this[key]; + }-*/; + + public native JsArrayString getKeys() + /*-{ + var keys = []; + for(var key in this) { + if (Object.hasOwnProperty.call(this, key)) { + keys.push(key); + } + } + return keys; + }-*/; + + public native int size() + /*-{ + var size = 0; + for(var key in this) { + if (Object.hasOwnProperty.call(this, key)) { + size++; + } + } + return size; + }-*/; + + public static FastStringMap create() { + return JavaScriptObject.createObject().cast(); + } +} diff --git a/client/src/main/java/com/vaadin/client/FastStringSet.java b/client/src/main/java/com/vaadin/client/FastStringSet.java new file mode 100644 index 0000000000..756bd374dc --- /dev/null +++ b/client/src/main/java/com/vaadin/client/FastStringSet.java @@ -0,0 +1,101 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import java.util.Collection; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArrayString; + +public final class FastStringSet extends JavaScriptObject { + protected FastStringSet() { + // JSO constructor + } + + public native boolean contains(String string) + /*-{ + return this.hasOwnProperty(string); + }-*/; + + public native void add(String string) + /*-{ + this[string] = true; + }-*/; + + public native void addAll(JsArrayString array) + /*-{ + for(var i = 0; i < array.length; i++) { + this[array[i]] = true; + } + }-*/; + + public native void addAll(FastStringSet set) + /*-{ + for(var string in set) { + if (Object.hasOwnProperty.call(set, string)) { + this[string] = true; + } + } + }-*/; + + public native JsArrayString dump() + /*-{ + var array = []; + for(var string in this) { + if (this.hasOwnProperty(string)) { + array.push(string); + } + } + return array; + }-*/; + + public native void remove(String string) + /*-{ + delete this[string]; + }-*/; + + public native boolean isEmpty() + /*-{ + for(var string in this) { + if (this.hasOwnProperty(string)) { + return false; + } + } + return true; + }-*/; + + public static FastStringSet create() { + return JavaScriptObject.createObject().cast(); + } + + public native void addAllTo(Collection target) + /*-{ + for(var string in this) { + if (Object.hasOwnProperty.call(this, string)) { + target.@java.util.Collection::add(Ljava/lang/Object;)(string); + } + } + }-*/; + + public native void removeAll(FastStringSet valuesToRemove) + /*-{ + for(var string in valuesToRemove) { + if (Object.hasOwnProperty.call(valuesToRemove, string)) { + delete this[string]; + } + } + }-*/; +} diff --git a/client/src/main/java/com/vaadin/client/Focusable.java b/client/src/main/java/com/vaadin/client/Focusable.java new file mode 100644 index 0000000000..05b32a7b05 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/Focusable.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +/** + * GWT's HasFocus is way too overkill for just receiving focus in simple + * components. Vaadin uses this interface in addition to GWT's HasFocus to pass + * focus requests from server to actual ui widgets in browsers. + * + * So in to make your server side focusable component receive focus on client + * side it must either implement this or HasFocus interface. + */ +public interface Focusable { + /** + * Sets focus to this widget. + */ + public void focus(); +} diff --git a/client/src/main/java/com/vaadin/client/HasChildMeasurementHintConnector.java b/client/src/main/java/com/vaadin/client/HasChildMeasurementHintConnector.java new file mode 100644 index 0000000000..bb76377cd1 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/HasChildMeasurementHintConnector.java @@ -0,0 +1,72 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import com.vaadin.client.ui.ManagedLayout; +import com.vaadin.client.ui.layout.ElementResizeListener; + +/** + * Connector with layout measuring hint. Used to improve granularity of control + * over child component measurements. + * + * @since 7.6 + * @author Vaadin Ltd + */ +public interface HasChildMeasurementHintConnector extends + HasComponentsConnector { + + /** + * Specifies how you would like child components measurements to be handled. + * Since this is a hint, it can be ignored when deemed necessary. + */ + public enum ChildMeasurementHint { + + /** + * Always measure all child components (default). + */ + MEASURE_ALWAYS, + + /** + * Measure child component only if child component is a {@link Layout} + * or implements either {@link ManagedLayout} or + * {@link ElementResizeListener}. + */ + MEASURE_IF_NEEDED, + + /** + * Never measure child components. This can improve rendering speed of + * components with lots of children (e.g. Table), but can cause some + * child components to be rendered incorrectly (e.g. ComboBox). + */ + MEASURE_NEVER + } + + /** + * Sets the child measurement hint for this component. + * + * @param hint + * the value to set + */ + void setChildMeasurementHint(ChildMeasurementHint hint); + + /** + * Returns the current child measurement hint value. + * + * @return a ChildLayoutMeasureMode value + */ + ChildMeasurementHint getChildMeasurementHint(); + +} diff --git a/client/src/main/java/com/vaadin/client/HasComponentsConnector.java b/client/src/main/java/com/vaadin/client/HasComponentsConnector.java new file mode 100644 index 0000000000..19b93edd61 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/HasComponentsConnector.java @@ -0,0 +1,94 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client; + +import java.util.List; + +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.HasWidgets; +import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler; +import com.vaadin.ui.HasComponents; + +/** + * An interface used by client-side connectors whose widget is a component + * container (implements {@link HasWidgets}). + */ +public interface HasComponentsConnector extends ServerConnector { + + /** + * Update child components caption, description and error message. + * + *

+ * Each component is responsible for maintaining its caption, description + * and error message. In most cases components doesn't want to do that and + * those elements reside outside of the component. Because of this layouts + * must provide service for it's childen to show those elements for them. + *

+ * + * @param connector + * Child component for which service is requested. + */ + void updateCaption(ComponentConnector connector); + + /** + * Returns the child components for this connector. + *

+ * The children for this connector are defined as all {@link HasComponents}s + * whose parent is this {@link HasComponentsConnector}. + *

+ * Note that the method {@link ServerConnector#getChildren()} can return a + * larger list of children including both the child components and any + * extensions registered for the connector. + * + * @return A collection of child components for this connector. An empty + * collection if there are no children. Never returns null. + */ + public List getChildComponents(); + + /** + * Sets the children for this connector. This method should only be called + * by the framework to ensure that the connector hierarchy on the client + * side and the server side are in sync. + *

+ * Note that calling this method does not call + * {@link ConnectorHierarchyChangeHandler#onConnectorHierarchyChange(ConnectorHierarchyChangeEvent)} + * . The event method is called only when the hierarchy has been updated for + * all connectors. + *

+ * Note that this method is separate from + * {@link ServerConnector#setChildren(List)} and contains only child + * components. Both methods are called separately by the framework if the + * connector implements {@link HasComponentsConnector}. + * + * @param children + * The new child connectors (components only) + */ + public void setChildComponents(List children); + + /** + * Adds a handler that is called whenever the child hierarchy of this + * connector has been updated by the server. + * + * @param handler + * The handler that should be added. + * @return A handler registration reference that can be used to unregister + * the handler + */ + public HandlerRegistration addConnectorHierarchyChangeHandler( + ConnectorHierarchyChangeHandler handler); + +} diff --git a/client/src/main/java/com/vaadin/client/JavaScriptConnectorHelper.java b/client/src/main/java/com/vaadin/client/JavaScriptConnectorHelper.java new file mode 100644 index 0000000000..1833b370e5 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/JavaScriptConnectorHelper.java @@ -0,0 +1,511 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Logger; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.dom.client.Element; +import com.vaadin.client.communication.JavaScriptMethodInvocation; +import com.vaadin.client.communication.ServerRpcQueue; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; +import com.vaadin.client.ui.layout.ElementResizeEvent; +import com.vaadin.client.ui.layout.ElementResizeListener; +import com.vaadin.shared.JavaScriptConnectorState; +import com.vaadin.shared.communication.MethodInvocation; + +import elemental.json.JsonArray; + +public class JavaScriptConnectorHelper { + + private final ServerConnector connector; + private final JavaScriptObject nativeState = JavaScriptObject + .createObject(); + private final JavaScriptObject rpcMap = JavaScriptObject.createObject(); + + private final Map rpcObjects = new HashMap(); + private final Map> rpcMethods = new HashMap>(); + private final Map> resizeListeners = new HashMap>(); + + private JavaScriptObject connectorWrapper; + private int tag; + + private String initFunctionName; + + public JavaScriptConnectorHelper(ServerConnector connector) { + this.connector = connector; + + // Wildcard rpc object + rpcObjects.put("", JavaScriptObject.createObject()); + } + + /** + * The id of the previous response for which state changes have been + * processed. If this is the same as the + * {@link ApplicationConnection#getLastSeenServerSyncId()}, it means that the + * state change has already been handled and should not be done again. + */ + private int processedResponseId = -1; + + public void init() { + connector.addStateChangeHandler(new StateChangeHandler() { + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + processStateChanges(); + } + }); + } + + /** + * Makes sure the javascript part of the connector has been initialized. The + * javascript is usually initalized the first time a state change event is + * received, but it might in some cases be necessary to make this happen + * earlier. + * + * @since 7.4.0 + */ + public void ensureJavascriptInited() { + if (initFunctionName == null) { + processStateChanges(); + } + } + + private void processStateChanges() { + int lastResponseId = connector.getConnection().getLastSeenServerSyncId(); + if (processedResponseId == lastResponseId) { + return; + } + processedResponseId = lastResponseId; + + JavaScriptObject wrapper = getConnectorWrapper(); + JavaScriptConnectorState state = getConnectorState(); + + for (String callback : state.getCallbackNames()) { + ensureCallback(JavaScriptConnectorHelper.this, wrapper, callback); + } + + for (Entry> entry : state.getRpcInterfaces() + .entrySet()) { + String rpcName = entry.getKey(); + String jsName = getJsInterfaceName(rpcName); + if (!rpcObjects.containsKey(jsName)) { + Set methods = entry.getValue(); + rpcObjects.put(jsName, createRpcObject(rpcName, methods)); + + // Init all methods for wildcard rpc + for (String method : methods) { + JavaScriptObject wildcardRpcObject = rpcObjects.get(""); + Set interfaces = rpcMethods.get(method); + if (interfaces == null) { + interfaces = new HashSet(); + rpcMethods.put(method, interfaces); + attachRpcMethod(wildcardRpcObject, null, method); + } + interfaces.add(rpcName); + } + } + } + + // Init after setting up callbacks & rpc + if (initFunctionName == null) { + initJavaScript(); + } + + invokeIfPresent(wrapper, "onStateChange"); + } + + private static String getJsInterfaceName(String rpcName) { + return rpcName.replace('$', '.'); + } + + protected JavaScriptObject createRpcObject(String iface, Set methods) { + JavaScriptObject object = JavaScriptObject.createObject(); + + for (String method : methods) { + attachRpcMethod(object, iface, method); + } + + return object; + } + + protected boolean initJavaScript() { + ApplicationConfiguration conf = connector.getConnection() + .getConfiguration(); + ArrayList attemptedNames = new ArrayList(); + Integer tag = Integer.valueOf(this.tag); + while (tag != null) { + String serverSideClassName = conf.getServerSideClassNameForTag(tag); + String initFunctionName = serverSideClassName + .replaceAll("\\.", "_"); + if (tryInitJs(initFunctionName, getConnectorWrapper())) { + getLogger().info( + "JavaScript connector initialized using " + + initFunctionName); + this.initFunctionName = initFunctionName; + return true; + } else { + getLogger() + .warning( + "No JavaScript function " + initFunctionName + + " found"); + attemptedNames.add(initFunctionName); + tag = conf.getParentTag(tag.intValue()); + } + } + getLogger().info("No JavaScript init for connector found"); + showInitProblem(attemptedNames); + return false; + } + + protected void showInitProblem(ArrayList attemptedNames) { + // Default does nothing + } + + private static native boolean tryInitJs(String initFunctionName, + JavaScriptObject connectorWrapper) + /*-{ + if (typeof $wnd[initFunctionName] == 'function') { + $wnd[initFunctionName].apply(connectorWrapper); + return true; + } else { + return false; + } + }-*/; + + public JavaScriptObject getConnectorWrapper() { + if (connectorWrapper == null) { + connectorWrapper = createConnectorWrapper(this, + connector.getConnection(), nativeState, rpcMap, + connector.getConnectorId(), rpcObjects); + } + + return connectorWrapper; + } + + private static native JavaScriptObject createConnectorWrapper( + JavaScriptConnectorHelper h, ApplicationConnection c, + JavaScriptObject nativeState, JavaScriptObject registeredRpc, + String connectorId, Map rpcObjects) + /*-{ + return { + 'getConnectorId': function() { + return connectorId; + }, + 'getParentId': $entry(function(connectorId) { + return h.@com.vaadin.client.JavaScriptConnectorHelper::getParentId(Ljava/lang/String;)(connectorId); + }), + 'getState': function() { + return nativeState; + }, + 'getRpcProxy': $entry(function(iface) { + if (!iface) { + iface = ''; + } + return rpcObjects.@java.util.Map::get(Ljava/lang/Object;)(iface); + }), + 'getElement': $entry(function(connectorId) { + return h.@com.vaadin.client.JavaScriptConnectorHelper::getWidgetElement(Ljava/lang/String;)(connectorId); + }), + 'registerRpc': function(iface, rpcHandler) { + //registerRpc(handler) -> registerRpc('', handler); + if (!rpcHandler) { + rpcHandler = iface; + iface = ''; + } + if (!registeredRpc[iface]) { + registeredRpc[iface] = []; + } + registeredRpc[iface].push(rpcHandler); + }, + 'translateVaadinUri': $entry(function(uri) { + return c.@com.vaadin.client.ApplicationConnection::translateVaadinUri(Ljava/lang/String;)(uri); + }), + 'addResizeListener': function(element, resizeListener) { + if (!element || element.nodeType != 1) throw "element must be defined"; + if (typeof resizeListener != "function") throw "resizeListener must be defined"; + $entry(h.@com.vaadin.client.JavaScriptConnectorHelper::addResizeListener(*)).call(h, element, resizeListener); + }, + 'removeResizeListener': function(element, resizeListener) { + if (!element || element.nodeType != 1) throw "element must be defined"; + if (typeof resizeListener != "function") throw "resizeListener must be defined"; + $entry(h.@com.vaadin.client.JavaScriptConnectorHelper::removeResizeListener(*)).call(h, element, resizeListener); + } + }; + }-*/; + + // Called from JSNI to add a listener + private void addResizeListener(Element element, + final JavaScriptObject callbackFunction) { + Map elementListeners = resizeListeners + .get(element); + if (elementListeners == null) { + elementListeners = new HashMap(); + resizeListeners.put(element, elementListeners); + } + + ElementResizeListener listener = elementListeners.get(callbackFunction); + if (listener == null) { + LayoutManager layoutManager = LayoutManager.get(connector + .getConnection()); + listener = new ElementResizeListener() { + @Override + public void onElementResize(ElementResizeEvent e) { + invokeElementResizeCallback(e.getElement(), + callbackFunction); + } + }; + layoutManager.addElementResizeListener(element, listener); + elementListeners.put(callbackFunction, listener); + } + } + + private static native void invokeElementResizeCallback(Element element, + JavaScriptObject callbackFunction) + /*-{ + // Call with a simple event object and 'this' pointing to the global scope + callbackFunction.call($wnd, {'element': element}); + }-*/; + + // Called from JSNI to remove a listener + private void removeResizeListener(Element element, + JavaScriptObject callbackFunction) { + Map listenerMap = resizeListeners + .get(element); + if (listenerMap == null) { + return; + } + + ElementResizeListener listener = listenerMap.remove(callbackFunction); + if (listener != null) { + LayoutManager.get(connector.getConnection()) + .removeElementResizeListener(element, listener); + if (listenerMap.isEmpty()) { + resizeListeners.remove(element); + } + } + } + + private native void attachRpcMethod(JavaScriptObject rpc, String iface, + String method) + /*-{ + var self = this; + rpc[method] = $entry(function() { + self.@com.vaadin.client.JavaScriptConnectorHelper::fireRpc(Ljava/lang/String;Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(iface, method, arguments); + }); + }-*/; + + private String getParentId(String connectorId) { + ServerConnector target = getConnector(connectorId); + if (target == null) { + return null; + } + ServerConnector parent = target.getParent(); + if (parent == null) { + return null; + } else { + return parent.getConnectorId(); + } + } + + private Element getWidgetElement(String connectorId) { + ServerConnector target = getConnector(connectorId); + if (target instanceof ComponentConnector) { + return ((ComponentConnector) target).getWidget().getElement(); + } else { + return null; + } + } + + private ServerConnector getConnector(String connectorId) { + if (connectorId == null || connectorId.length() == 0) { + return connector; + } + + return ConnectorMap.get(connector.getConnection()).getConnector( + connectorId); + } + + private void fireRpc(String iface, String method, + JsArray arguments) { + if (iface == null) { + iface = findWildcardInterface(method); + } + + JsonArray argumentsArray = Util.jso2json(arguments); + Object[] parameters = new Object[arguments.length()]; + for (int i = 0; i < parameters.length; i++) { + parameters[i] = argumentsArray.get(i); + } + ServerRpcQueue rpcQueue = ServerRpcQueue.get(connector.getConnection()); + rpcQueue.add(new JavaScriptMethodInvocation(connector.getConnectorId(), + iface, method, parameters), false); + rpcQueue.flush(); + } + + private String findWildcardInterface(String method) { + Set interfaces = rpcMethods.get(method); + if (interfaces.size() == 1) { + return interfaces.iterator().next(); + } else { + // TODO Resolve conflicts using argument count and types + String interfaceList = ""; + for (String iface : interfaces) { + if (interfaceList.length() != 0) { + interfaceList += ", "; + } + interfaceList += getJsInterfaceName(iface); + } + + throw new IllegalStateException( + "Can not call method " + + method + + " for wildcard rpc proxy because the function is defined for multiple rpc interfaces: " + + interfaceList + + ". Retrieve a rpc proxy for a specific interface using getRpcProxy(interfaceName) to use the function."); + } + } + + private void fireCallback(String name, JsArray arguments) { + MethodInvocation invocation = new JavaScriptMethodInvocation( + connector.getConnectorId(), + "com.vaadin.ui.JavaScript$JavaScriptCallbackRpc", "call", + new Object[] { name, arguments }); + ServerRpcQueue rpcQueue = ServerRpcQueue.get(connector.getConnection()); + rpcQueue.add(invocation, false); + rpcQueue.flush(); + } + + public void setNativeState(JavaScriptObject state) { + updateNativeState(nativeState, state); + } + + private static native void updateNativeState(JavaScriptObject state, + JavaScriptObject input) + /*-{ + // Copy all fields to existing state object + for(var key in state) { + if (state.hasOwnProperty(key)) { + delete state[key]; + } + } + + for(var key in input) { + if (input.hasOwnProperty(key)) { + state[key] = input[key]; + } + } + }-*/; + + public Object[] decodeRpcParameters(JsonArray parametersJson) { + return new Object[] { Util.json2jso(parametersJson) }; + } + + public void setTag(int tag) { + this.tag = tag; + } + + public void invokeJsRpc(MethodInvocation invocation, + JsonArray parametersJson) { + String iface = invocation.getInterfaceName(); + String method = invocation.getMethodName(); + if ("com.vaadin.ui.JavaScript$JavaScriptCallbackRpc".equals(iface) + && "call".equals(method)) { + String callbackName = parametersJson.getString(0); + JavaScriptObject arguments = Util.json2jso(parametersJson.get(1)); + invokeCallback(getConnectorWrapper(), callbackName, arguments); + } else { + JavaScriptObject arguments = Util.json2jso(parametersJson); + invokeJsRpc(rpcMap, iface, method, arguments); + // Also invoke wildcard interface + invokeJsRpc(rpcMap, "", method, arguments); + } + } + + private static native void invokeCallback(JavaScriptObject connector, + String name, JavaScriptObject arguments) + /*-{ + connector[name].apply(connector, arguments); + }-*/; + + private static native void invokeJsRpc(JavaScriptObject rpcMap, + String interfaceName, String methodName, JavaScriptObject parameters) + /*-{ + var targets = rpcMap[interfaceName]; + if (!targets) { + return; + } + for(var i = 0; i < targets.length; i++) { + var target = targets[i]; + target[methodName].apply(target, parameters); + } + }-*/; + + private static native void ensureCallback(JavaScriptConnectorHelper h, + JavaScriptObject connector, String name) + /*-{ + connector[name] = $entry(function() { + var args = Array.prototype.slice.call(arguments, 0); + h.@com.vaadin.client.JavaScriptConnectorHelper::fireCallback(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(name, args); + }); + }-*/; + + private JavaScriptConnectorState getConnectorState() { + return (JavaScriptConnectorState) connector.getState(); + } + + public void onUnregister() { + invokeIfPresent(connectorWrapper, "onUnregister"); + + if (!resizeListeners.isEmpty()) { + LayoutManager layoutManager = LayoutManager.get(connector + .getConnection()); + for (Entry> entry : resizeListeners + .entrySet()) { + Element element = entry.getKey(); + for (ElementResizeListener listener : entry.getValue().values()) { + layoutManager + .removeElementResizeListener(element, listener); + } + } + resizeListeners.clear(); + } + } + + private static native void invokeIfPresent( + JavaScriptObject connectorWrapper, String functionName) + /*-{ + if (typeof connectorWrapper[functionName] == 'function') { + connectorWrapper[functionName].apply(connectorWrapper, arguments); + } + }-*/; + + public String getInitFunctionName() { + return initFunctionName; + } + + private static Logger getLogger() { + return Logger.getLogger(JavaScriptConnectorHelper.class.getName()); + } +} diff --git a/client/src/main/java/com/vaadin/client/JavaScriptExtension.java b/client/src/main/java/com/vaadin/client/JavaScriptExtension.java new file mode 100644 index 0000000000..9c60d38a5a --- /dev/null +++ b/client/src/main/java/com/vaadin/client/JavaScriptExtension.java @@ -0,0 +1,58 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client; + +import com.vaadin.client.communication.HasJavaScriptConnectorHelper; +import com.vaadin.client.extensions.AbstractExtensionConnector; +import com.vaadin.server.AbstractJavaScriptExtension; +import com.vaadin.shared.JavaScriptExtensionState; +import com.vaadin.shared.ui.Connect; + +@Connect(AbstractJavaScriptExtension.class) +public final class JavaScriptExtension extends AbstractExtensionConnector + implements HasJavaScriptConnectorHelper { + private final JavaScriptConnectorHelper helper = new JavaScriptConnectorHelper( + this); + + @Override + protected void init() { + super.init(); + helper.init(); + } + + @Override + public JavaScriptConnectorHelper getJavascriptConnectorHelper() { + return helper; + } + + @Override + public JavaScriptExtensionState getState() { + return (JavaScriptExtensionState) super.getState(); + } + + @Override + public void onUnregister() { + super.onUnregister(); + helper.onUnregister(); + } + + @Override + protected void extend(ServerConnector target) { + // Nothing to do for JavaScriptExtension here. Everything is done in + // javascript. + } +} diff --git a/client/src/main/java/com/vaadin/client/JsArrayObject.java b/client/src/main/java/com/vaadin/client/JsArrayObject.java new file mode 100644 index 0000000000..b6dcf3789d --- /dev/null +++ b/client/src/main/java/com/vaadin/client/JsArrayObject.java @@ -0,0 +1,42 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client; + +import com.google.gwt.core.client.JavaScriptObject; + +public final class JsArrayObject extends JavaScriptObject { + + protected JsArrayObject() { + // JSO constructor + } + + public native void add(T value) + /*-{ + this.push(value); + }-*/; + + public native int size() + /*-{ + return this.length; + }-*/; + + public native T get(int i) + /*-{ + return this[i]; + }-*/; + +} diff --git a/client/src/main/java/com/vaadin/client/LayoutManager.java b/client/src/main/java/com/vaadin/client/LayoutManager.java new file mode 100644 index 0000000000..ed83e195d7 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/LayoutManager.java @@ -0,0 +1,1836 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gwt.core.client.Duration; +import com.google.gwt.core.client.JsArrayString; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.user.client.Timer; +import com.vaadin.client.MeasuredSize.MeasureResult; +import com.vaadin.client.ui.ManagedLayout; +import com.vaadin.client.ui.PostLayoutListener; +import com.vaadin.client.ui.SimpleManagedLayout; +import com.vaadin.client.ui.VNotification; +import com.vaadin.client.ui.layout.ElementResizeEvent; +import com.vaadin.client.ui.layout.ElementResizeListener; +import com.vaadin.client.ui.layout.LayoutDependencyTree; + +public class LayoutManager { + private static final String STATE_CHANGE_MESSAGE = "Cannot run layout while processing state change from the server."; + + private static final String LOOP_ABORT_MESSAGE = "Aborting layout after 100 passes. This would probably be an infinite loop."; + + private static final boolean debugLogging = false; + + private ApplicationConnection connection; + private final Set measuredNonConnectorElements = new HashSet(); + private final MeasuredSize nullSize = new MeasuredSize(); + + private LayoutDependencyTree currentDependencyTree; + + private FastStringSet needsHorizontalLayout = FastStringSet.create(); + private FastStringSet needsVerticalLayout = FastStringSet.create(); + + private FastStringSet needsMeasure = FastStringSet.create(); + + private FastStringSet pendingOverflowFixes = FastStringSet.create(); + + private final Map> elementResizeListeners = new HashMap>(); + private final Set listenersToFire = new HashSet(); + + private boolean layoutPending = false; + private Timer layoutTimer = new Timer() { + @Override + public void run() { + layoutNow(); + } + }; + private boolean everythingNeedsMeasure = false; + + /** + * Sets the application connection this instance is connected to. Called + * internally by the framework. + * + * @param connection + * the application connection this instance is connected to + */ + public void setConnection(ApplicationConnection connection) { + if (this.connection != null) { + throw new RuntimeException( + "LayoutManager connection can never be changed"); + } + this.connection = connection; + } + + /** + * Returns the application connection for this layout manager. + * + * @return connection + */ + protected ApplicationConnection getConnection() { + return connection; + } + + /** + * Gets the layout manager associated with the given + * {@link ApplicationConnection}. + * + * @param connection + * the application connection to get a layout manager for + * @return the layout manager associated with the provided application + * connection + */ + public static LayoutManager get(ApplicationConnection connection) { + return connection.getLayoutManager(); + } + + /** + * Registers that a ManagedLayout is depending on the size of an Element. + * This causes this layout manager to measure the element in the beginning + * of every layout phase and call the appropriate layout method of the + * managed layout if the size of the element has changed. + * + * @param owner + * the ManagedLayout that depends on an element + * @param element + * the Element that should be measured + */ + public void registerDependency(ManagedLayout owner, Element element) { + MeasuredSize measuredSize = ensureMeasured(element); + setNeedsLayout(owner); + measuredSize.addDependent(owner.getConnectorId()); + } + + private MeasuredSize ensureMeasured(Element element) { + MeasuredSize measuredSize = getMeasuredSize(element, null); + if (measuredSize == null) { + measuredSize = new MeasuredSize(); + + if (ConnectorMap.get(connection).getConnector(element) == null) { + measuredNonConnectorElements.add(element); + } + setMeasuredSize(element, measuredSize); + } + return measuredSize; + } + + private boolean needsMeasure(Element e) { + ComponentConnector connector = connection.getConnectorMap() + .getConnector(e); + if (connector != null && needsMeasureForManagedLayout(connector)) { + return true; + } else if (elementResizeListeners.containsKey(e)) { + return true; + } else if (getMeasuredSize(e, nullSize).hasDependents()) { + return true; + } else { + return false; + } + } + + private boolean needsMeasureForManagedLayout(ComponentConnector connector) { + if (connector instanceof ManagedLayout) { + return true; + } else if (connector.getParent() instanceof ManagedLayout) { + return true; + } else { + return false; + } + } + + /** + * Assigns a measured size to an element. Method defined as protected to + * allow separate implementation for IE8. + * + * @param element + * the dom element to attach the measured size to + * @param measuredSize + * the measured size to attach to the element. If + * null, any previous measured size is removed. + */ + protected native void setMeasuredSize(Element element, + MeasuredSize measuredSize) + /*-{ + if (measuredSize) { + element.vMeasuredSize = measuredSize; + } else { + delete element.vMeasuredSize; + } + }-*/; + + /** + * Gets the measured size for an element. Method defined as protected to + * allow separate implementation for IE8. + * + * @param element + * The element to get measured size for + * @param defaultSize + * The size to return if no measured size could be found + * @return The measured size for the element or {@literal defaultSize} + */ + protected native MeasuredSize getMeasuredSize(Element element, + MeasuredSize defaultSize) + /*-{ + return element.vMeasuredSize || defaultSize; + }-*/; + + private final MeasuredSize getMeasuredSize(Element element) { + MeasuredSize measuredSize = getMeasuredSize(element, null); + if (measuredSize == null) { + measuredSize = new MeasuredSize(); + setMeasuredSize(element, measuredSize); + } + return measuredSize; + } + + /** + * Registers that a ManagedLayout is no longer depending on the size of an + * Element. + * + * @see #registerDependency(ManagedLayout, Element) + * + * @param owner + * the ManagedLayout no longer depends on an element + * @param element + * the Element that that no longer needs to be measured + */ + public void unregisterDependency(ManagedLayout owner, Element element) { + MeasuredSize measuredSize = getMeasuredSize(element, null); + if (measuredSize == null) { + return; + } + measuredSize.removeDependent(owner.getConnectorId()); + stopMeasuringIfUnecessary(element); + } + + public boolean isLayoutRunning() { + return currentDependencyTree != null; + } + + private void countLayout(FastStringMap layoutCounts, + ManagedLayout layout) { + Integer count = layoutCounts.get(layout.getConnectorId()); + if (count == null) { + count = Integer.valueOf(0); + } else { + count = Integer.valueOf(count.intValue() + 1); + } + layoutCounts.put(layout.getConnectorId(), count); + if (count.intValue() > 2) { + getLogger().severe( + Util.getConnectorString(layout) + " has been layouted " + + count.intValue() + " times"); + } + } + + public void layoutLater() { + if (!layoutPending) { + layoutPending = true; + layoutTimer.schedule(100); + } + } + + public void layoutNow() { + if (isLayoutRunning()) { + throw new IllegalStateException( + "Can't start a new layout phase before the previous layout phase ends."); + } + + if (connection.getMessageHandler().isUpdatingState()) { + // If assertions are enabled, throw an exception + assert false : STATE_CHANGE_MESSAGE; + + // Else just log a warning and postpone the layout + getLogger().warning(STATE_CHANGE_MESSAGE); + + // Framework will call layoutNow when the state update is completed + return; + } + + layoutPending = false; + layoutTimer.cancel(); + try { + currentDependencyTree = new LayoutDependencyTree(connection); + doLayout(); + } finally { + currentDependencyTree = null; + } + } + + /** + * Called once per iteration in the layout loop before size calculations so + * different browsers quirks can be handled. Mainly this is currently for + * the IE8 permutation. + */ + protected void performBrowserLayoutHacks() { + // Permutations implement this + } + + private void doLayout() { + getLogger().info("Starting layout phase"); + Profiler.enter("LayoutManager phase init"); + + FastStringMap layoutCounts = FastStringMap.create(); + + int passes = 0; + Duration totalDuration = new Duration(); + + ConnectorMap connectorMap = ConnectorMap.get(connection); + + JsArrayString dump = needsHorizontalLayout.dump(); + int dumpLength = dump.length(); + for (int i = 0; i < dumpLength; i++) { + String layoutId = dump.get(i); + currentDependencyTree.setNeedsHorizontalLayout(layoutId, true); + } + + dump = needsVerticalLayout.dump(); + dumpLength = dump.length(); + for (int i = 0; i < dumpLength; i++) { + String layoutId = dump.get(i); + currentDependencyTree.setNeedsVerticalLayout(layoutId, true); + } + needsHorizontalLayout = FastStringSet.create(); + needsVerticalLayout = FastStringSet.create(); + + dump = needsMeasure.dump(); + dumpLength = dump.length(); + for (int i = 0; i < dumpLength; i++) { + ServerConnector connector = connectorMap.getConnector(dump.get(i)); + if (connector != null) { + currentDependencyTree.setNeedsMeasure( + (ComponentConnector) connector, true); + } + } + needsMeasure = FastStringSet.create(); + + measureNonConnectors(); + + Profiler.leave("LayoutManager phase init"); + + while (true) { + Profiler.enter("Layout pass"); + passes++; + + performBrowserLayoutHacks(); + + Profiler.enter("Layout measure connectors"); + int measuredConnectorCount = measureConnectors( + currentDependencyTree, everythingNeedsMeasure); + Profiler.leave("Layout measure connectors"); + + everythingNeedsMeasure = false; + if (measuredConnectorCount == 0) { + getLogger().info("No more changes in pass " + passes); + Profiler.leave("Layout pass"); + break; + } + + int firedListeners = 0; + if (!listenersToFire.isEmpty()) { + firedListeners = listenersToFire.size(); + Profiler.enter("Layout fire resize events"); + for (Element element : listenersToFire) { + Collection listeners = elementResizeListeners + .get(element); + if (listeners != null) { + Profiler.enter("Layout fire resize events - listeners not null"); + Profiler.enter("ElementResizeListener.onElementResize copy list"); + ElementResizeListener[] array = listeners + .toArray(new ElementResizeListener[listeners + .size()]); + Profiler.leave("ElementResizeListener.onElementResize copy list"); + ElementResizeEvent event = new ElementResizeEvent(this, + element); + for (ElementResizeListener listener : array) { + try { + String key = null; + if (Profiler.isEnabled()) { + Profiler.enter("ElementResizeListener.onElementResize construct profiler key"); + key = "ElementResizeListener.onElementResize for " + + listener.getClass() + .getSimpleName(); + Profiler.leave("ElementResizeListener.onElementResize construct profiler key"); + Profiler.enter(key); + } + + listener.onElementResize(event); + if (Profiler.isEnabled()) { + Profiler.leave(key); + } + } catch (RuntimeException e) { + getLogger().log(Level.SEVERE, + "Error in resize listener", e); + } + } + Profiler.leave("Layout fire resize events - listeners not null"); + } + } + listenersToFire.clear(); + + Profiler.leave("Layout fire resize events"); + } + + Profiler.enter("LayoutManager handle ManagedLayout"); + + FastStringSet updatedSet = FastStringSet.create(); + + int layoutCount = 0; + while (currentDependencyTree.hasHorizontalConnectorToLayout() + || currentDependencyTree.hasVerticaConnectorToLayout()) { + + JsArrayString layoutTargets = currentDependencyTree + .getHorizontalLayoutTargetsJsArray(); + int length = layoutTargets.length(); + for (int i = 0; i < length; i++) { + ManagedLayout layout = (ManagedLayout) connectorMap + .getConnector(layoutTargets.get(i)); + if (layout instanceof DirectionalManagedLayout) { + currentDependencyTree + .markAsHorizontallyLayouted(layout); + DirectionalManagedLayout cl = (DirectionalManagedLayout) layout; + try { + String key = null; + if (Profiler.isEnabled()) { + key = "layoutHorizontally() for " + + cl.getClass().getSimpleName(); + Profiler.enter(key); + } + + cl.layoutHorizontally(); + layoutCount++; + + if (Profiler.isEnabled()) { + Profiler.leave(key); + } + } catch (RuntimeException e) { + getLogger().log(Level.SEVERE, + "Error in ManagedLayout handling", e); + } + countLayout(layoutCounts, cl); + } else { + currentDependencyTree + .markAsHorizontallyLayouted(layout); + currentDependencyTree.markAsVerticallyLayouted(layout); + SimpleManagedLayout rr = (SimpleManagedLayout) layout; + try { + String key = null; + if (Profiler.isEnabled()) { + key = "layout() for " + + rr.getClass().getSimpleName(); + Profiler.enter(key); + } + + rr.layout(); + layoutCount++; + + if (Profiler.isEnabled()) { + Profiler.leave(key); + } + } catch (RuntimeException e) { + getLogger() + .log(Level.SEVERE, + "Error in SimpleManagedLayout (horizontal) handling", + e); + + } + countLayout(layoutCounts, rr); + } + if (debugLogging) { + updatedSet.add(layout.getConnectorId()); + } + } + + layoutTargets = currentDependencyTree + .getVerticalLayoutTargetsJsArray(); + length = layoutTargets.length(); + for (int i = 0; i < length; i++) { + ManagedLayout layout = (ManagedLayout) connectorMap + .getConnector(layoutTargets.get(i)); + if (layout instanceof DirectionalManagedLayout) { + currentDependencyTree.markAsVerticallyLayouted(layout); + DirectionalManagedLayout cl = (DirectionalManagedLayout) layout; + try { + String key = null; + if (Profiler.isEnabled()) { + key = "layoutVertically() for " + + cl.getClass().getSimpleName(); + Profiler.enter(key); + } + + cl.layoutVertically(); + layoutCount++; + + if (Profiler.isEnabled()) { + Profiler.leave(key); + } + } catch (RuntimeException e) { + getLogger() + .log(Level.SEVERE, + "Error in DirectionalManagedLayout handling", + e); + } + countLayout(layoutCounts, cl); + } else { + currentDependencyTree + .markAsHorizontallyLayouted(layout); + currentDependencyTree.markAsVerticallyLayouted(layout); + SimpleManagedLayout rr = (SimpleManagedLayout) layout; + try { + String key = null; + if (Profiler.isEnabled()) { + key = "layout() for " + + rr.getClass().getSimpleName(); + Profiler.enter(key); + } + + rr.layout(); + layoutCount++; + + if (Profiler.isEnabled()) { + Profiler.leave(key); + } + } catch (RuntimeException e) { + getLogger() + .log(Level.SEVERE, + "Error in SimpleManagedLayout (vertical) handling", + e); + } + countLayout(layoutCounts, rr); + } + if (debugLogging) { + updatedSet.add(layout.getConnectorId()); + } + } + } + + Profiler.leave("LayoutManager handle ManagedLayout"); + + if (debugLogging) { + JsArrayString changedCids = updatedSet.dump(); + + StringBuilder b = new StringBuilder(" "); + b.append(changedCids.length()); + b.append(" requestLayout invocations "); + if (changedCids.length() < 30) { + for (int i = 0; i < changedCids.length(); i++) { + if (i != 0) { + b.append(", "); + } else { + b.append(": "); + } + String connectorString = changedCids.get(i); + if (changedCids.length() < 10) { + ServerConnector connector = ConnectorMap.get( + connection).getConnector(connectorString); + connectorString = Util + .getConnectorString(connector); + } + b.append(connectorString); + } + } + getLogger().info(b.toString()); + } + + Profiler.leave("Layout pass"); + + getLogger() + .info("Pass " + passes + " measured " + + measuredConnectorCount + " elements, fired " + + firedListeners + " listeners and did " + + layoutCount + " layouts."); + + if (passes > 100) { + getLogger().severe(LOOP_ABORT_MESSAGE); + if (ApplicationConfiguration.isDebugMode()) { + VNotification.createNotification( + VNotification.DELAY_FOREVER, + connection.getUIConnector().getWidget()) + .show(LOOP_ABORT_MESSAGE, VNotification.CENTERED, + "error"); + } + break; + } + } + + Profiler.enter("layout PostLayoutListener"); + JsArrayObject componentConnectors = connectorMap + .getComponentConnectorsAsJsArray(); + int size = componentConnectors.size(); + for (int i = 0; i < size; i++) { + ComponentConnector connector = componentConnectors.get(i); + if (connector instanceof PostLayoutListener) { + String key = null; + if (Profiler.isEnabled()) { + key = "layout PostLayoutListener for " + + connector.getClass().getSimpleName(); + Profiler.enter(key); + } + + ((PostLayoutListener) connector).postLayout(); + + if (Profiler.isEnabled()) { + Profiler.leave(key); + } + } + } + Profiler.leave("layout PostLayoutListener"); + + cleanMeasuredSizes(); + + getLogger().info( + "Total layout phase time: " + totalDuration.elapsedMillis() + + "ms"); + } + + private void logConnectorStatus(int connectorId) { + currentDependencyTree + .logDependencyStatus((ComponentConnector) ConnectorMap.get( + connection).getConnector(Integer.toString(connectorId))); + } + + private int measureConnectors(LayoutDependencyTree layoutDependencyTree, + boolean measureAll) { + Profiler.enter("Layout overflow fix handling"); + JsArrayString pendingOverflowConnectorsIds = pendingOverflowFixes + .dump(); + int pendingOverflowCount = pendingOverflowConnectorsIds.length(); + ConnectorMap connectorMap = ConnectorMap.get(connection); + if (pendingOverflowCount > 0) { + HashMap originalOverflows = new HashMap(); + + FastStringSet delayedOverflowFixes = FastStringSet.create(); + + // First set overflow to hidden (and save previous value so it can + // be restored later) + for (int i = 0; i < pendingOverflowCount; i++) { + String connectorId = pendingOverflowConnectorsIds.get(i); + ComponentConnector componentConnector = (ComponentConnector) connectorMap + .getConnector(connectorId); + + if (delayOverflowFix(componentConnector)) { + delayedOverflowFixes.add(connectorId); + continue; + } + + if (debugLogging) { + getLogger() + .info("Doing overflow fix for " + + Util.getConnectorString(componentConnector) + + " in " + + Util.getConnectorString(componentConnector + .getParent())); + } + Profiler.enter("Overflow fix apply"); + + Element parentElement = componentConnector.getWidget() + .getElement().getParentElement(); + Style style = parentElement.getStyle(); + String originalOverflow = style.getOverflow(); + + if (originalOverflow != null + && !originalOverflows.containsKey(parentElement)) { + // Store original value for restore, but only the first time + // the value is changed + originalOverflows.put(parentElement, originalOverflow); + } + + style.setOverflow(Overflow.HIDDEN); + Profiler.leave("Overflow fix apply"); + } + + pendingOverflowFixes.removeAll(delayedOverflowFixes); + + JsArrayString remainingOverflowFixIds = pendingOverflowFixes.dump(); + int remainingCount = remainingOverflowFixIds.length(); + + Profiler.enter("Overflow fix reflow"); + // Then ensure all scrolling elements are reflowed by measuring + for (int i = 0; i < remainingCount; i++) { + ComponentConnector componentConnector = (ComponentConnector) connectorMap + .getConnector(remainingOverflowFixIds.get(i)); + componentConnector.getWidget().getElement().getParentElement() + .getOffsetHeight(); + } + Profiler.leave("Overflow fix reflow"); + + Profiler.enter("Overflow fix restore"); + // Finally restore old overflow value and update bookkeeping + for (int i = 0; i < remainingCount; i++) { + String connectorId = remainingOverflowFixIds.get(i); + ComponentConnector componentConnector = (ComponentConnector) connectorMap + .getConnector(connectorId); + Element parentElement = componentConnector.getWidget() + .getElement().getParentElement(); + parentElement.getStyle().setProperty("overflow", + originalOverflows.get(parentElement)); + + layoutDependencyTree.setNeedsMeasure(componentConnector, true); + } + Profiler.leave("Overflow fix restore"); + + if (!pendingOverflowFixes.isEmpty()) { + getLogger().info( + "Did overflow fix for " + remainingCount + " elements"); + } + pendingOverflowFixes = delayedOverflowFixes; + } + Profiler.leave("Layout overflow fix handling"); + + int measureCount = 0; + if (measureAll) { + Profiler.enter("Layout measureAll"); + JsArrayObject allConnectors = connectorMap + .getComponentConnectorsAsJsArray(); + int size = allConnectors.size(); + + // Find connectors that should actually be measured + JsArrayObject connectors = JsArrayObject + .createArray().cast(); + for (int i = 0; i < size; i++) { + ComponentConnector candidate = allConnectors.get(i); + if (!Util.shouldSkipMeasurementOfConnector(candidate) + && needsMeasure(candidate.getWidget().getElement())) { + connectors.add(candidate); + } + } + + int connectorCount = connectors.size(); + for (int i = 0; i < connectorCount; i++) { + measureConnector(connectors.get(i)); + } + for (int i = 0; i < connectorCount; i++) { + layoutDependencyTree.setNeedsMeasure(connectors.get(i), false); + } + measureCount += connectorCount; + + Profiler.leave("Layout measureAll"); + } + + Profiler.enter("Layout measure from tree"); + while (layoutDependencyTree.hasConnectorsToMeasure()) { + JsArrayString measureTargets = layoutDependencyTree + .getMeasureTargetsJsArray(); + int length = measureTargets.length(); + for (int i = 0; i < length; i++) { + ComponentConnector connector = (ComponentConnector) connectorMap + .getConnector(measureTargets.get(i)); + measureConnector(connector); + measureCount++; + } + for (int i = 0; i < length; i++) { + ComponentConnector connector = (ComponentConnector) connectorMap + .getConnector(measureTargets.get(i)); + layoutDependencyTree.setNeedsMeasure(connector, false); + } + } + Profiler.leave("Layout measure from tree"); + + return measureCount; + } + + /* + * Delay the overflow fix if the involved connectors might still change + */ + private boolean delayOverflowFix(ComponentConnector componentConnector) { + if (!currentDependencyTree.noMoreChangesExpected(componentConnector)) { + return true; + } + ServerConnector parent = componentConnector.getParent(); + if (parent instanceof ComponentConnector + && !currentDependencyTree + .noMoreChangesExpected((ComponentConnector) parent)) { + return true; + } + + return false; + } + + private void measureConnector(ComponentConnector connector) { + Profiler.enter("LayoutManager.measureConnector"); + Element element = connector.getWidget().getElement(); + MeasuredSize measuredSize = getMeasuredSize(element); + MeasureResult measureResult = measuredAndUpdate(element, measuredSize); + + if (measureResult.isChanged()) { + onConnectorChange(connector, measureResult.isWidthChanged(), + measureResult.isHeightChanged()); + } + Profiler.leave("LayoutManager.measureConnector"); + } + + private void onConnectorChange(ComponentConnector connector, + boolean widthChanged, boolean heightChanged) { + Profiler.enter("LayoutManager.onConnectorChange"); + Profiler.enter("LayoutManager.onConnectorChange setNeedsOverflowFix"); + setNeedsOverflowFix(connector); + Profiler.leave("LayoutManager.onConnectorChange setNeedsOverflowFix"); + Profiler.enter("LayoutManager.onConnectorChange heightChanged"); + if (heightChanged) { + currentDependencyTree.markHeightAsChanged(connector); + } + Profiler.leave("LayoutManager.onConnectorChange heightChanged"); + Profiler.enter("LayoutManager.onConnectorChange widthChanged"); + if (widthChanged) { + currentDependencyTree.markWidthAsChanged(connector); + } + Profiler.leave("LayoutManager.onConnectorChange widthChanged"); + Profiler.leave("LayoutManager.onConnectorChange"); + } + + private void setNeedsOverflowFix(ComponentConnector connector) { + // IE9 doesn't need the original fix, but for some reason it needs this + if (BrowserInfo.get().requiresOverflowAutoFix() + || BrowserInfo.get().isIE9()) { + ComponentConnector scrollingBoundary = currentDependencyTree + .getScrollingBoundary(connector); + if (scrollingBoundary != null) { + pendingOverflowFixes.add(scrollingBoundary.getConnectorId()); + } + } + } + + private void measureNonConnectors() { + Profiler.enter("LayoutManager.measureNonConenctors"); + for (Element element : measuredNonConnectorElements) { + measuredAndUpdate(element, getMeasuredSize(element, null)); + } + Profiler.leave("LayoutManager.measureNonConenctors"); + getLogger().info( + "Measured " + measuredNonConnectorElements.size() + + " non connector elements"); + } + + private MeasureResult measuredAndUpdate(Element element, + MeasuredSize measuredSize) { + MeasureResult measureResult = measuredSize.measure(element); + if (measureResult.isChanged()) { + notifyListenersAndDepdendents(element, + measureResult.isWidthChanged(), + measureResult.isHeightChanged()); + } + return measureResult; + } + + private void notifyListenersAndDepdendents(Element element, + boolean widthChanged, boolean heightChanged) { + assert widthChanged || heightChanged; + + Profiler.enter("LayoutManager.notifyListenersAndDepdendents"); + + MeasuredSize measuredSize = getMeasuredSize(element, nullSize); + JsArrayString dependents = measuredSize.getDependents(); + for (int i = 0; i < dependents.length(); i++) { + String pid = dependents.get(i); + if (pid != null) { + if (heightChanged) { + currentDependencyTree.setNeedsVerticalLayout(pid, true); + } + if (widthChanged) { + currentDependencyTree.setNeedsHorizontalLayout(pid, true); + } + } + } + if (elementResizeListeners.containsKey(element)) { + listenersToFire.add(element); + } + Profiler.leave("LayoutManager.notifyListenersAndDepdendents"); + } + + private static boolean isManagedLayout(ComponentConnector connector) { + return connector instanceof ManagedLayout; + } + + public void forceLayout() { + ConnectorMap connectorMap = connection.getConnectorMap(); + JsArrayObject componentConnectors = connectorMap + .getComponentConnectorsAsJsArray(); + int size = componentConnectors.size(); + for (int i = 0; i < size; i++) { + ComponentConnector connector = componentConnectors.get(i); + if (connector instanceof ManagedLayout) { + setNeedsLayout((ManagedLayout) connector); + } + } + setEverythingNeedsMeasure(); + layoutNow(); + } + + /** + * Marks that a ManagedLayout should be layouted in the next layout phase + * even if none of the elements managed by the layout have been resized. + *

+ * This method should not be invoked during a layout phase since it only + * controls what will happen in the beginning of the next phase. If you want + * to explicitly cause some layout to be considered in an ongoing layout + * phase, you should use {@link #setNeedsMeasure(ComponentConnector)} + * instead. + * + * @param layout + * the managed layout that should be layouted + */ + public final void setNeedsLayout(ManagedLayout layout) { + setNeedsHorizontalLayout(layout); + setNeedsVerticalLayout(layout); + } + + /** + * Marks that a ManagedLayout should be layouted horizontally in the next + * layout phase even if none of the elements managed by the layout have been + * resized horizontally. + *

+ * For SimpleManagedLayout which is always layouted in both directions, this + * has the same effect as {@link #setNeedsLayout(ManagedLayout)}. + *

+ * This method should not be invoked during a layout phase since it only + * controls what will happen in the beginning of the next phase. If you want + * to explicitly cause some layout to be considered in an ongoing layout + * phase, you should use {@link #setNeedsMeasure(ComponentConnector)} + * instead. + * + * @param layout + * the managed layout that should be layouted + */ + public final void setNeedsHorizontalLayout(ManagedLayout layout) { + if (isLayoutRunning()) { + getLogger() + .warning( + "setNeedsHorizontalLayout should not be run while a layout phase is in progress."); + } + needsHorizontalLayout.add(layout.getConnectorId()); + } + + /** + * Marks that a ManagedLayout should be layouted vertically in the next + * layout phase even if none of the elements managed by the layout have been + * resized vertically. + *

+ * For SimpleManagedLayout which is always layouted in both directions, this + * has the same effect as {@link #setNeedsLayout(ManagedLayout)}. + *

+ * This method should not be invoked during a layout phase since it only + * controls what will happen in the beginning of the next phase. If you want + * to explicitly cause some layout to be considered in an ongoing layout + * phase, you should use {@link #setNeedsMeasure(ComponentConnector)} + * instead. + * + * @param layout + * the managed layout that should be layouted + */ + public final void setNeedsVerticalLayout(ManagedLayout layout) { + if (isLayoutRunning()) { + getLogger() + .warning( + "setNeedsVerticalLayout should not be run while a layout phase is in progress."); + } + needsVerticalLayout.add(layout.getConnectorId()); + } + + /** + * Gets the outer height (including margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + *

    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + *

+ * The value returned by this method is always rounded up. To get the exact + * outer width, use {@link #getOuterHeightDouble(Element)} + * + * @param element + * the element to get the measured size for + * @return the measured outer height (including margins, paddings and + * borders) of the element in pixels. + */ + public final int getOuterHeight(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return (int) Math.ceil(getMeasuredSize(element, nullSize) + .getOuterHeight()); + } + + /** + * Gets the outer height (including margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + *

    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + * + * @since 7.5.1 + * @param element + * the element to get the measured size for + * @return the measured outer height (including margins, paddings and + * borders) of the element in pixels. + */ + public final double getOuterHeightDouble(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getOuterHeight(); + } + + /** + * Gets the outer width (including margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + *

+ * The value returned by this method is always rounded up. To get the exact + * outer width, use {@link #getOuterWidthDouble(Element)} + * + * @since 7.5.1 + * @param element + * the element to get the measured size for + * @return the measured outer width (including margins, paddings and + * borders) of the element in pixels. + */ + public final int getOuterWidth(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return (int) Math.ceil(getMeasuredSize(element, nullSize) + .getOuterWidth()); + } + + /** + * Gets the outer width (including margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + *

    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + * + * @param element + * the element to get the measured size for + * @return the measured outer width (including margins, paddings and + * borders) of the element in pixels. + */ + public final double getOuterWidthDouble(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getOuterWidth(); + } + + /** + * Gets the inner height (excluding margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + *

+ * The value returned by this method is always rounded up. To get the exact + * outer width, use {@link #getInnerHeightDouble(Element)} + * + * @param element + * the element to get the measured size for + * @return the measured inner height (excluding margins, paddings and + * borders) of the element in pixels. + */ + public final int getInnerHeight(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return (int) Math.ceil(getMeasuredSize(element, nullSize) + .getInnerHeight()); + } + + /** + * Gets the inner height (excluding margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + *

    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + * + * @since 7.5.1 + * @param element + * the element to get the measured size for + * @return the measured inner height (excluding margins, paddings and + * borders) of the element in pixels. + */ + public final double getInnerHeightDouble(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getInnerHeight(); + } + + /** + * Gets the inner width (excluding margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + *

+ * The value returned by this method is always rounded up. To get the exact + * outer width, use {@link #getOuterHeightDouble(Element)} + * + * @param element + * the element to get the measured size for + * @return the measured inner width (excluding margins, paddings and + * borders) of the element in pixels. + */ + public final int getInnerWidth(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return (int) Math.ceil(getMeasuredSize(element, nullSize) + .getInnerWidth()); + } + + /** + * Gets the inner width (excluding margins, paddings and borders) of the + * given element, provided that it has been measured. These elements are + * guaranteed to be measured: + *

    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * -1 is returned if the element has not been measured. If 0 is returned, it + * might indicate that the element is not attached to the DOM. + * + * @since 7.5.1 + * @param element + * the element to get the measured size for + * @return the measured inner width (excluding margins, paddings and + * borders) of the element in pixels. + */ + public final double getInnerWidthDouble(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getInnerWidth(); + } + + /** + * Gets the border height (top border + bottom border) of the given element, + * provided that it has been measured. These elements are guaranteed to be + * measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured border height (top border + bottom border) of the + * element in pixels. + */ + public final int getBorderHeight(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getBorderHeight(); + } + + /** + * Gets the padding height (top padding + bottom padding) of the given + * element, provided that it has been measured. These elements are + * guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured padding height (top padding + bottom padding) of the + * element in pixels. + */ + public int getPaddingHeight(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getPaddingHeight(); + } + + /** + * Gets the border width (left border + right border) of the given element, + * provided that it has been measured. These elements are guaranteed to be + * measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured border width (left border + right border) of the + * element in pixels. + */ + public int getBorderWidth(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getBorderWidth(); + } + + /** + * Gets the top border of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured top border of the element in pixels. + */ + public int getBorderTop(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getBorderTop(); + } + + /** + * Gets the left border of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured left border of the element in pixels. + */ + public int getBorderLeft(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getBorderLeft(); + } + + /** + * Gets the bottom border of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured bottom border of the element in pixels. + */ + public int getBorderBottom(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getBorderBottom(); + } + + /** + * Gets the right border of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured right border of the element in pixels. + */ + public int getBorderRight(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getBorderRight(); + } + + /** + * Gets the padding width (left padding + right padding) of the given + * element, provided that it has been measured. These elements are + * guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured padding width (left padding + right padding) of the + * element in pixels. + */ + public int getPaddingWidth(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getPaddingWidth(); + } + + /** + * Gets the top padding of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured top padding of the element in pixels. + */ + public int getPaddingTop(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getPaddingTop(); + } + + /** + * Gets the left padding of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured left padding of the element in pixels. + */ + public int getPaddingLeft(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getPaddingLeft(); + } + + /** + * Gets the bottom padding of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured bottom padding of the element in pixels. + */ + public int getPaddingBottom(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getPaddingBottom(); + } + + /** + * Gets the right padding of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured right padding of the element in pixels. + */ + public int getPaddingRight(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getPaddingRight(); + } + + /** + * Gets the top margin of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured top margin of the element in pixels. + */ + public int getMarginTop(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getMarginTop(); + } + + /** + * Gets the right margin of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured right margin of the element in pixels. + */ + public int getMarginRight(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getMarginRight(); + } + + /** + * Gets the bottom margin of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured bottom margin of the element in pixels. + */ + public int getMarginBottom(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getMarginBottom(); + } + + /** + * Gets the left margin of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured left margin of the element in pixels. + */ + public int getMarginLeft(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getMarginLeft(); + } + + /** + * Gets the combined top & bottom margin of the given element, provided that + * they have been measured. These elements are guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured margin for + * @return the measured top+bottom margin of the element in pixels. + */ + public int getMarginHeight(Element element) { + return getMarginTop(element) + getMarginBottom(element); + } + + /** + * Gets the combined left & right margin of the given element, provided that + * they have been measured. These elements are guaranteed to be measured: + *
    + *
  • ManagedLayouts and their child Connectors + *
  • Elements for which there is at least one ElementResizeListener + *
  • Elements for which at least one ManagedLayout has registered a + * dependency + *
+ * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured margin for + * @return the measured left+right margin of the element in pixels. + */ + public int getMarginWidth(Element element) { + return getMarginLeft(element) + getMarginRight(element); + } + + /** + * Registers the outer height (including margins, borders and paddings) of a + * component. This can be used as an optimization by ManagedLayouts; by + * informing the LayoutManager about what size a component will have, the + * layout propagation can continue directly without first measuring the + * potentially resized elements. + * + * @param component + * the component for which the size is reported + * @param outerHeight + * the new outer height (including margins, borders and paddings) + * of the component in pixels + */ + public void reportOuterHeight(ComponentConnector component, int outerHeight) { + Element element = component.getWidget().getElement(); + MeasuredSize measuredSize = getMeasuredSize(element); + if (isLayoutRunning()) { + boolean heightChanged = measuredSize.setOuterHeight(outerHeight); + + if (heightChanged) { + onConnectorChange(component, false, true); + notifyListenersAndDepdendents(element, false, true); + } + currentDependencyTree.setNeedsVerticalMeasure(component, false); + } else if (measuredSize.getOuterHeight() != outerHeight) { + setNeedsMeasure(component); + } + } + + /** + * Registers the height reserved for a relatively sized component. This can + * be used as an optimization by ManagedLayouts; by informing the + * LayoutManager about what size a component will have, the layout + * propagation can continue directly without first measuring the potentially + * resized elements. + * + * @param component + * the relatively sized component for which the size is reported + * @param assignedHeight + * the inner height of the relatively sized component's parent + * element in pixels + */ + public void reportHeightAssignedToRelative(ComponentConnector component, + int assignedHeight) { + assert component.isRelativeHeight(); + + float percentSize = parsePercent(component.getState().height == null ? "" + : component.getState().height); + int effectiveHeight = Math.round(assignedHeight * (percentSize / 100)); + + reportOuterHeight(component, effectiveHeight); + } + + /** + * Registers the width reserved for a relatively sized component. This can + * be used as an optimization by ManagedLayouts; by informing the + * LayoutManager about what size a component will have, the layout + * propagation can continue directly without first measuring the potentially + * resized elements. + * + * @param component + * the relatively sized component for which the size is reported + * @param assignedWidth + * the inner width of the relatively sized component's parent + * element in pixels + */ + public void reportWidthAssignedToRelative(ComponentConnector component, + int assignedWidth) { + assert component.isRelativeWidth(); + + float percentSize = parsePercent(component.getState().width == null ? "" + : component.getState().width); + int effectiveWidth = Math.round(assignedWidth * (percentSize / 100)); + + reportOuterWidth(component, effectiveWidth); + } + + private static float parsePercent(String size) { + return Float.parseFloat(size.substring(0, size.length() - 1)); + } + + /** + * Registers the outer width (including margins, borders and paddings) of a + * component. This can be used as an optimization by ManagedLayouts; by + * informing the LayoutManager about what size a component will have, the + * layout propagation can continue directly without first measuring the + * potentially resized elements. + * + * @param component + * the component for which the size is reported + * @param outerWidth + * the new outer width (including margins, borders and paddings) + * of the component in pixels + */ + public void reportOuterWidth(ComponentConnector component, int outerWidth) { + Element element = component.getWidget().getElement(); + MeasuredSize measuredSize = getMeasuredSize(element); + if (isLayoutRunning()) { + boolean widthChanged = measuredSize.setOuterWidth(outerWidth); + + if (widthChanged) { + onConnectorChange(component, true, false); + notifyListenersAndDepdendents(element, true, false); + } + currentDependencyTree.setNeedsHorizontalMeasure(component, false); + } else if (measuredSize.getOuterWidth() != outerWidth) { + setNeedsMeasure(component); + } + } + + /** + * Adds a listener that will be notified whenever the size of a specific + * element changes. Adding a listener to an element also ensures that all + * sizes for that element will be available starting from the next layout + * phase. + * + * @param element + * the element that should be checked for size changes + * @param listener + * an ElementResizeListener that will be informed whenever the + * size of the target element has changed + */ + public void addElementResizeListener(Element element, + ElementResizeListener listener) { + Collection listeners = elementResizeListeners + .get(element); + if (listeners == null) { + listeners = new HashSet(); + elementResizeListeners.put(element, listeners); + ensureMeasured(element); + } + listeners.add(listener); + } + + /** + * Removes an element resize listener from the provided element. This might + * cause this LayoutManager to stop tracking the size of the element if no + * other sources are interested in the size. + * + * @param element + * the element to which the element resize listener was + * previously added + * @param listener + * the ElementResizeListener that should no longer get informed + * about size changes to the target element. + */ + public void removeElementResizeListener(Element element, + ElementResizeListener listener) { + Collection listeners = elementResizeListeners + .get(element); + if (listeners != null) { + listeners.remove(listener); + if (listeners.isEmpty()) { + elementResizeListeners.remove(element); + stopMeasuringIfUnecessary(element); + } + } + } + + private void stopMeasuringIfUnecessary(Element element) { + if (!needsMeasure(element)) { + measuredNonConnectorElements.remove(element); + setMeasuredSize(element, null); + } + } + + /** + * Informs this LayoutManager that the size of a component might have + * changed. This method should be used whenever the size of an individual + * component might have changed from outside of Vaadin's normal update + * phase, e.g. when an icon has been loaded or when the user resizes some + * part of the UI using the mouse. + *

+ * To set an entire component hierarchy to be measured, use + * {@link #setNeedsMeasureRecursively(ComponentConnector)} instead. + *

+ * If there is no upcoming layout phase, a new layout phase is scheduled. + * + * @param component + * the component whose size might have changed. + */ + public void setNeedsMeasure(ComponentConnector component) { + if (isLayoutRunning()) { + currentDependencyTree.setNeedsMeasure(component, true); + } else { + needsMeasure.add(component.getConnectorId()); + layoutLater(); + } + } + + /** + * Informs this LayoutManager that some sizes in a component hierarchy might + * have changed. This method should be used whenever the size of any child + * component might have changed from outside of Vaadin's normal update + * phase, e.g. when a CSS class name related to sizing has been changed. + *

+ * To set a single component to be measured, use + * {@link #setNeedsMeasure(ComponentConnector)} instead. + *

+ * If there is no upcoming layout phase, a new layout phase is scheduled. + * + * @since 7.2 + * @param component + * the component at the root of the component hierarchy to + * measure + */ + public void setNeedsMeasureRecursively(ComponentConnector component) { + setNeedsMeasure(component); + + if (component instanceof HasComponentsConnector) { + HasComponentsConnector hasComponents = (HasComponentsConnector) component; + for (ComponentConnector child : hasComponents.getChildComponents()) { + setNeedsMeasureRecursively(child); + } + } + } + + public void setEverythingNeedsMeasure() { + everythingNeedsMeasure = true; + } + + /** + * Clean measured sizes which are no longer needed. Only for IE8. + */ + public void cleanMeasuredSizes() { + } + + private static Logger getLogger() { + return Logger.getLogger(LayoutManager.class.getName()); + } + + /** + * Checks if there is something waiting for a layout to take place. + * + * @since 7.5.6 + * @return true if there are connectors waiting for measurement or layout, + * false otherwise + */ + public boolean isLayoutNeeded() { + if (!needsHorizontalLayout.isEmpty() || !needsVerticalLayout.isEmpty()) { + return true; + } + if (!needsMeasure.isEmpty()) { + return true; + } + + if (everythingNeedsMeasure) { + return true; + } + + return false; + } +} diff --git a/client/src/main/java/com/vaadin/client/LayoutManagerIE8.java b/client/src/main/java/com/vaadin/client/LayoutManagerIE8.java new file mode 100644 index 0000000000..479155d0e6 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/LayoutManagerIE8.java @@ -0,0 +1,115 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Node; +import com.google.gwt.user.client.ui.RootPanel; + +/** + * Alternative MeasuredSize storage for IE8. Storing any information in a DOM + * element in IE8 seems to make the browser think the element has changed in a + * way that requires a reflow. To work around that, the MeasureData is instead + * stored in Map for IE8. + * + * This implementation is injected for IE8 by a replace-with definition in the + * GWT module. + * + * @author Vaadin Ltd + * @since 7.0.0 + */ +public class LayoutManagerIE8 extends LayoutManager { + + private Map measuredSizes = new HashMap(); + + // this method is needed to test for memory leaks (see + // LayoutMemoryUsageIE8ExtensionConnector) but can be private + private int getMeasuredSizesMapSize() { + return measuredSizes.size(); + } + + @Override + protected void setMeasuredSize(Element element, MeasuredSize measuredSize) { + if (measuredSize != null) { + measuredSizes.put(element, measuredSize); + } else { + measuredSizes.remove(element); + } + // clear any values that are saved to the element + if (super.getMeasuredSize(element, null) != null) { + super.setMeasuredSize(element, null); + } + } + + @Override + protected MeasuredSize getMeasuredSize(Element element, + MeasuredSize defaultSize) { + MeasuredSize measured = measuredSizes.get(element); + if (measured != null) { + return measured; + } else { + // check if saved to the element instead + MeasuredSize measuredSize = super.getMeasuredSize(element, null); + if (measuredSize != null) { + // move the value back to the map + setMeasuredSize(element, measuredSize); + return measuredSize; + } + return defaultSize; + } + } + + @Override + public void cleanMeasuredSizes() { + Profiler.enter("LayoutManager.cleanMeasuredSizes"); + + // #12688: IE8 was leaking memory when adding&removing components. + // Uses IE specific logic to figure if an element has been removed from + // DOM or not. For removed elements the measured size is stored within + // the element in case the element gets re-attached. + Node rootNode = Document.get().getBody(); + + Iterator i = measuredSizes.keySet().iterator(); + while (i.hasNext()) { + Element e = i.next(); + if (!rootNode.isOrHasChild(e)) { + // Store in element in case is still needed. + // Not attached, so reflow isn't a problem. + super.setMeasuredSize(e, measuredSizes.get(e)); + i.remove(); + } + } + + Profiler.leave("LayoutManager.cleanMeasuredSizes"); + } + + @Override + protected void performBrowserLayoutHacks() { + Profiler.enter("LayoutManagerIE8.performBrowserLayoutHacks"); + /* + * Fixes IE8 issues where IE8 sometimes forgets to update the size of + * the containing element. To force a reflow by modifying the magical + * zoom property. + */ + WidgetUtil.forceIE8Redraw(RootPanel.get().getElement()); + Profiler.leave("LayoutManagerIE8.performBrowserLayoutHacks"); + } +} diff --git a/client/src/main/java/com/vaadin/client/LocaleNotLoadedException.java b/client/src/main/java/com/vaadin/client/LocaleNotLoadedException.java new file mode 100644 index 0000000000..6f59e786e4 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/LocaleNotLoadedException.java @@ -0,0 +1,25 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client; + +@SuppressWarnings("serial") +public class LocaleNotLoadedException extends Exception { + + public LocaleNotLoadedException(String locale) { + super(locale); + } +} diff --git a/client/src/main/java/com/vaadin/client/LocaleService.java b/client/src/main/java/com/vaadin/client/LocaleService.java new file mode 100644 index 0000000000..dcd1c9ea4e --- /dev/null +++ b/client/src/main/java/com/vaadin/client/LocaleService.java @@ -0,0 +1,154 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import com.vaadin.shared.ui.ui.UIState.LocaleData; + +/** + * Date / time etc. localisation service for all widgets. Caches all loaded + * locales as JSONObjects. + * + * @author Vaadin Ltd. + * + */ +public class LocaleService { + + private static Map cache = new HashMap(); + + private static String defaultLocale; + + public static void addLocale(LocaleData localeData) { + final String key = localeData.name; + if (cache.containsKey(key)) { + cache.remove(key); + } + getLogger().fine("Received locale data for " + localeData.name); + cache.put(key, localeData); + if (cache.size() == 1) { + setDefaultLocale(key); + } + } + + public static void setDefaultLocale(String locale) { + defaultLocale = locale; + } + + public static String getDefaultLocale() { + return defaultLocale; + } + + public static Set getAvailableLocales() { + return cache.keySet(); + } + + public static String[] getMonthNames(String locale) + throws LocaleNotLoadedException { + if (cache.containsKey(locale)) { + return cache.get(locale).monthNames; + } else { + throw new LocaleNotLoadedException(locale); + } + } + + public static String[] getShortMonthNames(String locale) + throws LocaleNotLoadedException { + if (cache.containsKey(locale)) { + return cache.get(locale).shortMonthNames; + } else { + throw new LocaleNotLoadedException(locale); + } + } + + public static String[] getDayNames(String locale) + throws LocaleNotLoadedException { + if (cache.containsKey(locale)) { + return cache.get(locale).dayNames; + } else { + throw new LocaleNotLoadedException(locale); + } + } + + public static String[] getShortDayNames(String locale) + throws LocaleNotLoadedException { + if (cache.containsKey(locale)) { + return cache.get(locale).shortDayNames; + } else { + throw new LocaleNotLoadedException(locale); + } + } + + public static int getFirstDayOfWeek(String locale) + throws LocaleNotLoadedException { + if (cache.containsKey(locale)) { + return cache.get(locale).firstDayOfWeek; + } else { + throw new LocaleNotLoadedException(locale); + } + } + + public static String getDateFormat(String locale) + throws LocaleNotLoadedException { + if (cache.containsKey(locale)) { + return cache.get(locale).dateFormat; + } else { + throw new LocaleNotLoadedException(locale); + } + } + + public static boolean isTwelveHourClock(String locale) + throws LocaleNotLoadedException { + if (cache.containsKey(locale)) { + return cache.get(locale).twelveHourClock; + } else { + throw new LocaleNotLoadedException(locale); + } + } + + public static String getClockDelimiter(String locale) + throws LocaleNotLoadedException { + if (cache.containsKey(locale)) { + return cache.get(locale).hourMinuteDelimiter; + } else { + throw new LocaleNotLoadedException(locale); + } + } + + public static String[] getAmPmStrings(String locale) + throws LocaleNotLoadedException { + if (cache.containsKey(locale)) { + return new String[] { cache.get(locale).am, cache.get(locale).pm }; + } else { + throw new LocaleNotLoadedException(locale); + } + } + + public static void addLocales(List localeDatas) { + for (LocaleData localeData : localeDatas) { + addLocale(localeData); + } + } + + private static Logger getLogger() { + return Logger.getLogger(LocaleService.class.getName()); + } +} diff --git a/client/src/main/java/com/vaadin/client/MeasuredSize.java b/client/src/main/java/com/vaadin/client/MeasuredSize.java new file mode 100644 index 0000000000..2099008350 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/MeasuredSize.java @@ -0,0 +1,299 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import java.util.logging.Logger; + +import com.google.gwt.core.client.JsArrayString; +import com.google.gwt.dom.client.Element; + +public class MeasuredSize { + private final static boolean debugSizeChanges = false; + + public static class MeasureResult { + private final boolean widthChanged; + private final boolean heightChanged; + + private MeasureResult(boolean widthChanged, boolean heightChanged) { + this.widthChanged = widthChanged; + this.heightChanged = heightChanged; + } + + public boolean isHeightChanged() { + return heightChanged; + } + + public boolean isWidthChanged() { + return widthChanged; + } + + public boolean isChanged() { + return heightChanged || widthChanged; + } + } + + private double width = -1; + private double height = -1; + + private int[] paddings = new int[4]; + private int[] borders = new int[4]; + private int[] margins = new int[4]; + + private FastStringSet dependents = FastStringSet.create(); + + public double getOuterHeight() { + return height; + } + + public double getOuterWidth() { + return width; + } + + public void addDependent(String pid) { + dependents.add(pid); + } + + public void removeDependent(String pid) { + dependents.remove(pid); + } + + public boolean hasDependents() { + return !dependents.isEmpty(); + } + + public JsArrayString getDependents() { + return dependents.dump(); + } + + private static int sumWidths(int[] sizes) { + return sizes[1] + sizes[3]; + } + + private static int sumHeights(int[] sizes) { + return sizes[0] + sizes[2]; + } + + public double getInnerHeight() { + return height - sumHeights(margins) - sumHeights(borders) + - sumHeights(paddings); + } + + public double getInnerWidth() { + return width - sumWidths(margins) - sumWidths(borders) + - sumWidths(paddings); + } + + public boolean setOuterHeight(double height) { + if (this.height != height) { + this.height = height; + return true; + } else { + return false; + } + } + + public boolean setOuterWidth(double width) { + if (this.width != width) { + this.width = width; + return true; + } else { + return false; + } + } + + public int getBorderHeight() { + return sumHeights(borders); + } + + public int getBorderWidth() { + return sumWidths(borders); + } + + public int getPaddingHeight() { + return sumHeights(paddings); + } + + public int getPaddingWidth() { + return sumWidths(paddings); + } + + public int getMarginHeight() { + return sumHeights(margins); + } + + public int getMarginWidth() { + return sumWidths(margins); + } + + public int getMarginTop() { + return margins[0]; + } + + public int getMarginRight() { + return margins[1]; + } + + public int getMarginBottom() { + return margins[2]; + } + + public int getMarginLeft() { + return margins[3]; + } + + public int getBorderTop() { + return borders[0]; + } + + public int getBorderRight() { + return borders[1]; + } + + public int getBorderBottom() { + return borders[2]; + } + + public int getBorderLeft() { + return borders[3]; + } + + public int getPaddingTop() { + return paddings[0]; + } + + public int getPaddingRight() { + return paddings[1]; + } + + public int getPaddingBottom() { + return paddings[2]; + } + + public int getPaddingLeft() { + return paddings[3]; + } + + public MeasureResult measure(Element element) { + Profiler.enter("MeasuredSize.measure"); + boolean heightChanged = false; + boolean widthChanged = false; + + Profiler.enter("new ComputedStyle"); + ComputedStyle computedStyle = new ComputedStyle(element); + int[] paddings = computedStyle.getPadding(); + // Some browsers do not reflow until accessing data from the computed + // style object + Profiler.leave("new ComputedStyle"); + + Profiler.enter("Measure paddings"); + if (!heightChanged && hasHeightChanged(this.paddings, paddings)) { + debugSizeChange(element, "Height (padding)", this.paddings, + paddings); + heightChanged = true; + } + if (!widthChanged && hasWidthChanged(this.paddings, paddings)) { + debugSizeChange(element, "Width (padding)", this.paddings, paddings); + widthChanged = true; + } + this.paddings = paddings; + Profiler.leave("Measure paddings"); + + Profiler.enter("Measure margins"); + int[] margins = computedStyle.getMargin(); + if (!heightChanged && hasHeightChanged(this.margins, margins)) { + debugSizeChange(element, "Height (margins)", this.margins, margins); + heightChanged = true; + } + if (!widthChanged && hasWidthChanged(this.margins, margins)) { + debugSizeChange(element, "Width (margins)", this.margins, margins); + widthChanged = true; + } + this.margins = margins; + Profiler.leave("Measure margins"); + + Profiler.enter("Measure borders"); + int[] borders = computedStyle.getBorder(); + if (!heightChanged && hasHeightChanged(this.borders, borders)) { + debugSizeChange(element, "Height (borders)", this.borders, borders); + heightChanged = true; + } + if (!widthChanged && hasWidthChanged(this.borders, borders)) { + debugSizeChange(element, "Width (borders)", this.borders, borders); + widthChanged = true; + } + this.borders = borders; + Profiler.leave("Measure borders"); + + Profiler.enter("Measure height"); + double requiredHeight = WidgetUtil.getRequiredHeightDouble(element); + double outerHeight = requiredHeight + sumHeights(margins); + double oldHeight = height; + if (setOuterHeight(outerHeight)) { + debugSizeChange(element, "Height (outer)", oldHeight, height); + heightChanged = true; + } + Profiler.leave("Measure height"); + + Profiler.enter("Measure width"); + double requiredWidth = WidgetUtil.getRequiredWidthDouble(element); + double outerWidth = requiredWidth + sumWidths(margins); + double oldWidth = width; + if (setOuterWidth(outerWidth)) { + debugSizeChange(element, "Width (outer)", oldWidth, width); + widthChanged = true; + } + Profiler.leave("Measure width"); + + Profiler.leave("MeasuredSize.measure"); + + return new MeasureResult(widthChanged, heightChanged); + } + + private void debugSizeChange(Element element, String sizeChangeType, + int[] changedFrom, int[] changedTo) { + debugSizeChange(element, sizeChangeType, + java.util.Arrays.asList(changedFrom).toString(), + java.util.Arrays.asList(changedTo).toString()); + } + + private void debugSizeChange(Element element, String sizeChangeType, + double changedFrom, double changedTo) { + debugSizeChange(element, sizeChangeType, String.valueOf(changedFrom), + String.valueOf(changedTo)); + } + + private void debugSizeChange(Element element, String sizeChangeType, + String changedFrom, String changedTo) { + if (debugSizeChanges) { + getLogger() + .info(sizeChangeType + " has changed from " + changedFrom + + " to " + changedTo + " for " + element.toString()); + } + } + + private static boolean hasWidthChanged(int[] sizes1, int[] sizes2) { + return sizes1[1] != sizes2[1] || sizes1[3] != sizes2[3]; + } + + private static boolean hasHeightChanged(int[] sizes1, int[] sizes2) { + return sizes1[0] != sizes2[0] || sizes1[2] != sizes2[2]; + } + + private static Logger getLogger() { + return Logger.getLogger(MeasuredSize.class.getName()); + } + +} diff --git a/client/src/main/java/com/vaadin/client/MouseEventDetailsBuilder.java b/client/src/main/java/com/vaadin/client/MouseEventDetailsBuilder.java new file mode 100644 index 0000000000..11ebe3925c --- /dev/null +++ b/client/src/main/java/com/vaadin/client/MouseEventDetailsBuilder.java @@ -0,0 +1,96 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.user.client.Event; +import com.vaadin.shared.MouseEventDetails; +import com.vaadin.shared.MouseEventDetails.MouseButton; + +/** + * Helper class for constructing a MouseEventDetails object from a + * {@link NativeEvent}. + * + * @author Vaadin Ltd + * @since 7.0.0 + * + */ +public class MouseEventDetailsBuilder { + + /** + * Construct a {@link MouseEventDetails} object from the given event + * + * @param evt + * The event to use as a source for the details + * @return a MouseEventDetails containing information from the event + */ + public static MouseEventDetails buildMouseEventDetails(NativeEvent evt) { + return buildMouseEventDetails(evt, null); + } + + /** + * Construct a {@link MouseEventDetails} object from the given event + * + * @param evt + * The event to use as a source for the details + * @param relativeToElement + * The element whose position + * {@link MouseEventDetails#getRelativeX()} and + * {@link MouseEventDetails#getRelativeY()} are relative to. + * @return a MouseEventDetails containing information from the event + */ + public static MouseEventDetails buildMouseEventDetails(NativeEvent evt, + Element relativeToElement) { + MouseEventDetails mouseEventDetails = new MouseEventDetails(); + mouseEventDetails.setType(Event.getTypeInt(evt.getType())); + mouseEventDetails.setClientX(WidgetUtil.getTouchOrMouseClientX(evt)); + mouseEventDetails.setClientY(WidgetUtil.getTouchOrMouseClientY(evt)); + if (evt.getButton() == NativeEvent.BUTTON_LEFT) { + mouseEventDetails.setButton(MouseButton.LEFT); + } else if (evt.getButton() == NativeEvent.BUTTON_RIGHT) { + mouseEventDetails.setButton(MouseButton.RIGHT); + } else if (evt.getButton() == NativeEvent.BUTTON_MIDDLE) { + mouseEventDetails.setButton(MouseButton.MIDDLE); + } else { + // IE8 does not always report a button. Assume left. + mouseEventDetails.setButton(MouseButton.LEFT); + } + mouseEventDetails.setAltKey(evt.getAltKey()); + mouseEventDetails.setCtrlKey(evt.getCtrlKey()); + mouseEventDetails.setMetaKey(evt.getMetaKey()); + mouseEventDetails.setShiftKey(evt.getShiftKey()); + if (relativeToElement != null) { + mouseEventDetails.setRelativeX(getRelativeX( + mouseEventDetails.getClientX(), relativeToElement)); + mouseEventDetails.setRelativeY(getRelativeY( + mouseEventDetails.getClientY(), relativeToElement)); + } + return mouseEventDetails; + + } + + private static int getRelativeX(int clientX, Element target) { + return clientX - target.getAbsoluteLeft() + target.getScrollLeft() + + target.getOwnerDocument().getScrollLeft(); + } + + private static int getRelativeY(int clientY, Element target) { + return clientY - target.getAbsoluteTop() + target.getScrollTop() + + target.getOwnerDocument().getScrollTop(); + } + +} diff --git a/client/src/main/java/com/vaadin/client/Paintable.java b/client/src/main/java/com/vaadin/client/Paintable.java new file mode 100644 index 0000000000..34f2d0714f --- /dev/null +++ b/client/src/main/java/com/vaadin/client/Paintable.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client; + +/** + * An interface used by client-side widgets or paintable parts to receive + * updates from the corresponding server-side components in the form of + * {@link UIDL}. + * + * Updates can be sent back to the server using the + * {@link ApplicationConnection#updateVariable()} methods. + */ +@Deprecated +public interface Paintable { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client); +} diff --git a/client/src/main/java/com/vaadin/client/Profiler.java b/client/src/main/java/com/vaadin/client/Profiler.java new file mode 100644 index 0000000000..3923b66218 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/Profiler.java @@ -0,0 +1,719 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import com.google.gwt.core.client.Duration; +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; + +/** + * Lightweight profiling tool that can be used to collect profiling data with + * zero overhead unless enabled. To enable profiling, add + * <set-property name="vaadin.profiler" value="true" /> to + * your .gwt.xml file. + * + * @author Vaadin Ltd + * @since 7.0.0 + */ +public class Profiler { + + private static RelativeTimeSupplier RELATIVE_TIME_SUPPLIER; + + private static final String evtGroup = "VaadinProfiler"; + + private static ProfilerResultConsumer consumer; + + /** + * Class to include using deferred binding to enable the profiling. + * + * @author Vaadin Ltd + * @since 7.0.0 + */ + public static class EnabledProfiler extends Profiler { + + @Override + protected boolean isImplEnabled() { + return true; + } + } + + /** + * Interface for getting data from the {@link Profiler}. + *

+ * Warning! This interface is most likely to change in the future + * + * @since 7.1 + * @author Vaadin Ltd + */ + public interface ProfilerResultConsumer { + public void addProfilerData(Node rootNode, List totals); + + public void addBootstrapData(LinkedHashMap timings); + } + + /** + * A hierarchical representation of the time spent running a named block of + * code. + *

+ * Warning! This class is most likely to change in the future and is + * therefore defined in this class in an internal package instead of + * Profiler where it might seem more logical. + */ + public static class Node { + private final String name; + private final LinkedHashMap children = new LinkedHashMap(); + private double time = 0; + private int count = 0; + private double enterTime = 0; + private double minTime = 1000000000; + private double maxTime = 0; + + /** + * Create a new node with the given name. + * + * @param name + */ + public Node(String name) { + this.name = name; + } + + /** + * Gets the name of the node + * + * @return the name of the node + */ + public String getName() { + return name; + } + + /** + * Creates a new child node or retrieves and existing child and updates + * its total time and hit count. + * + * @param name + * the name of the child + * @param timestamp + * the timestamp for when the node is entered + * @return the child node object + */ + public Node enterChild(String name, double timestamp) { + Node child = children.get(name); + if (child == null) { + child = new Node(name); + children.put(name, child); + } + child.enterTime = timestamp; + child.count++; + return child; + } + + /** + * Gets the total time spent in this node, including time spent in sub + * nodes + * + * @return the total time spent, in milliseconds + */ + public double getTimeSpent() { + return time; + } + + /** + * Gets the minimum time spent for one invocation of this node, + * including time spent in sub nodes + * + * @return the time spent for the fastest invocation, in milliseconds + */ + public double getMinTimeSpent() { + return minTime; + } + + /** + * Gets the maximum time spent for one invocation of this node, + * including time spent in sub nodes + * + * @return the time spent for the slowest invocation, in milliseconds + */ + public double getMaxTimeSpent() { + return maxTime; + } + + /** + * Gets the number of times this node has been entered + * + * @return the number of times the node has been entered + */ + public int getCount() { + return count; + } + + /** + * Gets the total time spent in this node, excluding time spent in sub + * nodes + * + * @return the total time spent, in milliseconds + */ + public double getOwnTime() { + double time = getTimeSpent(); + for (Node node : children.values()) { + time -= node.getTimeSpent(); + } + return time; + } + + /** + * Gets the child nodes of this node + * + * @return a collection of child nodes + */ + public Collection getChildren() { + return Collections.unmodifiableCollection(children.values()); + } + + private void buildRecursiveString(StringBuilder builder, String prefix) { + if (getName() != null) { + String msg = getStringRepresentation(prefix); + builder.append(msg + '\n'); + } + String childPrefix = prefix + "*"; + for (Node node : children.values()) { + node.buildRecursiveString(builder, childPrefix); + } + } + + @Override + public String toString() { + return getStringRepresentation(""); + } + + public String getStringRepresentation(String prefix) { + if (getName() == null) { + return ""; + } + String msg = prefix + " " + getName() + " in " + + roundToSignificantFigures(getTimeSpent()) + " ms."; + if (getCount() > 1) { + msg += " Invoked " + + getCount() + + " times (" + + roundToSignificantFigures(getTimeSpent() / getCount()) + + " ms per time, min " + + roundToSignificantFigures(getMinTimeSpent()) + + " ms, max " + + roundToSignificantFigures(getMaxTimeSpent()) + + " ms)."; + } + if (!children.isEmpty()) { + double ownTime = getOwnTime(); + msg += " " + roundToSignificantFigures(ownTime) + + " ms spent in own code"; + if (getCount() > 1) { + msg += " (" + + roundToSignificantFigures(ownTime / getCount()) + + " ms per time)"; + } + msg += '.'; + } + return msg; + } + + private static double roundToSignificantFigures(double num) { + // Number of significant digits + int n = 3; + if (num == 0) { + return 0; + } + + final double d = Math.ceil(Math.log10(num < 0 ? -num : num)); + final int power = n - (int) d; + + final double magnitude = Math.pow(10, power); + final long shifted = Math.round(num * magnitude); + return shifted / magnitude; + } + + public void sumUpTotals(Map totals) { + String name = getName(); + if (name != null) { + Node totalNode = totals.get(name); + if (totalNode == null) { + totalNode = new Node(name); + totals.put(name, totalNode); + } + + totalNode.time += getOwnTime(); + totalNode.count += getCount(); + totalNode.minTime = roundToSignificantFigures(Math.min( + totalNode.minTime, getMinTimeSpent())); + totalNode.maxTime = roundToSignificantFigures(Math.max( + totalNode.maxTime, getMaxTimeSpent())); + } + for (Node node : children.values()) { + node.sumUpTotals(totals); + } + } + + /** + * @param timestamp + */ + public void leave(double timestamp) { + double elapsed = (timestamp - enterTime); + time += elapsed; + enterTime = 0; + if (elapsed < minTime) { + minTime = elapsed; + } + if (elapsed > maxTime) { + maxTime = elapsed; + } + } + } + + private static final class GwtStatsEvent extends JavaScriptObject { + protected GwtStatsEvent() { + // JSO constructor + } + + private native String getEvtGroup() + /*-{ + return this.evtGroup; + }-*/; + + private native double getMillis() + /*-{ + return this.millis; + }-*/; + + private native String getSubSystem() + /*-{ + return this.subSystem; + }-*/; + + private native String getType() + /*-{ + return this.type; + }-*/; + + private native String getModuleName() + /*-{ + return this.moduleName; + }-*/; + + private native double getRelativeMillis() + /*-{ + return this.relativeMillis; + }-*/; + + private native boolean isExtendedEvent() + /*-{ + return 'relativeMillis' in this; + }-*/; + + public final String getEventName() { + String group = getEvtGroup(); + if (evtGroup.equals(group)) { + return getSubSystem(); + } else { + return group + "." + getSubSystem(); + } + } + } + + /** + * Checks whether the profiling gathering is enabled. + * + * @return true if the profiling is enabled, else + * false + */ + public static boolean isEnabled() { + // This will be fully inlined by the compiler + Profiler create = GWT.create(Profiler.class); + return create.isImplEnabled(); + } + + /** + * Enters a named block. There should always be a matching invocation of + * {@link #leave(String)} when leaving the block. Calls to this method will + * be removed by the compiler unless profiling is enabled. + * + * @param name + * the name of the entered block + */ + public static void enter(String name) { + if (isEnabled()) { + logGwtEvent(name, "begin"); + } + } + + /** + * Leaves a named block. There should always be a matching invocation of + * {@link #enter(String)} when entering the block. Calls to this method will + * be removed by the compiler unless profiling is enabled. + * + * @param name + * the name of the left block + */ + public static void leave(String name) { + if (isEnabled()) { + logGwtEvent(name, "end"); + } + } + + /** + * Returns time relative to the particular page load time. The value should + * not be used directly but rather difference between two values returned by + * this method should be used to compare measurements. + * + * @since 7.6 + */ + public static double getRelativeTimeMillis() { + return RELATIVE_TIME_SUPPLIER.getRelativeTime(); + } + + private static native final void logGwtEvent(String name, String type) + /*-{ + $wnd.__gwtStatsEvent({ + evtGroup: @com.vaadin.client.Profiler::evtGroup, + moduleName: @com.google.gwt.core.client.GWT::getModuleName()(), + millis: (new Date).getTime(), + sessionId: undefined, + subSystem: name, + type: type, + relativeMillis: @com.vaadin.client.Profiler::getRelativeTimeMillis()() + }); + }-*/; + + /** + * Resets the collected profiler data. Calls to this method will be removed + * by the compiler unless profiling is enabled. + */ + public static void reset() { + if (isEnabled()) { + /* + * Old implementations might call reset for initialization, so + * ensure it is initialized here as well. Initialization has no side + * effects if already done. + */ + initialize(); + + clearEventsList(); + } + } + + /** + * Initializes the profiler. This should be done before calling any other + * function in this class. Failing to do so might cause undesired behavior. + * This method has no side effects if the initialization has already been + * done. + *

+ * Please note that this method should be called even if the profiler is not + * enabled because it will then remove a logger function that might have + * been included in the HTML page and that would leak memory unless removed. + *

+ * + * @since 7.0.2 + */ + public static void initialize() { + if (hasHighPrecisionTime()) { + RELATIVE_TIME_SUPPLIER = new HighResolutionTimeSupplier(); + } else { + RELATIVE_TIME_SUPPLIER = new DefaultRelativeTimeSupplier(); + } + if (isEnabled()) { + ensureLogger(); + } else { + ensureNoLogger(); + } + } + + /** + * Outputs the gathered profiling data to the debug console. + */ + public static void logTimings() { + if (!isEnabled()) { + getLogger().warning( + "Profiler is not enabled, no data has been collected."); + return; + } + + LinkedList stack = new LinkedList(); + Node rootNode = new Node(null); + stack.add(rootNode); + JsArray gwtStatsEvents = getGwtStatsEvents(); + if (gwtStatsEvents.length() == 0) { + getLogger() + .warning( + "No profiling events recorded, this might happen if another __gwtStatsEvent handler is installed."); + return; + } + + Set extendedTimeNodes = new HashSet(); + for (int i = 0; i < gwtStatsEvents.length(); i++) { + GwtStatsEvent gwtStatsEvent = gwtStatsEvents.get(i); + String eventName = gwtStatsEvent.getEventName(); + String type = gwtStatsEvent.getType(); + boolean isExtendedEvent = gwtStatsEvent.isExtendedEvent(); + boolean isBeginEvent = "begin".equals(type); + + Node stackTop = stack.getLast(); + boolean inEvent = eventName.equals(stackTop.getName()) + && !isBeginEvent; + + if (!inEvent && stack.size() >= 2 + && eventName.equals(stack.get(stack.size() - 2).getName()) + && !isBeginEvent) { + // back out of sub event + if (extendedTimeNodes.contains(stackTop) && isExtendedEvent) { + stackTop.leave(gwtStatsEvent.getRelativeMillis()); + } else { + stackTop.leave(gwtStatsEvent.getMillis()); + } + stack.removeLast(); + stackTop = stack.getLast(); + + inEvent = true; + } + + if (type.equals("end")) { + if (!inEvent) { + getLogger().severe( + "Got end event for " + eventName + + " but is currently in " + + stackTop.getName()); + return; + } + Node previousStackTop = stack.removeLast(); + if (extendedTimeNodes.contains(previousStackTop)) { + previousStackTop.leave(gwtStatsEvent.getRelativeMillis()); + } else { + previousStackTop.leave(gwtStatsEvent.getMillis()); + } + } else { + double millis = isExtendedEvent ? gwtStatsEvent + .getRelativeMillis() : gwtStatsEvent.getMillis(); + if (!inEvent) { + stackTop = stackTop.enterChild(eventName, millis); + stack.add(stackTop); + if (isExtendedEvent) { + extendedTimeNodes.add(stackTop); + } + } + if (!isBeginEvent) { + // Create sub event + Node subNode = stackTop.enterChild(eventName + "." + type, + millis); + if (isExtendedEvent) { + extendedTimeNodes.add(subNode); + } + stack.add(subNode); + } + } + } + + if (stack.size() != 1) { + getLogger().warning( + "Not all nodes are left, the last node is " + + stack.getLast().getName()); + return; + } + + Map totals = new HashMap(); + rootNode.sumUpTotals(totals); + + ArrayList totalList = new ArrayList(totals.values()); + Collections.sort(totalList, new Comparator() { + @Override + public int compare(Node o1, Node o2) { + return (int) (o2.getTimeSpent() - o1.getTimeSpent()); + } + }); + + if (getConsumer() != null) { + getConsumer().addProfilerData(stack.getFirst(), totalList); + } + } + + /** + * Overridden in {@link EnabledProfiler} to make {@link #isEnabled()} return + * true if GWT.create returns that class. + * + * @return true if the profiling is enabled, else + * false + */ + protected boolean isImplEnabled() { + return false; + } + + /** + * Outputs the time passed since various events recored in + * performance.timing if supported by the browser. + */ + public static void logBootstrapTimings() { + if (isEnabled()) { + double now = Duration.currentTimeMillis(); + + String[] keys = new String[] { "navigationStart", + "unloadEventStart", "unloadEventEnd", "redirectStart", + "redirectEnd", "fetchStart", "domainLookupStart", + "domainLookupEnd", "connectStart", "connectEnd", + "requestStart", "responseStart", "responseEnd", + "domLoading", "domInteractive", + "domContentLoadedEventStart", "domContentLoadedEventEnd", + "domComplete", "loadEventStart", "loadEventEnd" }; + + LinkedHashMap timings = new LinkedHashMap(); + + for (String key : keys) { + double value = getPerformanceTiming(key); + if (value == 0) { + // Ignore missing value + continue; + } + timings.put(key, Double.valueOf(now - value)); + } + + if (timings.isEmpty()) { + getLogger() + .info("Bootstrap timings not supported, please ensure your browser supports performance.timing"); + return; + } + + if (getConsumer() != null) { + getConsumer().addBootstrapData(timings); + } + } + } + + private static final native double getPerformanceTiming(String name) + /*-{ + if ($wnd.performance && $wnd.performance.timing && $wnd.performance.timing[name]) { + return $wnd.performance.timing[name]; + } else { + return 0; + } + }-*/; + + private static native JsArray getGwtStatsEvents() + /*-{ + return $wnd.vaadin.gwtStatsEvents || []; + }-*/; + + /** + * Add logger if it's not already there, also initializing the event array + * if needed. + */ + private static native void ensureLogger() + /*-{ + if (typeof $wnd.__gwtStatsEvent != 'function') { + if (typeof $wnd.vaadin.gwtStatsEvents != 'object') { + $wnd.vaadin.gwtStatsEvents = []; + } + $wnd.__gwtStatsEvent = function(event) { + $wnd.vaadin.gwtStatsEvents.push(event); + return true; + } + } + }-*/; + + /** + * Remove logger function and event array if it seems like the function has + * been added by us. + */ + private static native void ensureNoLogger() + /*-{ + if (typeof $wnd.vaadin.gwtStatsEvents == 'object') { + delete $wnd.vaadin.gwtStatsEvents; + if (typeof $wnd.__gwtStatsEvent == 'function') { + $wnd.__gwtStatsEvent = function() { return true; }; + } + } + }-*/; + + private static native JsArray clearEventsList() + /*-{ + $wnd.vaadin.gwtStatsEvents = []; + }-*/; + + /** + * Sets the profiler result consumer that is used to output the profiler + * data to the user. + *

+ * Warning! This is internal API and should not be used by + * applications or add-ons. + * + * @since 7.1.4 + * @param profilerResultConsumer + * the consumer that gets profiler data + */ + public static void setProfilerResultConsumer( + ProfilerResultConsumer profilerResultConsumer) { + if (consumer != null) { + throw new IllegalStateException("The consumer has already been set"); + } + consumer = profilerResultConsumer; + } + + private static ProfilerResultConsumer getConsumer() { + return consumer; + } + + private static Logger getLogger() { + return Logger.getLogger(Profiler.class.getName()); + } + + private static native boolean hasHighPrecisionTime() + /*-{ + return $wnd.performance && (typeof $wnd.performance.now == 'function'); + }-*/; + + private interface RelativeTimeSupplier { + double getRelativeTime(); + } + + private static class DefaultRelativeTimeSupplier implements + RelativeTimeSupplier { + + @Override + public native double getRelativeTime() + /*-{ + return (new Date).getTime(); + }-*/; + } + + private static class HighResolutionTimeSupplier implements + RelativeTimeSupplier { + + @Override + public native double getRelativeTime() + /*-{ + return $wnd.performance.now(); + }-*/; + } +} diff --git a/client/src/main/java/com/vaadin/client/RenderInformation.java b/client/src/main/java/com/vaadin/client/RenderInformation.java new file mode 100644 index 0000000000..8fd3fc7e0b --- /dev/null +++ b/client/src/main/java/com/vaadin/client/RenderInformation.java @@ -0,0 +1,165 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.DOM; + +/** + * Contains size information about a rendered container and its content area. + * + * @author Artur Signell + * + */ +public class RenderInformation { + + private RenderSpace contentArea = new RenderSpace(); + private Size renderedSize = new Size(-1, -1); + + public void setContentAreaWidth(int w) { + contentArea.setWidth(w); + } + + public void setContentAreaHeight(int h) { + contentArea.setHeight(h); + } + + public RenderSpace getContentAreaSize() { + return contentArea; + + } + + public Size getRenderedSize() { + return renderedSize; + } + + /** + * Update the size of the widget. + * + * @param widget + * + * @return true if the size has changed since last update + * @deprecated As of 7.2, call and override {@link #updateSize(Element)} + * instead + */ + @Deprecated + public boolean updateSize(com.google.gwt.user.client.Element element) { + Size newSize = new Size(element.getOffsetWidth(), + element.getOffsetHeight()); + if (newSize.equals(renderedSize)) { + return false; + } else { + renderedSize = newSize; + return true; + } + } + + /** + * Update the size of the widget. + * + * @param widget + * + * @return true if the size has changed since last update + * + * @since 7.2 + */ + public boolean updateSize(Element element) { + return updateSize(DOM.asOld(element)); + } + + @Override + public String toString() { + return "RenderInformation [contentArea=" + contentArea + + ",renderedSize=" + renderedSize + "]"; + + } + + public static class FloatSize { + + private float width, height; + + public FloatSize(float width, float height) { + this.width = width; + this.height = height; + } + + public float getWidth() { + return width; + } + + public void setWidth(float width) { + this.width = width; + } + + public float getHeight() { + return height; + } + + public void setHeight(float height) { + this.height = height; + } + + } + + public static class Size { + + private int width, height; + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Size)) { + return false; + } + Size other = (Size) obj; + return other.width == width && other.height == height; + } + + @Override + public int hashCode() { + return (width << 8) | height; + } + + public Size() { + } + + public Size(int width, int height) { + this.height = height; + this.width = width; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + @Override + public String toString() { + return "Size [width=" + width + ",height=" + height + "]"; + } + } + +} diff --git a/client/src/main/java/com/vaadin/client/RenderSpace.java b/client/src/main/java/com/vaadin/client/RenderSpace.java new file mode 100644 index 0000000000..dff774aa6f --- /dev/null +++ b/client/src/main/java/com/vaadin/client/RenderSpace.java @@ -0,0 +1,68 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import com.vaadin.client.RenderInformation.Size; + +/** + * Contains information about render area. + */ +public class RenderSpace extends Size { + + private int scrollBarSize = 0; + + public RenderSpace(int width, int height) { + super(width, height); + } + + public RenderSpace() { + } + + public RenderSpace(int width, int height, boolean useNativeScrollbarSize) { + super(width, height); + if (useNativeScrollbarSize) { + scrollBarSize = WidgetUtil.getNativeScrollbarSize(); + } + } + + /** + * Returns pixels available vertically for contained widget, including + * possible scrollbars. + */ + @Override + public int getHeight() { + return super.getHeight(); + } + + /** + * Returns pixels available horizontally for contained widget, including + * possible scrollbars. + */ + @Override + public int getWidth() { + return super.getWidth(); + } + + /** + * In case containing block has oveflow: auto, this method must return + * number of pixels used by scrollbar. Returning zero means either that no + * scrollbar will be visible. + */ + public int getScrollbarSize() { + return scrollBarSize; + } + +} diff --git a/client/src/main/java/com/vaadin/client/ResourceLoader.java b/client/src/main/java/com/vaadin/client/ResourceLoader.java new file mode 100644 index 0000000000..559768d09c --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ResourceLoader.java @@ -0,0 +1,612 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.core.client.Duration; +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.RepeatingCommand; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.LinkElement; +import com.google.gwt.dom.client.NodeList; +import com.google.gwt.dom.client.ObjectElement; +import com.google.gwt.dom.client.ScriptElement; +import com.google.gwt.user.client.Timer; + +/** + * ResourceLoader lets you dynamically include external scripts and styles on + * the page and lets you know when the resource has been loaded. + * + * You can also preload resources, allowing them to get cached by the browser + * without being evaluated. This enables downloading multiple resources at once + * while still controlling in which order e.g. scripts are executed. + * + * @author Vaadin Ltd + * @since 7.0.0 + */ +public class ResourceLoader { + /** + * Event fired when a resource has been loaded. + */ + public static class ResourceLoadEvent { + private final ResourceLoader loader; + private final String resourceUrl; + private final boolean preload; + + /** + * Creates a new event. + * + * @param loader + * the resource loader that has loaded the resource + * @param resourceUrl + * the url of the loaded resource + * @param preload + * true if the resource has only been preloaded, false if + * it's fully loaded + */ + public ResourceLoadEvent(ResourceLoader loader, String resourceUrl, + boolean preload) { + this.loader = loader; + this.resourceUrl = resourceUrl; + this.preload = preload; + } + + /** + * Gets the resource loader that has fired this event + * + * @return the resource loader + */ + public ResourceLoader getResourceLoader() { + return loader; + } + + /** + * Gets the absolute url of the loaded resource. + * + * @return the absolute url of the loaded resource + */ + public String getResourceUrl() { + return resourceUrl; + } + + /** + * Returns true if the resource has been preloaded, false if it's fully + * loaded + * + * @see ResourceLoader#preloadResource(String, ResourceLoadListener) + * + * @return true if the resource has been preloaded, false if it's fully + * loaded + */ + public boolean isPreload() { + return preload; + } + } + + /** + * Event listener that gets notified when a resource has been loaded + */ + public interface ResourceLoadListener { + /** + * Notifies this ResourceLoadListener that a resource has been loaded. + * Some browsers do not support any way of detecting load errors. In + * these cases, onLoad will be called regardless of the status. + * + * @see ResourceLoadEvent + * + * @param event + * a resource load event with information about the loaded + * resource + */ + public void onLoad(ResourceLoadEvent event); + + /** + * Notifies this ResourceLoadListener that a resource could not be + * loaded, e.g. because the file could not be found or because the + * server did not respond. Some browsers do not support any way of + * detecting load errors. In these cases, onLoad will be called + * regardless of the status. + * + * @see ResourceLoadEvent + * + * @param event + * a resource load event with information about the resource + * that could not be loaded. + */ + public void onError(ResourceLoadEvent event); + } + + private static final ResourceLoader INSTANCE = GWT + .create(ResourceLoader.class); + + private ApplicationConnection connection; + + private final Set loadedResources = new HashSet(); + private final Set preloadedResources = new HashSet(); + + private final Map> loadListeners = new HashMap>(); + private final Map> preloadListeners = new HashMap>(); + + private final Element head; + + /** + * Creates a new resource loader. You should generally not create you own + * resource loader, but instead use {@link ResourceLoader#get()} to get an + * instance. + */ + protected ResourceLoader() { + Document document = Document.get(); + head = document.getElementsByTagName("head").getItem(0); + + // detect already loaded scripts and stylesheets + NodeList scripts = document.getElementsByTagName("script"); + for (int i = 0; i < scripts.getLength(); i++) { + ScriptElement element = ScriptElement.as(scripts.getItem(i)); + String src = element.getSrc(); + if (src != null && src.length() != 0) { + loadedResources.add(src); + } + } + + NodeList links = document.getElementsByTagName("link"); + for (int i = 0; i < links.getLength(); i++) { + LinkElement linkElement = LinkElement.as(links.getItem(i)); + String rel = linkElement.getRel(); + String href = linkElement.getHref(); + if ("stylesheet".equalsIgnoreCase(rel) && href != null + && href.length() != 0) { + loadedResources.add(href); + } + } + } + + /** + * Returns the default ResourceLoader + * + * @return the default ResourceLoader + */ + public static ResourceLoader get() { + return INSTANCE; + } + + /** + * Load a script and notify a listener when the script is loaded. Calling + * this method when the script is currently loading or already loaded + * doesn't cause the script to be loaded again, but the listener will still + * be notified when appropriate. + * + * + * @param scriptUrl + * the url of the script to load + * @param resourceLoadListener + * the listener that will get notified when the script is loaded + */ + public void loadScript(final String scriptUrl, + final ResourceLoadListener resourceLoadListener) { + loadScript(scriptUrl, resourceLoadListener, + !supportsInOrderScriptExecution()); + } + + /** + * Load a script and notify a listener when the script is loaded. Calling + * this method when the script is currently loading or already loaded + * doesn't cause the script to be loaded again, but the listener will still + * be notified when appropriate. + * + * + * @param scriptUrl + * url of script to load + * @param resourceLoadListener + * listener to notify when script is loaded + * @param async + * What mode the script.async attribute should be set to + * @since 7.2.4 + */ + public void loadScript(final String scriptUrl, + final ResourceLoadListener resourceLoadListener, boolean async) { + final String url = WidgetUtil.getAbsoluteUrl(scriptUrl); + ResourceLoadEvent event = new ResourceLoadEvent(this, url, false); + if (loadedResources.contains(url)) { + if (resourceLoadListener != null) { + resourceLoadListener.onLoad(event); + } + return; + } + + if (preloadListeners.containsKey(url)) { + // Preload going on, continue when preloaded + preloadResource(url, new ResourceLoadListener() { + @Override + public void onLoad(ResourceLoadEvent event) { + loadScript(url, resourceLoadListener); + } + + @Override + public void onError(ResourceLoadEvent event) { + // Preload failed -> signal error to own listener + if (resourceLoadListener != null) { + resourceLoadListener.onError(event); + } + } + }); + return; + } + + if (addListener(url, resourceLoadListener, loadListeners)) { + ScriptElement scriptTag = Document.get().createScriptElement(); + scriptTag.setSrc(url); + scriptTag.setType("text/javascript"); + + scriptTag.setPropertyBoolean("async", async); + + addOnloadHandler(scriptTag, new ResourceLoadListener() { + @Override + public void onLoad(ResourceLoadEvent event) { + fireLoad(event); + } + + @Override + public void onError(ResourceLoadEvent event) { + fireError(event); + } + }, event); + head.appendChild(scriptTag); + } + } + + /** + * The current browser supports script.async='false' for maintaining + * execution order for dynamically-added scripts. + * + * @return Browser supports script.async='false' + * @since 7.2.4 + */ + public static boolean supportsInOrderScriptExecution() { + return BrowserInfo.get().isIE11() || BrowserInfo.get().isEdge(); + } + + /** + * Download a resource and notify a listener when the resource is loaded + * without attempting to interpret the resource. When a resource has been + * preloaded, it will be present in the browser's cache (provided the HTTP + * headers allow caching), making a subsequent load operation complete + * without having to wait for the resource to be downloaded again. + * + * Calling this method when the resource is currently loading, currently + * preloading, already preloaded or already loaded doesn't cause the + * resource to be preloaded again, but the listener will still be notified + * when appropriate. + * + * @param url + * the url of the resource to preload + * @param resourceLoadListener + * the listener that will get notified when the resource is + * preloaded + */ + public void preloadResource(String url, + ResourceLoadListener resourceLoadListener) { + url = WidgetUtil.getAbsoluteUrl(url); + ResourceLoadEvent event = new ResourceLoadEvent(this, url, true); + if (loadedResources.contains(url) || preloadedResources.contains(url)) { + // Already loaded or preloaded -> just fire listener + if (resourceLoadListener != null) { + resourceLoadListener.onLoad(event); + } + return; + } + + if (addListener(url, resourceLoadListener, preloadListeners) + && !loadListeners.containsKey(url)) { + // Inject loader element if this is the first time this is preloaded + // AND the resources isn't already being loaded in the normal way + + final Element element = getPreloadElement(url); + addOnloadHandler(element, new ResourceLoadListener() { + @Override + public void onLoad(ResourceLoadEvent event) { + fireLoad(event); + Document.get().getBody().removeChild(element); + } + + @Override + public void onError(ResourceLoadEvent event) { + fireError(event); + Document.get().getBody().removeChild(element); + } + }, event); + + Document.get().getBody().appendChild(element); + } + } + + private static Element getPreloadElement(String url) { + /*- + * TODO + * In Chrome, FF: + * does not fire event if resource is 404 -> eternal spinner. + * always fires onerror -> no way to know if it loaded -> eternal spinner + * ", scriptStart); + scripts += html.substring(scriptStart + 1, j) + ";"; + nextPosToCheck = endOfPrevScript = j + "".length(); + scriptStart = lc.indexOf("", startOfBody) + 1; + final int endOfBody = lc.indexOf("", startOfBody); + if (endOfBody > startOfBody) { + res = html.substring(startOfBody, endOfBody); + } else { + res = html.substring(startOfBody); + } + } + + return res; + } + + /** Update caption for given widget */ + public void updateCaption(ComponentConnector paintable) { + Widget widget = paintable.getWidget(); + if (widget.getParent() != this) { + // Widget has not been added because the location was not found + return; + } + VCaptionWrapper wrapper = childWidgetToCaptionWrapper.get(widget); + if (VCaption.isNeeded(paintable.getState())) { + if (wrapper == null) { + // Add a wrapper between the layout and the child widget + final String loc = getLocation(widget); + super.remove(widget); + wrapper = new VCaptionWrapper(paintable, client); + super.add(wrapper, locationToElement.get(loc)); + childWidgetToCaptionWrapper.put(widget, wrapper); + } + wrapper.updateCaption(); + } else { + if (wrapper != null) { + // Remove the wrapper and add the widget directly to the layout + final String loc = getLocation(widget); + super.remove(wrapper); + super.add(widget, locationToElement.get(loc)); + childWidgetToCaptionWrapper.remove(widget); + } + } + } + + /** Get the location of an widget */ + public String getLocation(Widget w) { + for (final Iterator i = locationToWidget.keySet().iterator(); i + .hasNext();) { + final String location = i.next(); + if (locationToWidget.get(location) == w) { + return location; + } + } + return null; + } + + /** Removes given widget from the layout */ + @Override + public boolean remove(Widget w) { + final String location = getLocation(w); + if (location != null) { + locationToWidget.remove(location); + } + final VCaptionWrapper cw = childWidgetToCaptionWrapper.get(w); + if (cw != null) { + childWidgetToCaptionWrapper.remove(w); + return super.remove(cw); + } else if (w != null) { + return super.remove(w); + } + return false; + } + + /** Adding widget without specifying location is not supported */ + @Override + public void add(Widget w) { + throw new UnsupportedOperationException(); + } + + /** Clear all widgets from the layout */ + @Override + public void clear() { + super.clear(); + locationToWidget.clear(); + childWidgetToCaptionWrapper.clear(); + } + + /** + * This method is published to JS side with the same name into first DOM + * node of custom layout. This way if one implements some resizeable + * containers in custom layout he/she can notify children after resize. + */ + public void notifyChildrenOfSizeChange() { + client.runDescendentsLayout(this); + } + + @Override + public void onDetach() { + super.onDetach(); + if (elementWithNativeResizeFunction != null) { + detachResizedFunction(elementWithNativeResizeFunction); + } + } + + private native void detachResizedFunction(Element element) + /*-{ + element.notifyChildrenOfSizeChange = null; + }-*/; + + private native void publishResizedFunction(Element element) + /*-{ + var self = this; + element.notifyChildrenOfSizeChange = $entry(function() { + self.@com.vaadin.client.ui.VCustomLayout::notifyChildrenOfSizeChange()(); + }); + }-*/; + + /** + * In custom layout one may want to run layout functions made with + * JavaScript. This function tests if one exists (with name "iLayoutJS" in + * layouts first DOM node) and runs et. Return value is used to determine if + * children needs to be notified of size changes. + *

+ * Note! When implementing a JS layout function you most likely want to call + * notifyChildrenOfSizeChange() function on your custom layouts main + * element. That method is used to control whether child components layout + * functions are to be run. + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param el + * @return true if layout function exists and was run successfully, else + * false. + */ + public native boolean iLayoutJS(com.google.gwt.user.client.Element el) + /*-{ + if(el && el.iLayoutJS) { + try { + el.iLayoutJS(); + return true; + } catch (e) { + return false; + } + } else { + return false; + } + }-*/; + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (event.getTypeInt() == Event.ONLOAD) { + Util.notifyParentOfSizeChange(this, true); + event.cancelBubble(true); + } + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VDateField.java b/client/src/main/java/com/vaadin/client/ui/VDateField.java new file mode 100644 index 0000000000..b4084847dd --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VDateField.java @@ -0,0 +1,229 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.Date; + +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HasEnabled; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.DateTimeService; +import com.vaadin.shared.ui.datefield.Resolution; + +public class VDateField extends FlowPanel implements Field, HasEnabled { + + public static final String CLASSNAME = "v-datefield"; + + /** For internal use only. May be removed or replaced in the future. */ + public String paintableId; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean immediate; + + @Deprecated + public static final Resolution RESOLUTION_YEAR = Resolution.YEAR; + @Deprecated + public static final Resolution RESOLUTION_MONTH = Resolution.MONTH; + @Deprecated + public static final Resolution RESOLUTION_DAY = Resolution.DAY; + @Deprecated + public static final Resolution RESOLUTION_HOUR = Resolution.HOUR; + @Deprecated + public static final Resolution RESOLUTION_MIN = Resolution.MINUTE; + @Deprecated + public static final Resolution RESOLUTION_SEC = Resolution.SECOND; + + /** For internal use only. May be removed or replaced in the future. */ + public static String resolutionToString(Resolution res) { + if (res.getCalendarField() > Resolution.DAY.getCalendarField()) { + return "full"; + } + if (res == Resolution.DAY) { + return "day"; + } + if (res == Resolution.MONTH) { + return "month"; + } + return "year"; + } + + protected Resolution currentResolution = Resolution.YEAR; + + protected String currentLocale; + + protected boolean readonly; + + protected boolean enabled; + + /** + * The date that is selected in the date field. Null if an invalid date is + * specified. + */ + private Date date = null; + + /** For internal use only. May be removed or replaced in the future. */ + public DateTimeService dts; + + protected boolean showISOWeekNumbers = false; + + public VDateField() { + setStyleName(CLASSNAME); + dts = new DateTimeService(); + } + + /** + * We need this redundant native function because Java's Date object doesn't + * have a setMilliseconds method. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public static native double getTime(int y, int m, int d, int h, int mi, + int s, int ms) + /*-{ + try { + var date = new Date(2000,1,1,1); // don't use current date here + if(y && y >= 0) date.setFullYear(y); + if(m && m >= 1) date.setMonth(m-1); + if(d && d >= 0) date.setDate(d); + if(h >= 0) date.setHours(h); + if(mi >= 0) date.setMinutes(mi); + if(s >= 0) date.setSeconds(s); + if(ms >= 0) date.setMilliseconds(ms); + return date.getTime(); + } catch (e) { + // TODO print some error message on the console + //console.log(e); + return (new Date()).getTime(); + } + }-*/; + + public int getMilliseconds() { + return DateTimeService.getMilliseconds(date); + } + + public void setMilliseconds(int ms) { + DateTimeService.setMilliseconds(date, ms); + } + + public Resolution getCurrentResolution() { + return currentResolution; + } + + public void setCurrentResolution(Resolution currentResolution) { + this.currentResolution = currentResolution; + } + + public String getCurrentLocale() { + return currentLocale; + } + + public void setCurrentLocale(String currentLocale) { + this.currentLocale = currentLocale; + } + + public Date getCurrentDate() { + return date; + } + + public void setCurrentDate(Date date) { + this.date = date; + } + + public boolean isImmediate() { + return immediate; + } + + public void setImmediate(boolean immediate) { + this.immediate = immediate; + } + + public boolean isReadonly() { + return readonly; + } + + public void setReadonly(boolean readonly) { + this.readonly = readonly; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public DateTimeService getDateTimeService() { + return dts; + } + + public String getId() { + return paintableId; + } + + public ApplicationConnection getClient() { + return client; + } + + /** + * Returns whether ISO 8601 week numbers should be shown in the date + * selector or not. ISO 8601 defines that a week always starts with a Monday + * so the week numbers are only shown if this is the case. + * + * @return true if week number should be shown, false otherwise + */ + public boolean isShowISOWeekNumbers() { + return showISOWeekNumbers; + } + + public void setShowISOWeekNumbers(boolean showISOWeekNumbers) { + this.showISOWeekNumbers = showISOWeekNumbers; + } + + /** + * Returns a copy of the current date. Modifying the returned date will not + * modify the value of this VDateField. Use {@link #setDate(Date)} to change + * the current date. + *

+ * For internal use only. May be removed or replaced in the future. + * + * @return A copy of the current date + */ + public Date getDate() { + Date current = getCurrentDate(); + if (current == null) { + return null; + } else { + return (Date) getCurrentDate().clone(); + } + } + + /** + * Sets the current date for this VDateField. + * + * @param date + * The new date to use + */ + protected void setDate(Date date) { + this.date = date; + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VDateFieldCalendar.java b/client/src/main/java/com/vaadin/client/ui/VDateFieldCalendar.java new file mode 100644 index 0000000000..759ebef102 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VDateFieldCalendar.java @@ -0,0 +1,129 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.Date; + +import com.google.gwt.event.dom.client.DomEvent; +import com.vaadin.client.DateTimeService; +import com.vaadin.client.ui.VCalendarPanel.FocusOutListener; +import com.vaadin.client.ui.VCalendarPanel.SubmitListener; +import com.vaadin.shared.ui.datefield.Resolution; + +/** + * A client side implementation for InlineDateField + */ +public class VDateFieldCalendar extends VDateField { + + /** For internal use only. May be removed or replaced in the future. */ + public final VCalendarPanel calendarPanel; + + public VDateFieldCalendar() { + super(); + calendarPanel = new VCalendarPanel(); + calendarPanel.setParentField(this); + add(calendarPanel); + calendarPanel.setSubmitListener(new SubmitListener() { + @Override + public void onSubmit() { + updateValueFromPanel(); + } + + @Override + public void onCancel() { + // TODO Auto-generated method stub + + } + }); + calendarPanel.setFocusOutListener(new FocusOutListener() { + @Override + public boolean onFocusOut(DomEvent event) { + updateValueFromPanel(); + return false; + } + }); + } + + /** + * TODO refactor: almost same method as in VPopupCalendar.updateValue + *

+ * For internal use only. May be removed or replaced in the future. + */ + + @SuppressWarnings("deprecation") + public void updateValueFromPanel() { + + // If field is invisible at the beginning, client can still be null when + // this function is called. + if (getClient() == null) { + return; + } + + Date date2 = calendarPanel.getDate(); + Date currentDate = getCurrentDate(); + if (currentDate == null || date2.getTime() != currentDate.getTime()) { + setCurrentDate((Date) date2.clone()); + getClient().updateVariable(getId(), "year", date2.getYear() + 1900, + false); + if (getCurrentResolution().getCalendarField() > Resolution.YEAR + .getCalendarField()) { + getClient().updateVariable(getId(), "month", + date2.getMonth() + 1, false); + if (getCurrentResolution().getCalendarField() > Resolution.MONTH + .getCalendarField()) { + getClient().updateVariable(getId(), "day", date2.getDate(), + false); + if (getCurrentResolution().getCalendarField() > Resolution.DAY + .getCalendarField()) { + getClient().updateVariable(getId(), "hour", + date2.getHours(), false); + if (getCurrentResolution().getCalendarField() > Resolution.HOUR + .getCalendarField()) { + getClient().updateVariable(getId(), "min", + date2.getMinutes(), false); + if (getCurrentResolution().getCalendarField() > Resolution.MINUTE + .getCalendarField()) { + getClient().updateVariable(getId(), "sec", + date2.getSeconds(), false); + if (getCurrentResolution().getCalendarField() > Resolution.SECOND + .getCalendarField()) { + getClient().updateVariable( + getId(), + "msec", + DateTimeService + .getMilliseconds(date2), + false); + } + } + } + } + } + } + if (isImmediate()) { + getClient().sendPendingVariableChanges(); + } + } + } + + public void setTabIndex(int tabIndex) { + calendarPanel.getElement().setTabIndex(tabIndex); + } + + public int getTabIndex() { + return calendarPanel.getElement().getTabIndex(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VDragAndDropWrapper.java b/client/src/main/java/com/vaadin/client/ui/VDragAndDropWrapper.java new file mode 100644 index 0000000000..f3905f9e46 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VDragAndDropWrapper.java @@ -0,0 +1,730 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.JsArrayString; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.MouseDownEvent; +import com.google.gwt.event.dom.client.MouseDownHandler; +import com.google.gwt.event.dom.client.MouseUpEvent; +import com.google.gwt.event.dom.client.MouseUpHandler; +import com.google.gwt.event.dom.client.TouchStartEvent; +import com.google.gwt.event.dom.client.TouchStartHandler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.xhr.client.ReadyStateChangeHandler; +import com.google.gwt.xhr.client.XMLHttpRequest; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorMap; +import com.vaadin.client.LayoutManager; +import com.vaadin.client.MouseEventDetailsBuilder; +import com.vaadin.client.Util; +import com.vaadin.client.VConsole; +import com.vaadin.client.ValueMap; +import com.vaadin.client.ui.dd.DDUtil; +import com.vaadin.client.ui.dd.VAbstractDropHandler; +import com.vaadin.client.ui.dd.VAcceptCallback; +import com.vaadin.client.ui.dd.VDragAndDropManager; +import com.vaadin.client.ui.dd.VDragEvent; +import com.vaadin.client.ui.dd.VDropHandler; +import com.vaadin.client.ui.dd.VHasDropHandler; +import com.vaadin.client.ui.dd.VHtml5DragEvent; +import com.vaadin.client.ui.dd.VHtml5File; +import com.vaadin.client.ui.dd.VTransferable; +import com.vaadin.shared.ui.dd.HorizontalDropLocation; +import com.vaadin.shared.ui.dd.VerticalDropLocation; + +/** + * + * Must have features pending: + * + * drop details: locations + sizes in document hierarchy up to wrapper + * + */ +public class VDragAndDropWrapper extends VCustomComponent implements + VHasDropHandler { + + /** + * Minimum pixel delta is used to detect click from drag. #12838 + */ + private static final int MIN_PX_DELTA = 4; + private static final String CLASSNAME = "v-ddwrapper"; + protected static final String DRAGGABLE = "draggable"; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean hasTooltip = false; + private int startX = 0; + private int startY = 0; + + public VDragAndDropWrapper() { + super(); + hookHtml5Events(getElement()); + setStyleName(CLASSNAME); + + addDomHandler(new MouseDownHandler() { + + @Override + public void onMouseDown(final MouseDownEvent event) { + if (getConnector().isEnabled() + && event.getNativeEvent().getButton() == Event.BUTTON_LEFT + && startDrag(event.getNativeEvent())) { + event.preventDefault(); // prevent text selection + startX = event.getClientX(); + startY = event.getClientY(); + } + } + }, MouseDownEvent.getType()); + + addDomHandler(new MouseUpHandler() { + + @Override + public void onMouseUp(final MouseUpEvent event) { + final int deltaX = Math.abs(event.getClientX() - startX); + final int deltaY = Math.abs(event.getClientY() - startY); + if ((deltaX + deltaY) < MIN_PX_DELTA) { + setFocusOnLastElement(event); + } + } + + private void setFocusOnLastElement(final MouseUpEvent event) { + Element el = event.getRelativeElement(); + getLastChildElement(el).focus(); + } + + private Element getLastChildElement(Element el) { + do { + if (el == null) { + break; + } + el = el.getFirstChildElement(); + } while (el.getFirstChildElement() != null); + return el; + } + + }, MouseUpEvent.getType()); + + addDomHandler(new TouchStartHandler() { + + @Override + public void onTouchStart(TouchStartEvent event) { + if (getConnector().isEnabled() + && startDrag(event.getNativeEvent())) { + /* + * Dont let eg. panel start scrolling. + */ + event.stopPropagation(); + } + } + }, TouchStartEvent.getType()); + + sinkEvents(Event.TOUCHEVENTS); + } + + /** + * Starts a drag and drop operation from mousedown or touchstart event if + * required conditions are met. + * + * @param event + * @return true if the event was handled as a drag start event + */ + private boolean startDrag(NativeEvent event) { + if (dragStartMode == WRAPPER || dragStartMode == COMPONENT + || dragStartMode == COMPONENT_OTHER) { + VTransferable transferable = new VTransferable(); + transferable.setDragSource(getConnector()); + + ComponentConnector paintable = Util.findPaintable(client, + Element.as(event.getEventTarget())); + Widget widget = paintable.getWidget(); + transferable.setData("component", paintable); + VDragEvent dragEvent = VDragAndDropManager.get().startDrag( + transferable, event, true); + + transferable.setData("mouseDown", MouseEventDetailsBuilder + .buildMouseEventDetails(event).serialize()); + + if (dragStartMode == WRAPPER) { + dragEvent.createDragImage(getElement(), true); + } else if (dragStartMode == COMPONENT_OTHER + && getDragImageWidget() != null) { + dragEvent.createDragImage(getDragImageWidget().getElement(), + true); + } else { + dragEvent.createDragImage(widget.getElement(), true); + } + return true; + } + return false; + } + + protected final static int NONE = 0; + protected final static int COMPONENT = 1; + protected final static int WRAPPER = 2; + protected final static int HTML5 = 3; + protected final static int COMPONENT_OTHER = 4; + + /** For internal use only. May be removed or replaced in the future. */ + public int dragStartMode; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public VAbstractDropHandler dropHandler; + + private VDragEvent vaadinDragEvent; + + int filecounter = 0; + + /** For internal use only. May be removed or replaced in the future. */ + public Map fileIdToReceiver; + + /** For internal use only. May be removed or replaced in the future. */ + public ValueMap html5DataFlavors; + + private Element dragStartElement; + + /** For internal use only. May be removed or replaced in the future. */ + public void initDragStartMode() { + Element div = getElement(); + if (dragStartMode == HTML5) { + if (dragStartElement == null) { + dragStartElement = getDragStartElement(); + dragStartElement.setPropertyBoolean(DRAGGABLE, true); + VConsole.log("draggable = " + + dragStartElement.getPropertyBoolean(DRAGGABLE)); + hookHtml5DragStart(dragStartElement); + VConsole.log("drag start listeners hooked."); + } + } else { + dragStartElement = null; + if (div.hasAttribute(DRAGGABLE)) { + div.removeAttribute(DRAGGABLE); + } + } + } + + protected com.google.gwt.user.client.Element getDragStartElement() { + return getElement(); + } + + private boolean uploading; + + private final ReadyStateChangeHandler readyStateChangeHandler = new ReadyStateChangeHandler() { + + @Override + public void onReadyStateChange(XMLHttpRequest xhr) { + if (xhr.getReadyState() == XMLHttpRequest.DONE) { + // visit server for possible + // variable changes + client.sendPendingVariableChanges(); + uploading = false; + startNextUpload(); + xhr.clearOnReadyStateChange(); + } + } + }; + private Timer dragleavetimer; + + /** For internal use only. May be removed or replaced in the future. */ + public void startNextUpload() { + Scheduler.get().scheduleDeferred(new Command() { + + @Override + public void execute() { + if (!uploading) { + if (fileIds.size() > 0) { + + uploading = true; + final Integer fileId = fileIds.remove(0); + VHtml5File file = files.remove(0); + final String receiverUrl = client + .translateVaadinUri(fileIdToReceiver + .remove(fileId.toString())); + ExtendedXHR extendedXHR = (ExtendedXHR) ExtendedXHR + .create(); + extendedXHR + .setOnReadyStateChange(readyStateChangeHandler); + extendedXHR.open("POST", receiverUrl); + extendedXHR.postFile(file); + } + } + + } + }); + + } + + public boolean html5DragStart(VHtml5DragEvent event) { + if (dragStartMode == HTML5) { + /* + * Populate html5 payload with dataflavors from the serverside + */ + JsArrayString flavors = html5DataFlavors.getKeyArray(); + for (int i = 0; i < flavors.length(); i++) { + String flavor = flavors.get(i); + event.setHtml5DataFlavor(flavor, + html5DataFlavors.getString(flavor)); + } + event.setEffectAllowed("copy"); + return true; + } + return false; + } + + public boolean html5DragEnter(VHtml5DragEvent event) { + if (dropHandler == null) { + return true; + } + try { + if (dragleavetimer != null) { + // returned quickly back to wrapper + dragleavetimer.cancel(); + dragleavetimer = null; + } + if (VDragAndDropManager.get().getCurrentDropHandler() != getDropHandler()) { + VTransferable transferable = new VTransferable(); + transferable.setDragSource(getConnector()); + + vaadinDragEvent = VDragAndDropManager.get().startDrag( + transferable, event, false); + VDragAndDropManager.get().setCurrentDropHandler( + getDropHandler()); + } + try { + event.preventDefault(); + event.stopPropagation(); + } catch (Exception e) { + // VConsole.log("IE9 fails"); + } + return false; + } catch (Exception e) { + GWT.getUncaughtExceptionHandler().onUncaughtException(e); + return true; + } + } + + public boolean html5DragLeave(VHtml5DragEvent event) { + if (dropHandler == null) { + return true; + } + + try { + dragleavetimer = new Timer() { + + @Override + public void run() { + // Yes, dragleave happens before drop. Makes no sense to me. + // IMO shouldn't fire leave at all if drop happens (I guess + // this + // is what IE does). + // In Vaadin we fire it only if drop did not happen. + if (vaadinDragEvent != null + && VDragAndDropManager.get() + .getCurrentDropHandler() == getDropHandler()) { + VDragAndDropManager.get().interruptDrag(); + } + } + }; + dragleavetimer.schedule(350); + try { + event.preventDefault(); + event.stopPropagation(); + } catch (Exception e) { + // VConsole.log("IE9 fails"); + } + return false; + } catch (Exception e) { + GWT.getUncaughtExceptionHandler().onUncaughtException(e); + return true; + } + } + + public boolean html5DragOver(VHtml5DragEvent event) { + if (dropHandler == null) { + return true; + } + + if (dragleavetimer != null) { + // returned quickly back to wrapper + dragleavetimer.cancel(); + dragleavetimer = null; + } + + vaadinDragEvent.setCurrentGwtEvent(event); + getDropHandler().dragOver(vaadinDragEvent); + + try { + String s = event.getEffectAllowed(); + if ("all".equals(s) || s.contains("opy")) { + event.setDropEffect("copy"); + } else { + event.setDropEffect(s); + } + } catch (Exception e) { + // IE10 throws exception here in getEffectAllowed, ignore it, let + // drop effect be whatever it is + } + + try { + event.preventDefault(); + event.stopPropagation(); + } catch (Exception e) { + // VConsole.log("IE9 fails"); + } + return false; + } + + public boolean html5DragDrop(VHtml5DragEvent event) { + if (dropHandler == null || !currentlyValid) { + return true; + } + try { + + VTransferable transferable = vaadinDragEvent.getTransferable(); + + JsArrayString types = event.getTypes(); + for (int i = 0; i < types.length(); i++) { + String type = types.get(i); + if (isAcceptedType(type)) { + String data = event.getDataAsText(type); + if (data != null) { + transferable.setData(type, data); + } + } + } + + int fileCount = event.getFileCount(); + if (fileCount > 0) { + transferable.setData("filecount", fileCount); + for (int i = 0; i < fileCount; i++) { + final int fileId = filecounter++; + final VHtml5File file = event.getFile(i); + VConsole.log("Preparing to upload file " + file.getName() + + " with id " + fileId); + transferable.setData("fi" + i, "" + fileId); + transferable.setData("fn" + i, file.getName()); + transferable.setData("ft" + i, file.getType()); + transferable.setData("fs" + i, file.getSize()); + queueFilePost(fileId, file); + } + + } + + VDragAndDropManager.get().endDrag(); + vaadinDragEvent = null; + try { + event.preventDefault(); + event.stopPropagation(); + } catch (Exception e) { + // VConsole.log("IE9 fails"); + } + return false; + } catch (Exception e) { + GWT.getUncaughtExceptionHandler().onUncaughtException(e); + return true; + } + + } + + protected String[] acceptedTypes = new String[] { "Text", "Url", + "text/html", "text/plain", "text/rtf" }; + + private boolean isAcceptedType(String type) { + for (String t : acceptedTypes) { + if (t.equals(type)) { + return true; + } + } + return false; + } + + static class ExtendedXHR extends XMLHttpRequest { + + protected ExtendedXHR() { + } + + public final native void postFile(VHtml5File file) + /*-{ + + this.setRequestHeader('Content-Type', 'multipart/form-data'); + // Seems like IE10 will loose the file if we don't keep a reference to it... + this.fileBeingUploaded = file; + + this.send(file); + }-*/; + + } + + /** For internal use only. May be removed or replaced in the future. */ + public List fileIds = new ArrayList(); + + /** For internal use only. May be removed or replaced in the future. */ + public List files = new ArrayList(); + + private void queueFilePost(final int fileId, final VHtml5File file) { + fileIds.add(fileId); + files.add(file); + } + + @Override + public VDropHandler getDropHandler() { + return dropHandler; + } + + protected VerticalDropLocation verticalDropLocation; + protected HorizontalDropLocation horizontalDropLocation; + private VerticalDropLocation emphasizedVDrop; + private HorizontalDropLocation emphasizedHDrop; + + /** + * Flag used by html5 dd + */ + private boolean currentlyValid; + private Widget dragImageWidget; + + private static final String OVER_STYLE = "v-ddwrapper-over"; + + public class CustomDropHandler extends VAbstractDropHandler { + + @Override + public void dragEnter(VDragEvent drag) { + if (!getConnector().isEnabled()) { + return; + } + updateDropDetails(drag); + currentlyValid = false; + super.dragEnter(drag); + } + + @Override + public void dragLeave(VDragEvent drag) { + deEmphasis(true); + dragleavetimer = null; + } + + @Override + public void dragOver(final VDragEvent drag) { + if (!getConnector().isEnabled()) { + return; + } + boolean detailsChanged = updateDropDetails(drag); + if (detailsChanged) { + currentlyValid = false; + validate(new VAcceptCallback() { + + @Override + public void accepted(VDragEvent event) { + dragAccepted(drag); + } + }, drag); + } + } + + @Override + public boolean drop(VDragEvent drag) { + if (!getConnector().isEnabled()) { + return false; + } + deEmphasis(true); + + Map dd = drag.getDropDetails(); + + // this is absolute layout based, and we may want to set + // component + // relatively to where the drag ended. + // need to add current location of the drop area + + int absoluteLeft = getAbsoluteLeft(); + int absoluteTop = getAbsoluteTop(); + + dd.put("absoluteLeft", absoluteLeft); + dd.put("absoluteTop", absoluteTop); + + if (verticalDropLocation != null) { + dd.put("verticalLocation", verticalDropLocation.toString()); + dd.put("horizontalLocation", horizontalDropLocation.toString()); + } + + return super.drop(drag); + } + + @Override + protected void dragAccepted(VDragEvent drag) { + if (!getConnector().isEnabled()) { + return; + } + currentlyValid = true; + emphasis(drag); + } + + @Override + public ComponentConnector getConnector() { + return VDragAndDropWrapper.this.getConnector(); + } + + @Override + public ApplicationConnection getApplicationConnection() { + return client; + } + + } + + public ComponentConnector getConnector() { + return ConnectorMap.get(client).getConnector(this); + } + + /** + * @deprecated As of 7.2, call or override + * {@link #hookHtml5DragStart(Element)} instead + */ + @Deprecated + protected native void hookHtml5DragStart( + com.google.gwt.user.client.Element el) + /*-{ + var me = this; + el.addEventListener("dragstart", $entry(function(ev) { + return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); + }), false); + }-*/; + + /** + * @since 7.2 + */ + protected void hookHtml5DragStart(Element el) { + hookHtml5DragStart(DOM.asOld(el)); + } + + /** + * Prototype code, memory leak risk. + * + * @param el + * @deprecated As of 7.2, call or override {@link #hookHtml5Events(Element)} + * instead + */ + @Deprecated + protected native void hookHtml5Events(com.google.gwt.user.client.Element el) + /*-{ + var me = this; + + el.addEventListener("dragenter", $entry(function(ev) { + return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); + }), false); + + el.addEventListener("dragleave", $entry(function(ev) { + return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); + }), false); + + el.addEventListener("dragover", $entry(function(ev) { + return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); + }), false); + + el.addEventListener("drop", $entry(function(ev) { + return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); + }), false); + }-*/; + + /** + * Prototype code, memory leak risk. + * + * @param el + * + * @since 7.2 + */ + protected void hookHtml5Events(Element el) { + hookHtml5Events(DOM.asOld(el)); + } + + public boolean updateDropDetails(VDragEvent drag) { + VerticalDropLocation oldVL = verticalDropLocation; + verticalDropLocation = DDUtil.getVerticalDropLocation(getElement(), + drag.getCurrentGwtEvent(), 0.2); + drag.getDropDetails().put("verticalLocation", + verticalDropLocation.toString()); + HorizontalDropLocation oldHL = horizontalDropLocation; + horizontalDropLocation = DDUtil.getHorizontalDropLocation(getElement(), + drag.getCurrentGwtEvent(), 0.2); + drag.getDropDetails().put("horizontalLocation", + horizontalDropLocation.toString()); + if (oldHL != horizontalDropLocation || oldVL != verticalDropLocation) { + return true; + } else { + return false; + } + } + + protected void deEmphasis(boolean doLayout) { + if (emphasizedVDrop != null) { + VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, false); + VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-" + + emphasizedVDrop.toString().toLowerCase(), false); + VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-" + + emphasizedHDrop.toString().toLowerCase(), false); + } + if (doLayout) { + notifySizePotentiallyChanged(); + } + } + + private void notifySizePotentiallyChanged() { + LayoutManager.get(client).setNeedsMeasure(getConnector()); + } + + protected void emphasis(VDragEvent drag) { + deEmphasis(false); + VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, true); + VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-" + + verticalDropLocation.toString().toLowerCase(), true); + VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-" + + horizontalDropLocation.toString().toLowerCase(), true); + emphasizedVDrop = verticalDropLocation; + emphasizedHDrop = horizontalDropLocation; + + // TODO build (to be an example) an emphasis mode where drag image + // is fitted before or after the content + notifySizePotentiallyChanged(); + } + + /** + * Set the widget that will be used as the drag image when using + * DragStartMode {@link COMPONENT_OTHER} . + * + * @param widget + */ + public void setDragAndDropWidget(Widget widget) { + dragImageWidget = widget; + } + + /** + * @return the widget used as drag image. Returns null if no + * widget is set. + */ + public Widget getDragImageWidget() { + return dragImageWidget; + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VDragAndDropWrapperIE.java b/client/src/main/java/com/vaadin/client/ui/VDragAndDropWrapperIE.java new file mode 100644 index 0000000000..b32b36d13b --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VDragAndDropWrapperIE.java @@ -0,0 +1,95 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.dom.client.AnchorElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.DOM; +import com.vaadin.client.VConsole; + +public class VDragAndDropWrapperIE extends VDragAndDropWrapper { + private AnchorElement anchor = null; + + @Override + protected com.google.gwt.user.client.Element getDragStartElement() { + VConsole.log("IE get drag start element..."); + Element div = getElement(); + if (dragStartMode == HTML5) { + if (anchor == null) { + anchor = Document.get().createAnchorElement(); + anchor.setHref("#"); + anchor.setClassName("drag-start"); + div.appendChild(anchor); + } + VConsole.log("IE get drag start element..."); + return anchor.cast(); + } else { + if (anchor != null) { + div.removeChild(anchor); + anchor = null; + } + return DOM.asOld(div); + } + } + + @Deprecated + @Override + protected native void hookHtml5DragStart( + com.google.gwt.user.client.Element el) + /*-{ + var me = this; + + el.attachEvent("ondragstart", $entry(function(ev) { + return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); + })); + }-*/; + + @Override + protected void hookHtml5DragStart(Element el) { + hookHtml5DragStart(DOM.asOld(el)); + } + + @Deprecated + @Override + protected native void hookHtml5Events(com.google.gwt.user.client.Element el) + /*-{ + var me = this; + + el.attachEvent("ondragenter", $entry(function(ev) { + return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); + })); + + el.attachEvent("ondragleave", $entry(function(ev) { + return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); + })); + + el.attachEvent("ondragover", $entry(function(ev) { + return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); + })); + + el.attachEvent("ondrop", $entry(function(ev) { + return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev); + })); + }-*/; + + @Override + protected void hookHtml5Events(Element el) { + hookHtml5Events(DOM.asOld(el)); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VEmbedded.java b/client/src/main/java/com/vaadin/client/ui/VEmbedded.java new file mode 100644 index 0000000000..f3970f9ac7 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VEmbedded.java @@ -0,0 +1,263 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.HTML; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorMap; +import com.vaadin.client.UIDL; +import com.vaadin.client.Util; +import com.vaadin.client.VConsole; +import com.vaadin.client.WidgetUtil; +import com.vaadin.shared.ui.embedded.EmbeddedConstants; + +public class VEmbedded extends HTML { + public static String CLASSNAME = "v-embedded"; + + /** For internal use only. May be removed or replaced in the future. */ + public Element browserElement; + + /** For internal use only. May be removed or replaced in the future. */ + public String type; + + /** For internal use only. May be removed or replaced in the future. */ + public String mimetype; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + public VEmbedded() { + setStyleName(CLASSNAME); + } + + /** + * Creates the Object and Embed tags for the Flash plugin so it works + * cross-browser. + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param uidl + * The UIDL + * @return Tags concatenated into a string + */ + public String createFlashEmbed(UIDL uidl) { + /* + * To ensure cross-browser compatibility we are using the twice-cooked + * method to embed flash i.e. we add a OBJECT tag for IE ActiveX and + * inside it a EMBED for all other browsers. + */ + + StringBuilder html = new StringBuilder(); + + // Start the object tag + html.append(""); + + // Ensure we have an movie parameter + Map parameters = getParameters(uidl); + if (parameters.get("movie") == null) { + parameters.put("movie", getSrc(uidl, client)); + } + + // Add parameters to OBJECT + for (String name : parameters.keySet()) { + html.append(""); + } + + // Build inner EMBED tag + html.append(""); + + if (uidl.hasAttribute(EmbeddedConstants.ALTERNATE_TEXT)) { + html.append(uidl + .getStringAttribute(EmbeddedConstants.ALTERNATE_TEXT)); + } + + // End object tag + html.append(""); + + return html.toString(); + } + + /** + * Returns a map (name -> value) of all parameters in the UIDL. + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param uidl + * @return + */ + public static Map getParameters(UIDL uidl) { + Map parameters = new HashMap(); + + Iterator childIterator = uidl.getChildIterator(); + while (childIterator.hasNext()) { + + Object child = childIterator.next(); + if (child instanceof UIDL) { + + UIDL childUIDL = (UIDL) child; + if (childUIDL.getTag().equals("embeddedparam")) { + String name = childUIDL.getStringAttribute("name"); + String value = childUIDL.getStringAttribute("value"); + parameters.put(name, value); + } + } + + } + + return parameters; + } + + /** + * Helper to return translated src-attribute from embedded's UIDL + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param uidl + * @param client + * @return + */ + public String getSrc(UIDL uidl, ApplicationConnection client) { + String url = client.translateVaadinUri(uidl.getStringAttribute("src")); + if (url == null) { + return ""; + } + return url; + } + + @Override + protected void onDetach() { + if (BrowserInfo.get().isIE()) { + // Force browser to fire unload event when component is detached + // from the view (IE doesn't do this automatically) + if (browserElement != null) { + /* + * src was previously set to javascript:false, but this was not + * enough to overcome a bug when detaching an iframe with a pdf + * loaded in IE9. about:blank seems to cause the adobe reader + * plugin to unload properly before the iframe is removed. See + * #7855 + */ + DOM.setElementAttribute(browserElement, "src", "about:blank"); + } + } + super.onDetach(); + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (DOM.eventGetType(event) == Event.ONLOAD) { + VConsole.log("Embeddable onload"); + Util.notifyParentOfSizeChange(this, true); + } + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VFilterSelect.java b/client/src/main/java/com/vaadin/client/ui/VFilterSelect.java new file mode 100644 index 0000000000..9459cc14a6 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VFilterSelect.java @@ -0,0 +1,2351 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import com.google.gwt.aria.client.Roles; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyUpEvent; +import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.event.dom.client.LoadEvent; +import com.google.gwt.event.dom.client.LoadHandler; +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.i18n.client.HasDirection.Direction; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; +import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ComputedStyle; +import com.vaadin.client.ConnectorMap; +import com.vaadin.client.DeferredWorker; +import com.vaadin.client.Focusable; +import com.vaadin.client.UIDL; +import com.vaadin.client.VConsole; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.aria.AriaHelper; +import com.vaadin.client.ui.aria.HandlesAriaCaption; +import com.vaadin.client.ui.aria.HandlesAriaInvalid; +import com.vaadin.client.ui.aria.HandlesAriaRequired; +import com.vaadin.client.ui.menubar.MenuBar; +import com.vaadin.client.ui.menubar.MenuItem; +import com.vaadin.shared.AbstractComponentState; +import com.vaadin.shared.EventId; +import com.vaadin.shared.ui.ComponentStateUtil; +import com.vaadin.shared.ui.combobox.FilteringMode; +import com.vaadin.shared.util.SharedUtil; + +/** + * Client side implementation of the Select component. + * + * TODO needs major refactoring (to be extensible etc) + */ +@SuppressWarnings("deprecation") +public class VFilterSelect extends Composite implements Field, KeyDownHandler, + KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable, + SubPartAware, HandlesAriaCaption, HandlesAriaInvalid, + HandlesAriaRequired, DeferredWorker { + + /** + * Represents a suggestion in the suggestion popup box + */ + public class FilterSelectSuggestion implements Suggestion, Command { + + private final String key; + private final String caption; + private String untranslatedIconUri; + private String style; + + /** + * Constructor + * + * @param uidl + * The UIDL recieved from the server + */ + public FilterSelectSuggestion(UIDL uidl) { + key = uidl.getStringAttribute("key"); + caption = uidl.getStringAttribute("caption"); + style = uidl.getStringAttribute("style"); + + if (uidl.hasAttribute("icon")) { + untranslatedIconUri = uidl.getStringAttribute("icon"); + } + } + + /** + * Gets the visible row in the popup as a HTML string. The string + * contains an image tag with the rows icon (if an icon has been + * specified) and the caption of the item + */ + + @Override + public String getDisplayString() { + final StringBuffer sb = new StringBuffer(); + final Icon icon = client.getIcon(client + .translateVaadinUri(untranslatedIconUri)); + if (icon != null) { + sb.append(icon.getElement().getString()); + } + String content; + if ("".equals(caption)) { + // Ensure that empty options use the same height as other + // options and are not collapsed (#7506) + content = " "; + } else { + content = WidgetUtil.escapeHTML(caption); + } + sb.append("" + content + ""); + return sb.toString(); + } + + /** + * Get a string that represents this item. This is used in the text box. + */ + + @Override + public String getReplacementString() { + return caption; + } + + /** + * Get the option key which represents the item on the server side. + * + * @return The key of the item + */ + public String getOptionKey() { + return key; + } + + /** + * Get the URI of the icon. Used when constructing the displayed option. + * + * @return + */ + public String getIconUri() { + return client.translateVaadinUri(untranslatedIconUri); + } + + /** + * Gets the style set for this suggestion item. Styles are typically set + * by a server-side {@link com.vaadin.ui.ComboBox.ItemStyleGenerator}. + * The returned style is prefixed by v-filterselect-item-. + * + * @since 7.5.6 + * @return the style name to use, or null to not apply any + * custom style. + */ + public String getStyle() { + return style; + } + + /** + * Executes a selection of this item. + */ + + @Override + public void execute() { + onSuggestionSelected(this); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof FilterSelectSuggestion)) { + return false; + } + FilterSelectSuggestion other = (FilterSelectSuggestion) obj; + if ((key == null && other.key != null) + || (key != null && !key.equals(other.key))) { + return false; + } + if ((caption == null && other.caption != null) + || (caption != null && !caption.equals(other.caption))) { + return false; + } + if (!SharedUtil.equals(untranslatedIconUri, + other.untranslatedIconUri)) { + return false; + } + if (!SharedUtil.equals(style, other.style)) { + return false; + } + return true; + } + } + + /** An inner class that handles all logic related to mouse wheel. */ + private class MouseWheeler extends JsniMousewheelHandler { + + public MouseWheeler() { + super(VFilterSelect.this); + } + + @Override + protected native JavaScriptObject createMousewheelListenerFunction( + Widget widget) + /*-{ + return $entry(function(e) { + var deltaX = e.deltaX ? e.deltaX : -0.5*e.wheelDeltaX; + var deltaY = e.deltaY ? e.deltaY : -0.5*e.wheelDeltaY; + + // IE8 has only delta y + if (isNaN(deltaY)) { + deltaY = -0.5*e.wheelDelta; + } + + @com.vaadin.client.ui.VFilterSelect.JsniUtil::moveScrollFromEvent(*)(widget, deltaX, deltaY, e); + }); + }-*/; + + } + + /** + * A utility class that contains utility methods that are usually called + * from JSNI. + *

+ * The methods are moved in this class to minimize the amount of JSNI code + * as much as feasible. + */ + static class JsniUtil { + public static void moveScrollFromEvent(final Widget widget, + final double deltaX, final double deltaY, + final NativeEvent event) { + + if (!Double.isNaN(deltaY)) { + ((VFilterSelect) widget).suggestionPopup.scroll(deltaY); + } + } + } + + /** + * Represents the popup box with the selection options. Wraps a suggestion + * menu. + */ + public class SuggestionPopup extends VOverlay implements PositionCallback, + CloseHandler { + + private static final int Z_INDEX = 30000; + + /** For internal use only. May be removed or replaced in the future. */ + public final SuggestionMenu menu; + + private final Element up = DOM.createDiv(); + private final Element down = DOM.createDiv(); + private final Element status = DOM.createDiv(); + + private boolean isPagingEnabled = true; + + private long lastAutoClosed; + + private int popupOuterPadding = -1; + + private int topPosition; + + private final MouseWheeler mouseWheeler = new MouseWheeler(); + + /** + * Default constructor + */ + SuggestionPopup() { + super(true, false, true); + debug("VFS.SP: constructor()"); + setOwner(VFilterSelect.this); + menu = new SuggestionMenu(); + setWidget(menu); + + getElement().getStyle().setZIndex(Z_INDEX); + + final Element root = getContainerElement(); + + up.setInnerHTML("Prev"); + DOM.sinkEvents(up, Event.ONCLICK); + + down.setInnerHTML("Next"); + DOM.sinkEvents(down, Event.ONCLICK); + + root.insertFirst(up); + root.appendChild(down); + root.appendChild(status); + + DOM.sinkEvents(root, Event.ONMOUSEDOWN | Event.ONMOUSEWHEEL); + addCloseHandler(this); + + Roles.getListRole().set(getElement()); + + setPreviewingAllNativeEvents(true); + } + + @Override + protected void onLoad() { + super.onLoad(); + mouseWheeler.attachMousewheelListener(getElement()); + } + + @Override + protected void onUnload() { + mouseWheeler.detachMousewheelListener(getElement()); + super.onUnload(); + } + + /** + * Shows the popup where the user can see the filtered options + * + * @param currentSuggestions + * The filtered suggestions + * @param currentPage + * The current page number + * @param totalSuggestions + * The total amount of suggestions + */ + public void showSuggestions( + final Collection currentSuggestions, + final int currentPage, final int totalSuggestions) { + + debug("VFS.SP: showSuggestions(" + currentSuggestions + ", " + + currentPage + ", " + totalSuggestions + ")"); + + /* + * We need to defer the opening of the popup so that the parent DOM + * has stabilized so we can calculate an absolute top and left + * correctly. This issue manifests when a Combobox is placed in + * another popupView which also needs to calculate the absoluteTop() + * to position itself. #9768 + * + * After deferring the showSuggestions method, a problem with + * navigating in the combo box occurs. Because of that the method + * navigateItemAfterPageChange in ComboBoxConnector class, which + * navigates to the exact item after page was changed also was + * marked as deferred. #11333 + */ + final SuggestionPopup popup = this; + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + // Add TT anchor point + getElement().setId("VAADIN_COMBOBOX_OPTIONLIST"); + + menu.setSuggestions(currentSuggestions); + final int x = VFilterSelect.this.getAbsoluteLeft(); + + topPosition = tb.getAbsoluteTop(); + topPosition += tb.getOffsetHeight(); + + setPopupPosition(x, topPosition); + + int nullOffset = (nullSelectionAllowed + && "".equals(lastFilter) ? 1 : 0); + boolean firstPage = (currentPage == 0); + final int first = currentPage * pageLength + 1 + - (firstPage ? 0 : nullOffset); + final int last = first + + currentSuggestions.size() + - 1 + - (firstPage && "".equals(lastFilter) ? nullOffset + : 0); + final int matches = totalSuggestions - nullOffset; + if (last > 0) { + // nullsel not counted, as requested by user + status.setInnerText((matches == 0 ? 0 : first) + "-" + + last + "/" + matches); + } else { + status.setInnerText(""); + } + // We don't need to show arrows or statusbar if there is + // only one page + if (totalSuggestions <= pageLength || pageLength == 0) { + setPagingEnabled(false); + } else { + setPagingEnabled(true); + } + setPrevButtonActive(first > 1); + setNextButtonActive(last < matches); + + // clear previously fixed width + menu.setWidth(""); + menu.getElement().getFirstChildElement().getStyle() + .clearWidth(); + + setPopupPositionAndShow(popup); + // Fix for #14173 + // IE9 and IE10 have a bug, when resize an a element with + // box-shadow. + // IE9 and IE10 need explicit update to remove extra + // box-shadows + if (BrowserInfo.get().isIE9() || BrowserInfo.get().isIE10()) { + forceReflow(); + } + } + }); + } + + /** + * Should the next page button be visible to the user? + * + * @param active + */ + private void setNextButtonActive(boolean active) { + if (enableDebug) { + debug("VFS.SP: setNextButtonActive(" + active + ")"); + } + if (active) { + DOM.sinkEvents(down, Event.ONCLICK); + down.setClassName(VFilterSelect.this.getStylePrimaryName() + + "-nextpage"); + } else { + DOM.sinkEvents(down, 0); + down.setClassName(VFilterSelect.this.getStylePrimaryName() + + "-nextpage-off"); + } + } + + /** + * Should the previous page button be visible to the user + * + * @param active + */ + private void setPrevButtonActive(boolean active) { + if (enableDebug) { + debug("VFS.SP: setPrevButtonActive(" + active + ")"); + } + + if (active) { + DOM.sinkEvents(up, Event.ONCLICK); + up.setClassName(VFilterSelect.this.getStylePrimaryName() + + "-prevpage"); + } else { + DOM.sinkEvents(up, 0); + up.setClassName(VFilterSelect.this.getStylePrimaryName() + + "-prevpage-off"); + } + + } + + /** + * Selects the next item in the filtered selections + */ + public void selectNextItem() { + debug("VFS.SP: selectNextItem()"); + + final int index = menu.getSelectedIndex() + 1; + if (menu.getItems().size() > index) { + selectItem(menu.getItems().get(index)); + + } else { + selectNextPage(); + } + } + + /** + * Selects the previous item in the filtered selections + */ + public void selectPrevItem() { + debug("VFS.SP: selectPrevItem()"); + + final int index = menu.getSelectedIndex() - 1; + if (index > -1) { + selectItem(menu.getItems().get(index)); + + } else if (index == -1) { + selectPrevPage(); + + } else { + if (!menu.getItems().isEmpty()) { + selectLastItem(); + } + } + } + + /** + * Select the first item of the suggestions list popup. + * + * @since 7.2.6 + */ + public void selectFirstItem() { + debug("VFS.SP: selectFirstItem()"); + selectItem(menu.getFirstItem()); + } + + /** + * Select the last item of the suggestions list popup. + * + * @since 7.2.6 + */ + public void selectLastItem() { + debug("VFS.SP: selectLastItem()"); + selectItem(menu.getLastItem()); + } + + /* + * Sets the selected item in the popup menu. + */ + private void selectItem(final MenuItem newSelectedItem) { + menu.selectItem(newSelectedItem); + + // Set the icon. + FilterSelectSuggestion suggestion = (FilterSelectSuggestion) newSelectedItem + .getCommand(); + setSelectedItemIcon(suggestion.getIconUri()); + + // Set the text. + setText(suggestion.getReplacementString()); + + } + + /* + * Using a timer to scroll up or down the pages so when we receive lots + * of consecutive mouse wheel events the pages does not flicker. + */ + private LazyPageScroller lazyPageScroller = new LazyPageScroller(); + + private class LazyPageScroller extends Timer { + private int pagesToScroll = 0; + + @Override + public void run() { + debug("VFS.SP.LPS: run()"); + if (pagesToScroll != 0) { + if (!waitingForFilteringResponse) { + /* + * Avoid scrolling while we are waiting for a response + * because otherwise the waiting flag will be reset in + * the first response and the second response will be + * ignored, causing an empty popup... + * + * As long as the scrolling delay is suitable + * double/triple clicks will work by scrolling two or + * three pages at a time and this should not be a + * problem. + */ + filterOptions(currentPage + pagesToScroll, lastFilter); + } + pagesToScroll = 0; + } + } + + public void scrollUp() { + debug("VFS.SP.LPS: scrollUp()"); + if (pageLength > 0 && currentPage + pagesToScroll > 0) { + pagesToScroll--; + cancel(); + schedule(200); + } + } + + public void scrollDown() { + debug("VFS.SP.LPS: scrollDown()"); + if (pageLength > 0 + && totalMatches > (currentPage + pagesToScroll + 1) + * pageLength) { + pagesToScroll++; + cancel(); + schedule(200); + } + } + } + + private void scroll(double deltaY) { + boolean scrollActive = menu.isScrollActive(); + + debug("VFS.SP: scroll() scrollActive: " + scrollActive); + + if (!scrollActive) { + if (deltaY > 0d) { + lazyPageScroller.scrollDown(); + } else { + lazyPageScroller.scrollUp(); + } + } + } + + @Override + public void onBrowserEvent(Event event) { + debug("VFS.SP: onBrowserEvent()"); + + if (event.getTypeInt() == Event.ONCLICK) { + final Element target = DOM.eventGetTarget(event); + if (target == up || target == DOM.getChild(up, 0)) { + lazyPageScroller.scrollUp(); + } else if (target == down || target == DOM.getChild(down, 0)) { + lazyPageScroller.scrollDown(); + } + + } + + /* + * Prevent the keyboard focus from leaving the textfield by + * preventing the default behaviour of the browser. Fixes #4285. + */ + handleMouseDownEvent(event); + } + + /** + * Should paging be enabled. If paging is enabled then only a certain + * amount of items are visible at a time and a scrollbar or buttons are + * visible to change page. If paging is turned of then all options are + * rendered into the popup menu. + * + * @param paging + * Should the paging be turned on? + */ + public void setPagingEnabled(boolean paging) { + debug("VFS.SP: setPagingEnabled(" + paging + ")"); + if (isPagingEnabled == paging) { + return; + } + if (paging) { + down.getStyle().clearDisplay(); + up.getStyle().clearDisplay(); + status.getStyle().clearDisplay(); + } else { + down.getStyle().setDisplay(Display.NONE); + up.getStyle().setDisplay(Display.NONE); + status.getStyle().setDisplay(Display.NONE); + } + isPagingEnabled = paging; + } + + @Override + public void setPosition(int offsetWidth, int offsetHeight) { + debug("VFS.SP: setPosition(" + offsetWidth + ", " + offsetHeight + + ")"); + + int top = topPosition; + int left = getPopupLeft(); + + // reset menu size and retrieve its "natural" size + menu.setHeight(""); + if (currentPage > 0 && !hasNextPage()) { + // fix height to avoid height change when getting to last page + menu.fixHeightTo(pageLength); + } + + final int desiredHeight = offsetHeight = getOffsetHeight(); + final int desiredWidth = getMainWidth(); + + debug("VFS.SP: desired[" + desiredWidth + ", " + desiredHeight + + "]"); + + Element menuFirstChild = menu.getElement().getFirstChildElement(); + final int naturalMenuWidth = WidgetUtil + .getRequiredWidth(menuFirstChild); + + if (popupOuterPadding == -1) { + popupOuterPadding = WidgetUtil + .measureHorizontalPaddingAndBorder(getElement(), 2); + } + + if (naturalMenuWidth < desiredWidth) { + menu.setWidth((desiredWidth - popupOuterPadding) + "px"); + menuFirstChild.getStyle().setWidth(100, Unit.PCT); + } + + if (BrowserInfo.get().isIE() + && BrowserInfo.get().getBrowserMajorVersion() < 11) { + // Must take margin,border,padding manually into account for + // menu element as we measure the element child and set width to + // the element parent + double naturalMenuOuterWidth = WidgetUtil + .getRequiredWidthDouble(menuFirstChild) + + getMarginBorderPaddingWidth(menu.getElement()); + + /* + * IE requires us to specify the width for the container + * element. Otherwise it will be 100% wide + */ + double rootWidth = Math.max(desiredWidth - popupOuterPadding, + naturalMenuOuterWidth); + getContainerElement().getStyle().setWidth(rootWidth, Unit.PX); + } + + final int vfsHeight = VFilterSelect.this.getOffsetHeight(); + final int spaceAvailableAbove = top - vfsHeight; + final int spaceAvailableBelow = Window.getClientHeight() - top; + if (spaceAvailableBelow < offsetHeight + && spaceAvailableBelow < spaceAvailableAbove) { + // popup on top of input instead + top -= offsetHeight + vfsHeight; + if (top < 0) { + offsetHeight += top; + top = 0; + } + } else { + offsetHeight = Math.min(offsetHeight, spaceAvailableBelow); + } + + // fetch real width (mac FF bugs here due GWT popups overflow:auto ) + offsetWidth = menuFirstChild.getOffsetWidth(); + + if (offsetHeight < desiredHeight) { + int menuHeight = offsetHeight; + if (isPagingEnabled) { + menuHeight -= up.getOffsetHeight() + down.getOffsetHeight() + + status.getOffsetHeight(); + } else { + final ComputedStyle s = new ComputedStyle(menu.getElement()); + menuHeight -= s.getIntProperty("marginBottom") + + s.getIntProperty("marginTop"); + } + + // If the available page height is really tiny then this will be + // negative and an exception will be thrown on setHeight. + int menuElementHeight = menu.getItemOffsetHeight(); + if (menuHeight < menuElementHeight) { + menuHeight = menuElementHeight; + } + + menu.setHeight(menuHeight + "px"); + + final int naturalMenuWidthPlusScrollBar = naturalMenuWidth + + WidgetUtil.getNativeScrollbarSize(); + if (offsetWidth < naturalMenuWidthPlusScrollBar) { + menu.setWidth(naturalMenuWidthPlusScrollBar + "px"); + } + } + + if (offsetWidth + left > Window.getClientWidth()) { + left = VFilterSelect.this.getAbsoluteLeft() + + VFilterSelect.this.getOffsetWidth() - offsetWidth; + if (left < 0) { + left = 0; + menu.setWidth(Window.getClientWidth() + "px"); + } + } + + setPopupPosition(left, top); + menu.scrollSelectionIntoView(); + } + + /** + * Was the popup just closed? + * + * @return true if popup was just closed + */ + public boolean isJustClosed() { + debug("VFS.SP: justClosed()"); + final long now = (new Date()).getTime(); + return (lastAutoClosed > 0 && (now - lastAutoClosed) < 200); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google + * .gwt.event.logical.shared.CloseEvent) + */ + + @Override + public void onClose(CloseEvent event) { + if (enableDebug) { + debug("VFS.SP: onClose(" + event.isAutoClosed() + ")"); + } + if (event.isAutoClosed()) { + lastAutoClosed = (new Date()).getTime(); + } + } + + /** + * Updates style names in suggestion popup to help theme building. + * + * @param uidl + * UIDL for the whole combo box + * @param componentState + * shared state of the combo box + */ + public void updateStyleNames(UIDL uidl, + AbstractComponentState componentState) { + debug("VFS.SP: updateStyleNames()"); + setStyleName(VFilterSelect.this.getStylePrimaryName() + + "-suggestpopup"); + menu.setStyleName(VFilterSelect.this.getStylePrimaryName() + + "-suggestmenu"); + status.setClassName(VFilterSelect.this.getStylePrimaryName() + + "-status"); + if (ComponentStateUtil.hasStyles(componentState)) { + for (String style : componentState.styles) { + if (!"".equals(style)) { + addStyleDependentName(style); + } + } + } + } + + } + + /** + * The menu where the suggestions are rendered + */ + public class SuggestionMenu extends MenuBar implements SubPartAware, + LoadHandler { + + private VLazyExecutor delayedImageLoadExecutioner = new VLazyExecutor( + 100, new ScheduledCommand() { + + @Override + public void execute() { + debug("VFS.SM: delayedImageLoadExecutioner()"); + if (suggestionPopup.isVisible() + && suggestionPopup.isAttached()) { + setWidth(""); + getElement().getFirstChildElement().getStyle() + .clearWidth(); + suggestionPopup + .setPopupPositionAndShow(suggestionPopup); + } + + } + }); + + /** + * Default constructor + */ + SuggestionMenu() { + super(true); + debug("VFS.SM: constructor()"); + addDomHandler(this, LoadEvent.getType()); + + setScrollEnabled(true); + } + + /** + * Fixes menus height to use same space as full page would use. Needed + * to avoid height changes when quickly "scrolling" to last page. + */ + public void fixHeightTo(int pageItemsCount) { + setHeight(getPreferredHeight(pageItemsCount)); + } + + /* + * Gets the preferred height of the menu including pageItemsCount items. + */ + String getPreferredHeight(int pageItemsCount) { + if (currentSuggestions.size() > 0) { + final int pixels = (getPreferredHeight() / currentSuggestions + .size()) * pageItemsCount; + return pixels + "px"; + } else { + return ""; + } + } + + /** + * Sets the suggestions rendered in the menu + * + * @param suggestions + * The suggestions to be rendered in the menu + */ + public void setSuggestions( + Collection suggestions) { + if (enableDebug) { + debug("VFS.SM: setSuggestions(" + suggestions + ")"); + } + + clearItems(); + final Iterator it = suggestions.iterator(); + boolean isFirstIteration = true; + while (it.hasNext()) { + final FilterSelectSuggestion s = it.next(); + final MenuItem mi = new MenuItem(s.getDisplayString(), true, s); + String style = s.getStyle(); + if (style != null) { + mi.addStyleName("v-filterselect-item-" + style); + } + Roles.getListitemRole().set(mi.getElement()); + + WidgetUtil.sinkOnloadForImages(mi.getElement()); + + this.addItem(mi); + + // By default, first item on the list is always highlighted, + // unless adding new items is allowed. + if (isFirstIteration && !allowNewItem) { + selectItem(mi); + } + + // If the filter matches the current selection, highlight that + // instead of the first item. + if (tb.getText().equals(s.getReplacementString()) + && s == currentSuggestion) { + selectItem(mi); + } + + isFirstIteration = false; + } + } + + /** + * Send the current selection to the server. Triggered when a selection + * is made or on a blur event. + */ + public void doSelectedItemAction() { + debug("VFS.SM: doSelectedItemAction()"); + // do not send a value change event if null was and stays selected + final String enteredItemValue = tb.getText(); + if (nullSelectionAllowed && "".equals(enteredItemValue) + && selectedOptionKey != null + && !"".equals(selectedOptionKey)) { + if (nullSelectItem) { + reset(); + return; + } + // null is not visible on pages != 0, and not visible when + // filtering: handle separately + client.updateVariable(paintableId, "filter", "", false); + client.updateVariable(paintableId, "page", 0, false); + client.updateVariable(paintableId, "selected", new String[] {}, + immediate); + afterUpdateClientVariables(); + + suggestionPopup.hide(); + return; + } + + updateSelectionWhenReponseIsReceived = waitingForFilteringResponse; + if (!waitingForFilteringResponse) { + doPostFilterSelectedItemAction(); + } + } + + /** + * Triggered after a selection has been made + */ + public void doPostFilterSelectedItemAction() { + debug("VFS.SM: doPostFilterSelectedItemAction()"); + final MenuItem item = getSelectedItem(); + final String enteredItemValue = tb.getText(); + + updateSelectionWhenReponseIsReceived = false; + + // check for exact match in menu + int p = getItems().size(); + if (p > 0) { + for (int i = 0; i < p; i++) { + final MenuItem potentialExactMatch = getItems().get(i); + if (potentialExactMatch.getText().equals(enteredItemValue)) { + selectItem(potentialExactMatch); + // do not send a value change event if null was and + // stays selected + if (!"".equals(enteredItemValue) + || (selectedOptionKey != null && !"" + .equals(selectedOptionKey))) { + doItemAction(potentialExactMatch, true); + } + suggestionPopup.hide(); + return; + } + } + } + if (allowNewItem) { + + if (!prompting && !enteredItemValue.equals(lastNewItemString)) { + /* + * Store last sent new item string to avoid double sends + */ + lastNewItemString = enteredItemValue; + client.updateVariable(paintableId, "newitem", + enteredItemValue, immediate); + afterUpdateClientVariables(); + } + } else if (item != null + && !"".equals(lastFilter) + && (filteringmode == FilteringMode.CONTAINS ? item + .getText().toLowerCase() + .contains(lastFilter.toLowerCase()) : item + .getText().toLowerCase() + .startsWith(lastFilter.toLowerCase()))) { + doItemAction(item, true); + } else { + // currentSuggestion has key="" for nullselection + if (currentSuggestion != null + && !currentSuggestion.key.equals("")) { + // An item (not null) selected + String text = currentSuggestion.getReplacementString(); + setText(text); + selectedOptionKey = currentSuggestion.key; + } else { + // Null selected + setText(""); + selectedOptionKey = null; + } + } + suggestionPopup.hide(); + } + + private static final String SUBPART_PREFIX = "item"; + + @Override + public com.google.gwt.user.client.Element getSubPartElement( + String subPart) { + int index = Integer.parseInt(subPart.substring(SUBPART_PREFIX + .length())); + + MenuItem item = getItems().get(index); + + return item.getElement(); + } + + @Override + public String getSubPartName( + com.google.gwt.user.client.Element subElement) { + if (!getElement().isOrHasChild(subElement)) { + return null; + } + + Element menuItemRoot = subElement; + while (menuItemRoot != null + && !menuItemRoot.getTagName().equalsIgnoreCase("td")) { + menuItemRoot = menuItemRoot.getParentElement().cast(); + } + // "menuItemRoot" is now the root of the menu item + + final int itemCount = getItems().size(); + for (int i = 0; i < itemCount; i++) { + if (getItems().get(i).getElement() == menuItemRoot) { + String name = SUBPART_PREFIX + i; + return name; + } + } + return null; + } + + @Override + public void onLoad(LoadEvent event) { + debug("VFS.SM: onLoad()"); + // Handle icon onload events to ensure shadow is resized + // correctly + delayedImageLoadExecutioner.trigger(); + + } + + /** + * @deprecated use {@link SuggestionPopup#selectFirstItem()} instead. + */ + @Deprecated + public void selectFirstItem() { + debug("VFS.SM: selectFirstItem()"); + MenuItem firstItem = getItems().get(0); + selectItem(firstItem); + } + + /** + * @deprecated use {@link SuggestionPopup#selectLastItem()} instead. + */ + @Deprecated + public void selectLastItem() { + debug("VFS.SM: selectLastItem()"); + List items = getItems(); + MenuItem lastItem = items.get(items.size() - 1); + selectItem(lastItem); + } + + /* + * Gets the height of one menu item. + */ + int getItemOffsetHeight() { + List items = getItems(); + return items != null && items.size() > 0 ? items.get(0) + .getOffsetHeight() : 0; + } + + /* + * Gets the width of one menu item. + */ + int getItemOffsetWidth() { + List items = getItems(); + return items != null && items.size() > 0 ? items.get(0) + .getOffsetWidth() : 0; + } + + /** + * Returns true if the scroll is active on the menu element or if the + * menu currently displays the last page with less items then the + * maximum visibility (in which case the scroll is not active, but the + * scroll is active for any other page in general). + * + * @since 7.2.6 + */ + @Override + public boolean isScrollActive() { + String height = getElement().getStyle().getHeight(); + String preferredHeight = getPreferredHeight(pageLength); + + return !(height == null || height.length() == 0 || height + .equals(preferredHeight)); + } + + } + + /** + * TextBox variant used as input element for filter selects, which prevents + * selecting text when disabled. + * + * @since 7.1.5 + */ + public class FilterSelectTextBox extends TextBox { + + /** + * Overridden to avoid selecting text when text input is disabled + */ + @Override + public void setSelectionRange(int pos, int length) { + if (textInputEnabled) { + /* + * set selection range with a backwards direction: anchor at the + * back, focus at the front. This means that items that are too + * long to display will display from the start and not the end + * even on Firefox. + * + * We need the JSNI function to set selection range so that we + * can use the optional direction attribute to set the anchor to + * the end and the focus to the start. This makes Firefox work + * the same way as other browsers (#13477) + */ + WidgetUtil.setSelectionRange(getElement(), pos, length, + "backward"); + + } else { + /* + * Setting the selectionrange for an uneditable textbox leads to + * unwanted behaviour when the width of the textbox is narrower + * than the width of the entry: the end of the entry is shown + * instead of the beginning. (see #13477) + * + * To avoid this, we set the caret to the beginning of the line. + */ + + super.setSelectionRange(0, 0); + } + } + + } + + @Deprecated + public static final FilteringMode FILTERINGMODE_OFF = FilteringMode.OFF; + @Deprecated + public static final FilteringMode FILTERINGMODE_STARTSWITH = FilteringMode.STARTSWITH; + @Deprecated + public static final FilteringMode FILTERINGMODE_CONTAINS = FilteringMode.CONTAINS; + + public static final String CLASSNAME = "v-filterselect"; + private static final String STYLE_NO_INPUT = "no-input"; + + /** For internal use only. May be removed or replaced in the future. */ + public int pageLength = 10; + + private boolean enableDebug = false; + + private final FlowPanel panel = new FlowPanel(); + + /** + * The text box where the filter is written + *

+ * For internal use only. May be removed or replaced in the future. + */ + public final TextBox tb; + + /** For internal use only. May be removed or replaced in the future. */ + public final SuggestionPopup suggestionPopup; + + /** + * Used when measuring the width of the popup + */ + private final HTML popupOpener = new HTML("") { + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt + * .user.client.Event) + */ + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + + /* + * Prevent the keyboard focus from leaving the textfield by + * preventing the default behaviour of the browser. Fixes #4285. + */ + handleMouseDownEvent(event); + } + }; + + private class IconWidget extends Widget { + IconWidget(Icon icon) { + setElement(icon.getElement()); + addDomHandler(VFilterSelect.this, ClickEvent.getType()); + } + } + + private IconWidget selectedItemIcon; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public String paintableId; + + /** For internal use only. May be removed or replaced in the future. */ + public int currentPage; + + /** + * A collection of available suggestions (options) as received from the + * server. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public final List currentSuggestions = new ArrayList(); + + /** For internal use only. May be removed or replaced in the future. */ + public boolean immediate; + + /** For internal use only. May be removed or replaced in the future. */ + public String selectedOptionKey; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean waitingForFilteringResponse = false; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean updateSelectionWhenReponseIsReceived = false; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean initDone = false; + + /** For internal use only. May be removed or replaced in the future. */ + public String lastFilter = ""; + + /** For internal use only. May be removed or replaced in the future. */ + public enum Select { + NONE, FIRST, LAST + } + + /** For internal use only. May be removed or replaced in the future. */ + public Select selectPopupItemWhenResponseIsReceived = Select.NONE; + + /** + * The current suggestion selected from the dropdown. This is one of the + * values in currentSuggestions except when filtering, in this case + * currentSuggestion might not be in currentSuggestions. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public FilterSelectSuggestion currentSuggestion; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean allowNewItem; + + /** For internal use only. May be removed or replaced in the future. */ + public int totalMatches; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean nullSelectionAllowed; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean nullSelectItem; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean enabled; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean readonly; + + /** For internal use only. May be removed or replaced in the future. */ + public FilteringMode filteringmode = FilteringMode.OFF; + + // shown in unfocused empty field, disappears on focus (e.g "Search here") + private static final String CLASSNAME_PROMPT = "prompt"; + + /** For internal use only. May be removed or replaced in the future. */ + public String inputPrompt = ""; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean prompting = false; + + /** + * Set true when popupopened has been clicked. Cleared on each UIDL-update. + * This handles the special case where are not filtering yet and the + * selected value has changed on the server-side. See #2119 + *

+ * For internal use only. May be removed or replaced in the future. + */ + public boolean popupOpenerClicked; + + /** For internal use only. May be removed or replaced in the future. */ + public int suggestionPopupMinWidth = 0; + + private int popupWidth = -1; + /** + * Stores the last new item string to avoid double submissions. Cleared on + * uidl updates. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public String lastNewItemString; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean focused = false; + + /** + * If set to false, the component should not allow entering text to the + * field even for filtering. + */ + private boolean textInputEnabled = true; + + /** + * Default constructor. + */ + public VFilterSelect() { + tb = createTextBox(); + suggestionPopup = createSuggestionPopup(); + + popupOpener.sinkEvents(Event.ONMOUSEDOWN); + Roles.getButtonRole() + .setAriaHiddenState(popupOpener.getElement(), true); + Roles.getButtonRole().set(popupOpener.getElement()); + + panel.add(tb); + panel.add(popupOpener); + initWidget(panel); + Roles.getComboboxRole().set(panel.getElement()); + + tb.addKeyDownHandler(this); + tb.addKeyUpHandler(this); + + tb.addFocusHandler(this); + tb.addBlurHandler(this); + tb.addClickHandler(this); + + popupOpener.addClickHandler(this); + + setStyleName(CLASSNAME); + + sinkEvents(Event.ONPASTE); + } + + private static double getMarginBorderPaddingWidth(Element element) { + final ComputedStyle s = new ComputedStyle(element); + return s.getMarginWidth() + s.getBorderWidth() + s.getPaddingWidth(); + + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.user.client.ui.Composite#onBrowserEvent(com.google.gwt + * .user.client.Event) + */ + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + + if (event.getTypeInt() == Event.ONPASTE) { + if (textInputEnabled) { + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + + @Override + public void execute() { + filterOptions(currentPage); + } + }); + } + } + } + + /** + * This method will create the TextBox used by the VFilterSelect instance. + * It is invoked during the Constructor and should only be overridden if a + * custom TextBox shall be used. The overriding method cannot use any + * instance variables. + * + * @since 7.1.5 + * @return TextBox instance used by this VFilterSelect + */ + protected TextBox createTextBox() { + return new FilterSelectTextBox(); + } + + /** + * This method will create the SuggestionPopup used by the VFilterSelect + * instance. It is invoked during the Constructor and should only be + * overridden if a custom SuggestionPopup shall be used. The overriding + * method cannot use any instance variables. + * + * @since 7.1.5 + * @return SuggestionPopup instance used by this VFilterSelect + */ + protected SuggestionPopup createSuggestionPopup() { + return new SuggestionPopup(); + } + + @Override + public void setStyleName(String style) { + super.setStyleName(style); + updateStyleNames(); + } + + @Override + public void setStylePrimaryName(String style) { + super.setStylePrimaryName(style); + updateStyleNames(); + } + + protected void updateStyleNames() { + tb.setStyleName(getStylePrimaryName() + "-input"); + popupOpener.setStyleName(getStylePrimaryName() + "-button"); + suggestionPopup.setStyleName(getStylePrimaryName() + "-suggestpopup"); + } + + /** + * Does the Select have more pages? + * + * @return true if a next page exists, else false if the current page is the + * last page + */ + public boolean hasNextPage() { + if (pageLength > 0 && totalMatches > (currentPage + 1) * pageLength) { + return true; + } else { + return false; + } + } + + /** + * Filters the options at a certain page. Uses the text box input as a + * filter + * + * @param page + * The page which items are to be filtered + */ + public void filterOptions(int page) { + filterOptions(page, tb.getText()); + } + + /** + * Filters the options at certain page using the given filter + * + * @param page + * The page to filter + * @param filter + * The filter to apply to the components + */ + public void filterOptions(int page, String filter) { + filterOptions(page, filter, true); + } + + /** + * Filters the options at certain page using the given filter + * + * @param page + * The page to filter + * @param filter + * The filter to apply to the options + * @param immediate + * Whether to send the options request immediately + */ + private void filterOptions(int page, String filter, boolean immediate) { + debug("VFS: filterOptions(" + page + ", " + filter + ", " + immediate + + ")"); + + if (filter.equals(lastFilter) && currentPage == page) { + if (!suggestionPopup.isAttached()) { + suggestionPopup.showSuggestions(currentSuggestions, + currentPage, totalMatches); + } + return; + } + if (!filter.equals(lastFilter)) { + // when filtering, let the server decide the page unless we've + // set the filter to empty and explicitly said that we want to see + // the results starting from page 0. + if ("".equals(filter) && page != 0) { + // let server decide + page = -1; + } else { + page = 0; + } + } + + waitingForFilteringResponse = true; + client.updateVariable(paintableId, "filter", filter, false); + client.updateVariable(paintableId, "page", page, immediate); + afterUpdateClientVariables(); + + lastFilter = filter; + currentPage = page; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateReadOnly() { + debug("VFS: updateReadOnly()"); + tb.setReadOnly(readonly || !textInputEnabled); + } + + public void setTextInputEnabled(boolean textInputEnabled) { + debug("VFS: setTextInputEnabled()"); + // Always update styles as they might have been overwritten + if (textInputEnabled) { + removeStyleDependentName(STYLE_NO_INPUT); + Roles.getTextboxRole().removeAriaReadonlyProperty(tb.getElement()); + } else { + addStyleDependentName(STYLE_NO_INPUT); + Roles.getTextboxRole().setAriaReadonlyProperty(tb.getElement(), + true); + } + + if (this.textInputEnabled == textInputEnabled) { + return; + } + + this.textInputEnabled = textInputEnabled; + updateReadOnly(); + } + + /** + * Sets the text in the text box. + * + * @param text + * the text to set in the text box + */ + public void setTextboxText(final String text) { + if (enableDebug) { + debug("VFS: setTextboxText(" + text + ")"); + } + setText(text); + } + + private void setText(final String text) { + /** + * To leave caret in the beginning of the line. SetSelectionRange + * wouldn't work on IE (see #13477) + */ + Direction previousDirection = tb.getDirection(); + tb.setDirection(Direction.RTL); + tb.setText(text); + tb.setDirection(previousDirection); + } + + /** + * Turns prompting on. When prompting is turned on a command prompt is shown + * in the text box if nothing has been entered. + */ + public void setPromptingOn() { + debug("VFS: setPromptingOn()"); + if (!prompting) { + prompting = true; + addStyleDependentName(CLASSNAME_PROMPT); + } + setTextboxText(inputPrompt); + } + + /** + * Turns prompting off. When prompting is turned on a command prompt is + * shown in the text box if nothing has been entered. + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param text + * The text the text box should contain. + */ + public void setPromptingOff(String text) { + debug("VFS: setPromptingOff()"); + setTextboxText(text); + if (prompting) { + prompting = false; + removeStyleDependentName(CLASSNAME_PROMPT); + } + } + + /** + * Triggered when a suggestion is selected + * + * @param suggestion + * The suggestion that just got selected. + */ + public void onSuggestionSelected(FilterSelectSuggestion suggestion) { + if (enableDebug) { + debug("VFS: onSuggestionSelected(" + suggestion.caption + ": " + + suggestion.key + ")"); + } + updateSelectionWhenReponseIsReceived = false; + + currentSuggestion = suggestion; + String newKey; + if (suggestion.key.equals("")) { + // "nullselection" + newKey = ""; + } else { + // normal selection + newKey = suggestion.getOptionKey(); + } + + String text = suggestion.getReplacementString(); + if ("".equals(newKey) && !focused) { + setPromptingOn(); + } else { + setPromptingOff(text); + } + setSelectedItemIcon(suggestion.getIconUri()); + + if (!(newKey.equals(selectedOptionKey) || ("".equals(newKey) && selectedOptionKey == null))) { + selectedOptionKey = newKey; + client.updateVariable(paintableId, "selected", + new String[] { selectedOptionKey }, immediate); + afterUpdateClientVariables(); + + // currentPage = -1; // forget the page + } + suggestionPopup.hide(); + } + + /** + * Sets the icon URI of the selected item. The icon is shown on the left + * side of the item caption text. Set the URI to null to remove the icon. + * + * @param iconUri + * The URI of the icon + */ + public void setSelectedItemIcon(String iconUri) { + + if (iconUri == null || iconUri.length() == 0) { + if (selectedItemIcon != null) { + panel.remove(selectedItemIcon); + selectedItemIcon = null; + afterSelectedItemIconChange(); + } + } else { + if (selectedItemIcon != null) { + panel.remove(selectedItemIcon); + } + selectedItemIcon = new IconWidget(client.getIcon(iconUri)); + // Older IE versions don't scale icon correctly if DOM + // contains height and width attributes. + selectedItemIcon.getElement().removeAttribute("height"); + selectedItemIcon.getElement().removeAttribute("width"); + selectedItemIcon.addDomHandler(new LoadHandler() { + @Override + public void onLoad(LoadEvent event) { + afterSelectedItemIconChange(); + } + }, LoadEvent.getType()); + panel.insert(selectedItemIcon, 0); + afterSelectedItemIconChange(); + } + } + + private void afterSelectedItemIconChange() { + if (BrowserInfo.get().isWebkit() || BrowserInfo.get().isIE8()) { + // Some browsers need a nudge to reposition the text field + forceReflow(); + } + updateRootWidth(); + if (selectedItemIcon != null) { + updateSelectedIconPosition(); + } + } + + private void forceReflow() { + WidgetUtil.setStyleTemporarily(tb.getElement(), "zoom", "1"); + } + + /** + * Positions the icon vertically in the middle. Should be called after the + * icon has loaded + */ + private void updateSelectedIconPosition() { + // Position icon vertically to middle + int availableHeight = 0; + availableHeight = getOffsetHeight(); + + int iconHeight = WidgetUtil.getRequiredHeight(selectedItemIcon); + int marginTop = (availableHeight - iconHeight) / 2; + selectedItemIcon.getElement().getStyle() + .setMarginTop(marginTop, Unit.PX); + } + + private static Set navigationKeyCodes = new HashSet(); + static { + navigationKeyCodes.add(KeyCodes.KEY_DOWN); + navigationKeyCodes.add(KeyCodes.KEY_UP); + navigationKeyCodes.add(KeyCodes.KEY_PAGEDOWN); + navigationKeyCodes.add(KeyCodes.KEY_PAGEUP); + navigationKeyCodes.add(KeyCodes.KEY_ENTER); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt + * .event.dom.client.KeyDownEvent) + */ + + @Override + public void onKeyDown(KeyDownEvent event) { + if (enabled && !readonly) { + int keyCode = event.getNativeKeyCode(); + + if (enableDebug) { + debug("VFS: key down: " + keyCode); + } + if (waitingForFilteringResponse + && navigationKeyCodes.contains(keyCode)) { + /* + * Keyboard navigation events should not be handled while we are + * waiting for a response. This avoids flickering, disappearing + * items, wrongly interpreted responses and more. + */ + if (enableDebug) { + debug("Ignoring " + + keyCode + + " because we are waiting for a filtering response"); + } + DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); + event.stopPropagation(); + return; + } + + if (suggestionPopup.isAttached()) { + if (enableDebug) { + debug("Keycode " + keyCode + " target is popup"); + } + popupKeyDown(event); + } else { + if (enableDebug) { + debug("Keycode " + keyCode + " target is text field"); + } + inputFieldKeyDown(event); + } + } + } + + private void debug(String string) { + if (enableDebug) { + VConsole.error(string); + } + } + + /** + * Triggered when a key is pressed in the text box + * + * @param event + * The KeyDownEvent + */ + private void inputFieldKeyDown(KeyDownEvent event) { + if (enableDebug) { + debug("VFS: inputFieldKeyDown(" + event.getNativeKeyCode() + ")"); + } + switch (event.getNativeKeyCode()) { + case KeyCodes.KEY_DOWN: + case KeyCodes.KEY_UP: + case KeyCodes.KEY_PAGEDOWN: + case KeyCodes.KEY_PAGEUP: + // open popup as from gadget + filterOptions(-1, ""); + lastFilter = ""; + tb.selectAll(); + break; + case KeyCodes.KEY_ENTER: + /* + * This only handles the case when new items is allowed, a text is + * entered, the popup opener button is clicked to close the popup + * and enter is then pressed (see #7560). + */ + if (!allowNewItem) { + return; + } + + if (currentSuggestion != null + && tb.getText().equals( + currentSuggestion.getReplacementString())) { + // Retain behavior from #6686 by returning without stopping + // propagation if there's nothing to do + return; + } + suggestionPopup.menu.doSelectedItemAction(); + + event.stopPropagation(); + break; + } + + } + + /** + * Triggered when a key was pressed in the suggestion popup. + * + * @param event + * The KeyDownEvent of the key + */ + private void popupKeyDown(KeyDownEvent event) { + if (enableDebug) { + debug("VFS: popupKeyDown(" + event.getNativeKeyCode() + ")"); + } + // Propagation of handled events is stopped so other handlers such as + // shortcut key handlers do not also handle the same events. + switch (event.getNativeKeyCode()) { + case KeyCodes.KEY_DOWN: + suggestionPopup.selectNextItem(); + + DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); + event.stopPropagation(); + break; + case KeyCodes.KEY_UP: + suggestionPopup.selectPrevItem(); + + DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); + event.stopPropagation(); + break; + case KeyCodes.KEY_PAGEDOWN: + selectNextPage(); + event.stopPropagation(); + break; + case KeyCodes.KEY_PAGEUP: + selectPrevPage(); + event.stopPropagation(); + break; + case KeyCodes.KEY_ESCAPE: + reset(); + DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); + event.stopPropagation(); + break; + case KeyCodes.KEY_TAB: + case KeyCodes.KEY_ENTER: + + if (!allowNewItem) { + int selected = suggestionPopup.menu.getSelectedIndex(); + if (selected != -1) { + onSuggestionSelected(currentSuggestions.get(selected)); + } else { + // The way VFilterSelect is done, it handles enter and tab + // in exactly the same way so we close the popup in both + // cases even though we could leave it open when pressing + // enter + suggestionPopup.hide(); + } + } else { + // Handle addition of new items. + suggestionPopup.menu.doSelectedItemAction(); + } + + event.stopPropagation(); + break; + } + + } + + /* + * Show the prev page. + */ + private void selectPrevPage() { + if (currentPage > 0) { + filterOptions(currentPage - 1, lastFilter); + selectPopupItemWhenResponseIsReceived = Select.LAST; + } + } + + /* + * Show the next page. + */ + private void selectNextPage() { + if (hasNextPage()) { + filterOptions(currentPage + 1, lastFilter); + selectPopupItemWhenResponseIsReceived = Select.FIRST; + } + } + + /** + * Triggered when a key was depressed + * + * @param event + * The KeyUpEvent of the key depressed + */ + + @Override + public void onKeyUp(KeyUpEvent event) { + if (enableDebug) { + debug("VFS: onKeyUp(" + event.getNativeKeyCode() + ")"); + } + if (enabled && !readonly) { + switch (event.getNativeKeyCode()) { + case KeyCodes.KEY_ENTER: + case KeyCodes.KEY_TAB: + case KeyCodes.KEY_SHIFT: + case KeyCodes.KEY_CTRL: + case KeyCodes.KEY_ALT: + case KeyCodes.KEY_DOWN: + case KeyCodes.KEY_UP: + case KeyCodes.KEY_PAGEDOWN: + case KeyCodes.KEY_PAGEUP: + case KeyCodes.KEY_ESCAPE: + // NOP + break; + default: + if (textInputEnabled) { + // when filtering, we always want to see the results on the + // first page first. + filterOptions(0); + } + break; + } + } + } + + /** + * Resets the Select to its initial state + */ + private void reset() { + debug("VFS: reset()"); + if (currentSuggestion != null) { + String text = currentSuggestion.getReplacementString(); + setPromptingOff(text); + setSelectedItemIcon(currentSuggestion.getIconUri()); + + selectedOptionKey = currentSuggestion.key; + + } else { + if (focused || readonly || !enabled) { + setPromptingOff(""); + } else { + setPromptingOn(); + } + setSelectedItemIcon(null); + + selectedOptionKey = null; + } + + lastFilter = ""; + suggestionPopup.hide(); + } + + /** + * Listener for popupopener + */ + + @Override + public void onClick(ClickEvent event) { + debug("VFS: onClick()"); + if (textInputEnabled + && event.getNativeEvent().getEventTarget().cast() == tb + .getElement()) { + // Don't process clicks on the text field if text input is enabled + return; + } + if (enabled && !readonly) { + // ask suggestionPopup if it was just closed, we are using GWT + // Popup's auto close feature + if (!suggestionPopup.isJustClosed()) { + // If a focus event is not going to be sent, send the options + // request immediately; otherwise queue in the same burst as the + // focus event. Fixes #8321. + boolean immediate = focused + || !client.hasEventListeners(this, EventId.FOCUS); + filterOptions(-1, "", immediate); + popupOpenerClicked = true; + lastFilter = ""; + } + DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); + focus(); + tb.selectAll(); + } + } + + /** + * Update minimum width for FilterSelect textarea based on input prompt and + * suggestions. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public void updateSuggestionPopupMinWidth() { + // used only to calculate minimum width + String captions = WidgetUtil.escapeHTML(inputPrompt); + + for (FilterSelectSuggestion suggestion : currentSuggestions) { + // Collect captions so we can calculate minimum width for + // textarea + if (captions.length() > 0) { + captions += "|"; + } + captions += WidgetUtil + .escapeHTML(suggestion.getReplacementString()); + } + + // Calculate minimum textarea width + suggestionPopupMinWidth = minWidth(captions); + } + + /** + * Calculate minimum width for FilterSelect textarea. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public native int minWidth(String captions) + /*-{ + if(!captions || captions.length <= 0) + return 0; + captions = captions.split("|"); + var d = $wnd.document.createElement("div"); + var html = ""; + for(var i=0; i < captions.length; i++) { + html += "

" + captions[i] + "
"; + // TODO apply same CSS classname as in suggestionmenu + } + d.style.position = "absolute"; + d.style.top = "0"; + d.style.left = "0"; + d.style.visibility = "hidden"; + d.innerHTML = html; + $wnd.document.body.appendChild(d); + var w = d.offsetWidth; + $wnd.document.body.removeChild(d); + return w; + }-*/; + + /** + * A flag which prevents a focus event from taking place + */ + boolean iePreventNextFocus = false; + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event + * .dom.client.FocusEvent) + */ + + @Override + public void onFocus(FocusEvent event) { + debug("VFS: onFocus()"); + + /* + * When we disable a blur event in ie we need to refocus the textfield. + * This will cause a focus event we do not want to process, so in that + * case we just ignore it. + */ + if (BrowserInfo.get().isIE() && iePreventNextFocus) { + iePreventNextFocus = false; + return; + } + + focused = true; + if (prompting && !readonly) { + setPromptingOff(""); + } + addStyleDependentName("focus"); + + if (client.hasEventListeners(this, EventId.FOCUS)) { + client.updateVariable(paintableId, EventId.FOCUS, "", true); + afterUpdateClientVariables(); + } + + ComponentConnector connector = ConnectorMap.get(client).getConnector( + this); + client.getVTooltip().showAssistive( + connector.getTooltipInfo(getElement())); + } + + /** + * A flag which cancels the blur event and sets the focus back to the + * textfield if the Browser is IE + */ + boolean preventNextBlurEventInIE = false; + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event + * .dom.client.BlurEvent) + */ + + @Override + public void onBlur(BlurEvent event) { + debug("VFS: onBlur()"); + + if (BrowserInfo.get().isIE() && preventNextBlurEventInIE) { + /* + * Clicking in the suggestion popup or on the popup button in IE + * causes a blur event to be sent for the field. In other browsers + * this is prevented by canceling/preventing default behavior for + * the focus event, in IE we handle it here by refocusing the text + * field and ignoring the resulting focus event for the textfield + * (in onFocus). + */ + preventNextBlurEventInIE = false; + + Element focusedElement = WidgetUtil.getFocusedElement(); + if (getElement().isOrHasChild(focusedElement) + || suggestionPopup.getElement() + .isOrHasChild(focusedElement)) { + + // IF the suggestion popup or another part of the VFilterSelect + // was focused, move the focus back to the textfield and prevent + // the triggered focus event (in onFocus). + iePreventNextFocus = true; + tb.setFocus(true); + return; + } + } + + focused = false; + if (!readonly) { + if (selectedOptionKey == null) { + setPromptingOn(); + } else if (currentSuggestion != null) { + setPromptingOff(currentSuggestion.caption); + } + } + removeStyleDependentName("focus"); + + if (client.hasEventListeners(this, EventId.BLUR)) { + client.updateVariable(paintableId, EventId.BLUR, "", true); + afterUpdateClientVariables(); + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.Focusable#focus() + */ + + @Override + public void focus() { + debug("VFS: focus()"); + focused = true; + if (prompting && !readonly) { + setPromptingOff(""); + } + tb.setFocus(true); + } + + /** + * Calculates the width of the select if the select has undefined width. + * Should be called when the width changes or when the icon changes. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public void updateRootWidth() { + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + + if (paintable.isUndefinedWidth()) { + + /* + * When the select has a undefined with we need to check that we are + * only setting the text box width relative to the first page width + * of the items. If this is not done the text box width will change + * when the popup is used to view longer items than the text box is + * wide. + */ + int w = WidgetUtil.getRequiredWidth(this); + + if ((!initDone || currentPage + 1 < 0) + && suggestionPopupMinWidth > w) { + /* + * We want to compensate for the paddings just to preserve the + * exact size as in Vaadin 6.x, but we get here before + * MeasuredSize has been initialized. + * Util.measureHorizontalPaddingAndBorder does not work with + * border-box, so we must do this the hard way. + */ + Style style = getElement().getStyle(); + String originalPadding = style.getPadding(); + String originalBorder = style.getBorderWidth(); + style.setPaddingLeft(0, Unit.PX); + style.setBorderWidth(0, Unit.PX); + style.setProperty("padding", originalPadding); + style.setProperty("borderWidth", originalBorder); + + // Use util.getRequiredWidth instead of getOffsetWidth here + + int iconWidth = selectedItemIcon == null ? 0 : WidgetUtil + .getRequiredWidth(selectedItemIcon); + int buttonWidth = popupOpener == null ? 0 : WidgetUtil + .getRequiredWidth(popupOpener); + + /* + * Instead of setting the width of the wrapper, set the width of + * the combobox. Subtract the width of the icon and the + * popupopener + */ + + tb.setWidth((suggestionPopupMinWidth - iconWidth - buttonWidth) + + "px"); + + } + + /* + * Lock the textbox width to its current value if it's not already + * locked + */ + if (!tb.getElement().getStyle().getWidth().endsWith("px")) { + int iconWidth = selectedItemIcon == null ? 0 : selectedItemIcon + .getOffsetWidth(); + tb.setWidth((tb.getOffsetWidth() - iconWidth) + "px"); + } + } + } + + /** + * Get the width of the select in pixels where the text area and icon has + * been included. + * + * @return The width in pixels + */ + private int getMainWidth() { + return getOffsetWidth(); + } + + @Override + public void setWidth(String width) { + super.setWidth(width); + if (width.length() != 0) { + tb.setWidth("100%"); + } + } + + /** + * Handles special behavior of the mouse down event + * + * @param event + */ + private void handleMouseDownEvent(Event event) { + /* + * Prevent the keyboard focus from leaving the textfield by preventing + * the default behaviour of the browser. Fixes #4285. + */ + if (event.getTypeInt() == Event.ONMOUSEDOWN) { + event.preventDefault(); + event.stopPropagation(); + + /* + * In IE the above wont work, the blur event will still trigger. So, + * we set a flag here to prevent the next blur event from happening. + * This is not needed if do not already have focus, in that case + * there will not be any blur event and we should not cancel the + * next blur. + */ + if (BrowserInfo.get().isIE() && focused) { + preventNextBlurEventInIE = true; + debug("VFS: Going to prevent next blur event on IE"); + } + } + } + + @Override + protected void onDetach() { + super.onDetach(); + suggestionPopup.hide(); + } + + @Override + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + String[] parts = subPart.split("/"); + if ("textbox".equals(parts[0])) { + return tb.getElement(); + } else if ("button".equals(parts[0])) { + return popupOpener.getElement(); + } else if ("popup".equals(parts[0]) && suggestionPopup.isAttached()) { + if (parts.length == 2) { + return suggestionPopup.menu.getSubPartElement(parts[1]); + } + return suggestionPopup.getElement(); + } + return null; + } + + @Override + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + if (tb.getElement().isOrHasChild(subElement)) { + return "textbox"; + } else if (popupOpener.getElement().isOrHasChild(subElement)) { + return "button"; + } else if (suggestionPopup.getElement().isOrHasChild(subElement)) { + return "popup"; + } + return null; + } + + @Override + public void setAriaRequired(boolean required) { + AriaHelper.handleInputRequired(tb, required); + } + + @Override + public void setAriaInvalid(boolean invalid) { + AriaHelper.handleInputInvalid(tb, invalid); + } + + @Override + public void bindAriaCaption( + com.google.gwt.user.client.Element captionElement) { + AriaHelper.bindCaption(tb, captionElement); + } + + /* + * Anything that should be set after the client updates the server. + */ + private void afterUpdateClientVariables() { + // We need this here to be consistent with the all the calls. + // Then set your specific selection type only after + // client.updateVariable() method call. + selectPopupItemWhenResponseIsReceived = Select.NONE; + } + + @Override + public boolean isWorkPending() { + return waitingForFilteringResponse + || suggestionPopup.lazyPageScroller.isRunning(); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VFlash.java b/client/src/main/java/com/vaadin/client/ui/VFlash.java new file mode 100644 index 0000000000..eaf53836ee --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VFlash.java @@ -0,0 +1,250 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui; + +import java.util.HashMap; +import java.util.Map; + +import com.google.gwt.user.client.ui.HTML; +import com.vaadin.client.WidgetUtil; + +public class VFlash extends HTML { + + public static final String CLASSNAME = "v-flash"; + + protected String source; + protected String altText; + protected String classId; + protected String codebase; + protected String codetype; + protected String standby; + protected String archive; + protected Map embedParams = new HashMap(); + protected boolean needsRebuild = false; + protected String width; + protected String height; + + public VFlash() { + setStyleName(CLASSNAME); + } + + public void setSource(String source) { + if (this.source != source) { + this.source = source; + needsRebuild = true; + } + } + + public void setAlternateText(String altText) { + if (this.altText != altText) { + this.altText = altText; + needsRebuild = true; + } + } + + public void setClassId(String classId) { + if (this.classId != classId) { + this.classId = classId; + needsRebuild = true; + } + } + + public void setCodebase(String codebase) { + if (this.codebase != codebase) { + this.codebase = codebase; + needsRebuild = true; + } + } + + public void setCodetype(String codetype) { + if (this.codetype != codetype) { + this.codetype = codetype; + needsRebuild = true; + } + } + + public void setStandby(String standby) { + if (this.standby != standby) { + this.standby = standby; + needsRebuild = true; + } + } + + public void setArchive(String archive) { + if (this.archive != archive) { + this.archive = archive; + needsRebuild = true; + } + } + + /** + * Call this after changing values of widget. It will rebuild embedding + * structure if needed. + */ + public void rebuildIfNeeded() { + if (needsRebuild) { + needsRebuild = false; + this.setHTML(createFlashEmbed()); + } + } + + @Override + public void setWidth(String width) { + // super.setWidth(height); + + if (this.width != width) { + this.width = width; + needsRebuild = true; + } + } + + @Override + public void setHeight(String height) { + // super.setHeight(height); + + if (this.height != height) { + this.height = height; + needsRebuild = true; + } + } + + public void setEmbedParams(Map params) { + if (params == null) { + if (!embedParams.isEmpty()) { + embedParams.clear(); + needsRebuild = true; + } + return; + } + + if (!embedParams.equals(params)) { + embedParams = new HashMap(params); + needsRebuild = true; + } + } + + protected String createFlashEmbed() { + /* + * To ensure cross-browser compatibility we are using the twice-cooked + * method to embed flash i.e. we add a OBJECT tag for IE ActiveX and + * inside it a EMBED for all other browsers. + */ + + StringBuilder html = new StringBuilder(); + + // Start the object tag + html.append(""); + + // Ensure we have an movie parameter + if (embedParams.get("movie") == null) { + embedParams.put("movie", source); + } + + // Add parameters to OBJECT + for (String name : embedParams.keySet()) { + html.append(""); + } + + // Build inner EMBED tag + html.append(""); + + if (altText != null) { + html.append(""); + html.append(altText); + html.append(""); + } + + // End object tag + html.append(""); + + return html.toString(); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VForm.java b/client/src/main/java/com/vaadin/client/ui/VForm.java new file mode 100644 index 0000000000..ca38ea070b --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VForm.java @@ -0,0 +1,139 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.VErrorMessage; + +public class VForm extends ComplexPanel implements KeyDownHandler { + + public static final String CLASSNAME = "v-form"; + + /** For internal use only. May be removed or replaced in the future. */ + public String id; + + /** For internal use only. May be removed or replaced in the future. */ + public Widget lo; + + /** For internal use only. May be removed or replaced in the future. */ + public Element legend = DOM.createLegend(); + + /** For internal use only. May be removed or replaced in the future. */ + public Element caption = DOM.createSpan(); + + /** For internal use only. May be removed or replaced in the future. */ + public Element desc = DOM.createDiv(); + + /** For internal use only. May be removed or replaced in the future. */ + public Icon icon; + + /** For internal use only. May be removed or replaced in the future. */ + public VErrorMessage errorMessage = new VErrorMessage(); + + /** For internal use only. May be removed or replaced in the future. */ + public Element fieldContainer = DOM.createDiv(); + + /** For internal use only. May be removed or replaced in the future. */ + public Element footerContainer = DOM.createDiv(); + + /** For internal use only. May be removed or replaced in the future. */ + public Element fieldSet = DOM.createFieldSet(); + + /** For internal use only. May be removed or replaced in the future. */ + public Widget footer; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public ShortcutActionHandler shortcutHandler; + + /** For internal use only. May be removed or replaced in the future. */ + public HandlerRegistration keyDownRegistration; + + public VForm() { + setElement(DOM.createDiv()); + getElement().appendChild(fieldSet); + setStyleName(CLASSNAME); + fieldSet.appendChild(legend); + legend.appendChild(caption); + + fieldSet.appendChild(desc); // Adding description for initial padding + // measurements, removed later if no + // description is set + + fieldSet.appendChild(fieldContainer); + errorMessage.setVisible(false); + + fieldSet.appendChild(errorMessage.getElement()); + fieldSet.appendChild(footerContainer); + + errorMessage.setOwner(this); + } + + @Override + public void setStyleName(String style) { + super.setStyleName(style); + updateStyleNames(); + } + + @Override + public void setStylePrimaryName(String style) { + super.setStylePrimaryName(style); + updateStyleNames(); + } + + protected void updateStyleNames() { + fieldContainer.setClassName(getStylePrimaryName() + "-content"); + errorMessage.setStyleName(getStylePrimaryName() + "-errormessage"); + desc.setClassName(getStylePrimaryName() + "-description"); + footerContainer.setClassName(getStylePrimaryName() + "-footer"); + } + + @Override + public void onKeyDown(KeyDownEvent event) { + shortcutHandler.handleKeyboardEvent(Event.as(event.getNativeEvent())); + } + + public void setFooterWidget(Widget footerWidget) { + if (footer != null) { + remove(footer); + } + if (footerWidget != null) { + super.add(footerWidget, footerContainer); + } + footer = footerWidget; + } + + public void setLayoutWidget(Widget newLayoutWidget) { + if (lo != null) { + remove(lo); + } + if (newLayoutWidget != null) { + super.add(newLayoutWidget, fieldContainer); + } + lo = newLayoutWidget; + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VFormLayout.java b/client/src/main/java/com/vaadin/client/ui/VFormLayout.java new file mode 100644 index 0000000000..3719688f2b --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VFormLayout.java @@ -0,0 +1,392 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import com.google.gwt.aria.client.Roles; +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.FlexTable; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.Focusable; +import com.vaadin.client.StyleConstants; +import com.vaadin.client.VTooltip; +import com.vaadin.client.ui.aria.AriaHelper; +import com.vaadin.shared.AbstractComponentState; +import com.vaadin.shared.ComponentConstants; +import com.vaadin.shared.ui.ComponentStateUtil; +import com.vaadin.shared.ui.MarginInfo; + +/** + * Two col Layout that places caption on left col and field on right col + */ +public class VFormLayout extends SimplePanel { + + private final static String CLASSNAME = "v-formlayout"; + + /** For internal use only. May be removed or replaced in the future. */ + public VFormLayoutTable table; + + public VFormLayout() { + super(); + setStyleName(CLASSNAME); + addStyleName(StyleConstants.UI_LAYOUT); + table = new VFormLayoutTable(); + setWidget(table); + } + + /** + * Parses the stylenames from shared state + * + * @param state + * shared state of the component + * @param enabled + * @return An array of stylenames + */ + private String[] getStylesFromState(AbstractComponentState state, + boolean enabled) { + List styles = new ArrayList(); + if (ComponentStateUtil.hasStyles(state)) { + for (String name : state.styles) { + styles.add(name); + } + } + + if (!enabled) { + styles.add(StyleConstants.DISABLED); + } + + return styles.toArray(new String[styles.size()]); + } + + public class VFormLayoutTable extends FlexTable implements ClickHandler { + + private static final int COLUMN_CAPTION = 0; + private static final int COLUMN_ERRORFLAG = 1; + public static final int COLUMN_WIDGET = 2; + + private HashMap widgetToCaption = new HashMap(); + private HashMap widgetToError = new HashMap(); + + public VFormLayoutTable() { + DOM.setElementProperty(getElement(), "cellPadding", "0"); + DOM.setElementProperty(getElement(), "cellSpacing", "0"); + + Roles.getPresentationRole().set(getElement()); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt + * .event.dom.client.ClickEvent) + */ + @Override + public void onClick(ClickEvent event) { + Caption caption = (Caption) event.getSource(); + if (caption.getOwner() != null) { + if (caption.getOwner() instanceof Focusable) { + ((Focusable) caption.getOwner()).focus(); + } else if (caption.getOwner() instanceof com.google.gwt.user.client.ui.Focusable) { + ((com.google.gwt.user.client.ui.Focusable) caption + .getOwner()).setFocus(true); + } + } + } + + public void setMargins(MarginInfo margins) { + Element margin = getElement(); + setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_TOP, + margins.hasTop()); + setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT, + margins.hasRight()); + setStyleName(margin, + CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM, + margins.hasBottom()); + setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_LEFT, + margins.hasLeft()); + + } + + public void setSpacing(boolean spacing) { + setStyleName(getElement(), CLASSNAME + "-" + "spacing", spacing); + + } + + public void setRowCount(int rowNr) { + for (int i = 0; i < rowNr; i++) { + prepareCell(i, COLUMN_CAPTION); + getCellFormatter().setStyleName(i, COLUMN_CAPTION, + CLASSNAME + "-captioncell"); + + prepareCell(i, 1); + getCellFormatter().setStyleName(i, COLUMN_ERRORFLAG, + CLASSNAME + "-errorcell"); + + prepareCell(i, 2); + getCellFormatter().setStyleName(i, COLUMN_WIDGET, + CLASSNAME + "-contentcell"); + + String rowstyles = CLASSNAME + "-row"; + if (i == 0) { + rowstyles += " " + CLASSNAME + "-firstrow"; + } + if (i == rowNr - 1) { + rowstyles += " " + CLASSNAME + "-lastrow"; + } + + getRowFormatter().setStyleName(i, rowstyles); + + } + while (getRowCount() != rowNr) { + removeRow(rowNr); + } + } + + public void setChild(int rowNr, Widget childWidget, Caption caption, + ErrorFlag error) { + setWidget(rowNr, COLUMN_WIDGET, childWidget); + setWidget(rowNr, COLUMN_CAPTION, caption); + setWidget(rowNr, COLUMN_ERRORFLAG, error); + + widgetToCaption.put(childWidget, caption); + widgetToError.put(childWidget, error); + + } + + public Caption getCaption(Widget childWidget) { + return widgetToCaption.get(childWidget); + } + + public ErrorFlag getError(Widget childWidget) { + return widgetToError.get(childWidget); + } + + public void cleanReferences(Widget oldChildWidget) { + widgetToError.remove(oldChildWidget); + widgetToCaption.remove(oldChildWidget); + + } + + public void updateCaption(Widget widget, AbstractComponentState state, + boolean enabled) { + final Caption c = widgetToCaption.get(widget); + if (c != null) { + c.updateCaption(state, enabled); + } + } + + public void updateError(Widget widget, String errorMessage, + boolean hideErrors) { + final ErrorFlag e = widgetToError.get(widget); + if (e != null) { + e.updateError(errorMessage, hideErrors); + } + + } + + } + + // TODO why duplicated here? + public class Caption extends HTML { + + public static final String CLASSNAME = "v-caption"; + + private final ComponentConnector owner; + + private Element requiredFieldIndicator; + + private Icon icon; + + private Element captionContent; + + /** + * + * @param component + * optional owner of caption. If not set, getOwner will + * return null + */ + public Caption(ComponentConnector component) { + super(); + owner = component; + } + + private void setStyles(String[] styles) { + String styleName = CLASSNAME; + + if (styles != null) { + for (String style : styles) { + if (StyleConstants.DISABLED.equals(style)) { + // Add v-disabled also without classname prefix so + // generic v-disabled CSS rules work + styleName += " " + style; + } + + styleName += " " + CLASSNAME + "-" + style; + } + } + + setStyleName(styleName); + } + + public void updateCaption(AbstractComponentState state, boolean enabled) { + // Update styles as they might have changed when the caption changed + setStyles(getStylesFromState(state, enabled)); + + boolean isEmpty = true; + + if (icon != null) { + getElement().removeChild(icon.getElement()); + icon = null; + } + if (state.resources.containsKey(ComponentConstants.ICON_RESOURCE)) { + icon = owner.getConnection().getIcon( + state.resources.get(ComponentConstants.ICON_RESOURCE) + .getURL()); + DOM.insertChild(getElement(), icon.getElement(), 0); + + isEmpty = false; + } + + if (state.caption != null) { + if (captionContent == null) { + captionContent = DOM.createSpan(); + + AriaHelper.bindCaption(owner.getWidget(), captionContent); + + DOM.insertChild(getElement(), captionContent, + icon == null ? 0 : 1); + } + String c = state.caption; + if (c == null) { + c = ""; + } else { + isEmpty = false; + } + if (state.captionAsHtml) { + captionContent.setInnerHTML(c); + } else { + captionContent.setInnerText(c); + } + } else { + // TODO should span also be removed + } + + if (state.description != null && captionContent != null) { + addStyleDependentName("hasdescription"); + } else { + removeStyleDependentName("hasdescription"); + } + + boolean required = owner instanceof AbstractFieldConnector + && ((AbstractFieldConnector) owner).isRequired(); + + AriaHelper.handleInputRequired(owner.getWidget(), required); + + if (required) { + if (requiredFieldIndicator == null) { + requiredFieldIndicator = DOM.createSpan(); + DOM.setInnerText(requiredFieldIndicator, "*"); + DOM.setElementProperty(requiredFieldIndicator, "className", + "v-required-field-indicator"); + DOM.appendChild(getElement(), requiredFieldIndicator); + + // Hide the required indicator from screen reader, as this + // information is set directly at the input field + Roles.getTextboxRole().setAriaHiddenState( + requiredFieldIndicator, true); + } + } else { + if (requiredFieldIndicator != null) { + DOM.removeChild(getElement(), requiredFieldIndicator); + requiredFieldIndicator = null; + } + } + } + + /** + * Returns Paintable for which this Caption belongs to. + * + * @return owner Widget + */ + public ComponentConnector getOwner() { + return owner; + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public class ErrorFlag extends HTML { + private static final String CLASSNAME = VFormLayout.CLASSNAME + + "-error-indicator"; + Element errorIndicatorElement; + + private ComponentConnector owner; + + public ErrorFlag(ComponentConnector owner) { + setStyleName(CLASSNAME); + + if (!BrowserInfo.get().isTouchDevice()) { + sinkEvents(VTooltip.TOOLTIP_EVENTS); + } + + this.owner = owner; + } + + public ComponentConnector getOwner() { + return owner; + } + + public void updateError(String errorMessage, boolean hideErrors) { + boolean showError = null != errorMessage; + if (hideErrors) { + showError = false; + } + + AriaHelper.handleInputInvalid(owner.getWidget(), showError); + + if (showError) { + if (errorIndicatorElement == null) { + errorIndicatorElement = DOM.createDiv(); + DOM.setInnerHTML(errorIndicatorElement, " "); + DOM.setElementProperty(errorIndicatorElement, "className", + "v-errorindicator"); + DOM.appendChild(getElement(), errorIndicatorElement); + + // Hide the error indicator from screen reader, as this + // information is set directly at the input field + Roles.getFormRole().setAriaHiddenState( + errorIndicatorElement, true); + } + + } else if (errorIndicatorElement != null) { + DOM.removeChild(getElement(), errorIndicatorElement); + errorIndicatorElement = null; + } + } + + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VGridLayout.java b/client/src/main/java/com/vaadin/client/ui/VGridLayout.java new file mode 100644 index 0000000000..42bcb5060a --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VGridLayout.java @@ -0,0 +1,925 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorMap; +import com.vaadin.client.LayoutManager; +import com.vaadin.client.StyleConstants; +import com.vaadin.client.Util; +import com.vaadin.client.VCaption; +import com.vaadin.client.ui.gridlayout.GridLayoutConnector; +import com.vaadin.client.ui.layout.ComponentConnectorLayoutSlot; +import com.vaadin.client.ui.layout.VLayoutSlot; +import com.vaadin.shared.ui.AlignmentInfo; +import com.vaadin.shared.ui.MarginInfo; +import com.vaadin.shared.ui.gridlayout.GridLayoutState.ChildComponentData; + +public class VGridLayout extends ComplexPanel { + + public static final String CLASSNAME = "v-gridlayout"; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public HashMap widgetToCell = new HashMap(); + + /** For internal use only. May be removed or replaced in the future. */ + public int[] columnWidths; + + /** For internal use only. May be removed or replaced in the future. */ + public int[] rowHeights; + + /** For internal use only. May be removed or replaced in the future. */ + public int[] colExpandRatioArray; + + /** For internal use only. May be removed or replaced in the future. */ + public int[] rowExpandRatioArray; + + int[] minColumnWidths; + + private int[] minRowHeights; + + /** For internal use only. May be removed or replaced in the future. */ + public DivElement spacingMeasureElement; + public Set explicitRowRatios; + public Set explicitColRatios; + + public boolean hideEmptyRowsAndColumns = false; + + public VGridLayout() { + super(); + setElement(Document.get().createDivElement()); + + spacingMeasureElement = Document.get().createDivElement(); + Style spacingStyle = spacingMeasureElement.getStyle(); + spacingStyle.setPosition(Position.ABSOLUTE); + getElement().appendChild(spacingMeasureElement); + + setStyleName(CLASSNAME); + addStyleName(StyleConstants.UI_LAYOUT); + } + + private GridLayoutConnector getConnector() { + return (GridLayoutConnector) ConnectorMap.get(client) + .getConnector(this); + } + + /** + * Returns the column widths measured in pixels + * + * @return + */ + protected int[] getColumnWidths() { + return columnWidths; + } + + /** + * Returns the row heights measured in pixels + * + * @return + */ + protected int[] getRowHeights() { + return rowHeights; + } + + /** + * Returns the spacing between the cells horizontally in pixels + * + * @return + */ + protected int getHorizontalSpacing() { + return LayoutManager.get(client).getOuterWidth(spacingMeasureElement); + } + + /** + * Returns the spacing between the cells vertically in pixels + * + * @return + */ + protected int getVerticalSpacing() { + return LayoutManager.get(client).getOuterHeight(spacingMeasureElement); + } + + static int[] cloneArray(int[] toBeCloned) { + int[] clone = new int[toBeCloned.length]; + for (int i = 0; i < clone.length; i++) { + clone[i] = toBeCloned[i] * 1; + } + return clone; + } + + void expandRows() { + if (!isUndefinedHeight()) { + int usedSpace = calcRowUsedSpace(); + int[] actualExpandRatio = calcRowExpandRatio(); + // Round down to avoid problems with fractions (100.1px available -> + // can use 100, not 101) + int availableSpace = (int) LayoutManager.get(client) + .getInnerHeightDouble(getElement()); + int excessSpace = availableSpace - usedSpace; + int distributed = 0; + if (excessSpace > 0) { + int expandRatioSum = 0; + for (int i = 0; i < rowHeights.length; i++) { + expandRatioSum += actualExpandRatio[i]; + } + for (int i = 0; i < rowHeights.length; i++) { + int ew = excessSpace * actualExpandRatio[i] + / expandRatioSum; + rowHeights[i] = minRowHeights[i] + ew; + distributed += ew; + } + excessSpace -= distributed; + int c = 0; + while (excessSpace > 0) { + rowHeights[c % rowHeights.length]++; + excessSpace--; + c++; + } + } + } + } + + private int[] calcRowExpandRatio() { + int[] actualExpandRatio = new int[minRowHeights.length]; + for (int i = 0; i < minRowHeights.length; i++) { + if (hiddenEmptyRow(i)) { + actualExpandRatio[i] = 0; + } else { + actualExpandRatio[i] = rowExpandRatioArray[i]; + } + } + return actualExpandRatio; + } + + /** + * Checks if it is ok to hide (or ignore) the given row. + * + * @param rowIndex + * the row to check + * @return true, if the row should be interpreted as non-existant (hides + * extra spacing) + */ + private boolean hiddenEmptyRow(int rowIndex) { + return hideEmptyRowsAndColumns && !rowHasComponentsOrRowSpan(rowIndex) + && !explicitRowRatios.contains(rowIndex); + } + + /** + * Checks if it is ok to hide (or ignore) the given column. + * + * @param columnIndex + * the column to check + * @return true, if the column should be interpreted as non-existant (hides + * extra spacing) + */ + private boolean hiddenEmptyColumn(int columnIndex) { + return hideEmptyRowsAndColumns + && !colHasComponentsOrColSpan(columnIndex) + && !explicitColRatios.contains(columnIndex); + } + + private int calcRowUsedSpace() { + int usedSpace = minRowHeights[0]; + int verticalSpacing = getVerticalSpacing(); + for (int i = 1; i < minRowHeights.length; i++) { + if (minRowHeights[i] > 0 || !hiddenEmptyRow(i)) { + usedSpace += verticalSpacing + minRowHeights[i]; + } + } + return usedSpace; + } + + void expandColumns() { + if (!isUndefinedWidth()) { + int usedSpace = calcColumnUsedSpace(); + int[] actualExpandRatio = calcColumnExpandRatio(); + // Round down to avoid problems with fractions (100.1px available -> + // can use 100, not 101) + int availableSpace = (int) LayoutManager.get(client) + .getInnerWidthDouble(getElement()); + int excessSpace = availableSpace - usedSpace; + int distributed = 0; + if (excessSpace > 0) { + int expandRatioSum = 0; + for (int i = 0; i < columnWidths.length; i++) { + expandRatioSum += actualExpandRatio[i]; + } + for (int i = 0; i < columnWidths.length; i++) { + int ew = excessSpace * actualExpandRatio[i] + / expandRatioSum; + columnWidths[i] = minColumnWidths[i] + ew; + distributed += ew; + } + excessSpace -= distributed; + int c = 0; + while (excessSpace > 0) { + columnWidths[c % columnWidths.length]++; + excessSpace--; + c++; + } + } + } + } + + /** + * Calculates column expand ratio. + */ + private int[] calcColumnExpandRatio() { + int[] actualExpandRatio = new int[minColumnWidths.length]; + for (int i = 0; i < minColumnWidths.length; i++) { + if (!hiddenEmptyColumn(i)) { + actualExpandRatio[i] = colExpandRatioArray[i]; + } else { + actualExpandRatio[i] = 0; + } + } + return actualExpandRatio; + } + + /** + * Calculates column used space + */ + private int calcColumnUsedSpace() { + int usedSpace = minColumnWidths[0]; + int horizontalSpacing = getHorizontalSpacing(); + for (int i = 1; i < minColumnWidths.length; i++) { + if (minColumnWidths[i] > 0 || !hiddenEmptyColumn(i)) { + usedSpace += horizontalSpacing + minColumnWidths[i]; + } + } + return usedSpace; + } + + private boolean rowHasComponentsOrRowSpan(int i) { + for (Cell cell : widgetToCell.values()) { + if (cell.row == i) { + return true; + } + } + for (SpanList l : rowSpans) { + for (Cell cell : l.cells) { + if (cell.row <= i && i < cell.row + cell.rowspan) { + return true; + } + } + } + return false; + } + + private boolean colHasComponentsOrColSpan(int i) { + for (Cell cell : widgetToCell.values()) { + if (cell.col == i) { + return true; + } + } + for (SpanList l : colSpans) { + for (Cell cell : l.cells) { + if (cell.col <= i && i < cell.col + cell.colspan) { + return true; + } + } + } + return false; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateHeight() { + // Detect minimum heights & calculate spans + detectRowHeights(); + + // Expand + expandRows(); + + // Position + layoutCellsVertically(); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateWidth() { + // Detect widths & calculate spans + detectColWidths(); + // Expand + expandColumns(); + // Position + layoutCellsHorizontally(); + + } + + void layoutCellsVertically() { + int verticalSpacing = getVerticalSpacing(); + LayoutManager layoutManager = LayoutManager.get(client); + Element element = getElement(); + int paddingTop = layoutManager.getPaddingTop(element); + int paddingBottom = layoutManager.getPaddingBottom(element); + + int y = paddingTop; + for (int column = 0; column < cells.length; column++) { + y = paddingTop + 1 - 1; // Ensure IE10 does not optimize this out by + // adding something to evaluate on the RHS + // #11303 + + for (int row = 0; row < cells[column].length; row++) { + Cell cell = cells[column][row]; + if (cell != null) { + int reservedMargin; + if (cell.rowspan + row >= cells[column].length) { + // Make room for layout padding for cells reaching the + // bottom of the layout + reservedMargin = paddingBottom; + } else { + reservedMargin = 0; + } + + cell.layoutVertically(y, reservedMargin); + } + if (!hideEmptyRowsAndColumns || rowHasComponentsOrRowSpan(row) + || rowHeights[row] > 0) { + y += rowHeights[row] + verticalSpacing; + } + } + } + + if (isUndefinedHeight()) { + int outerHeight = y - verticalSpacing + + layoutManager.getPaddingBottom(element) + + layoutManager.getBorderHeight(element); + element.getStyle().setHeight(outerHeight, Unit.PX); + + getConnector().getLayoutManager().reportOuterHeight(getConnector(), + outerHeight); + } + } + + void layoutCellsHorizontally() { + LayoutManager layoutManager = LayoutManager.get(client); + Element element = getElement(); + int x = layoutManager.getPaddingLeft(element); + int paddingRight = layoutManager.getPaddingRight(element); + int horizontalSpacing = getHorizontalSpacing(); + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells[i].length; j++) { + Cell cell = cells[i][j]; + if (cell != null) { + int reservedMargin; + // Make room for layout padding for cells reaching the + // right edge of the layout + if (i + cell.colspan >= cells.length) { + reservedMargin = paddingRight; + } else { + reservedMargin = 0; + } + cell.layoutHorizontally(x, reservedMargin); + } + } + if (!hideEmptyRowsAndColumns || colHasComponentsOrColSpan(i) + || columnWidths[i] > 0) { + x += columnWidths[i] + horizontalSpacing; + } + } + + if (isUndefinedWidth()) { + int outerWidth = x - horizontalSpacing + + layoutManager.getPaddingRight(element) + + layoutManager.getBorderWidth(element); + element.getStyle().setWidth(outerWidth, Unit.PX); + getConnector().getLayoutManager().reportOuterWidth(getConnector(), + outerWidth); + } + } + + private boolean isUndefinedHeight() { + return getConnector().isUndefinedHeight(); + } + + private boolean isUndefinedWidth() { + return getConnector().isUndefinedWidth(); + } + + private void detectRowHeights() { + for (int i = 0; i < rowHeights.length; i++) { + rowHeights[i] = 0; + } + + // collect min rowheight from non-rowspanned cells + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells[i].length; j++) { + Cell cell = cells[i][j]; + if (cell != null) { + if (cell.rowspan == 1) { + if (!cell.hasRelativeHeight() + && rowHeights[j] < cell.getHeight()) { + rowHeights[j] = cell.getHeight(); + } + } else { + storeRowSpannedCell(cell); + } + } + } + } + + distributeRowSpanHeights(); + + minRowHeights = cloneArray(rowHeights); + } + + private void detectColWidths() { + // collect min colwidths from non-colspanned cells + for (int i = 0; i < columnWidths.length; i++) { + columnWidths[i] = 0; + } + + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells[i].length; j++) { + Cell cell = cells[i][j]; + if (cell != null) { + if (cell.colspan == 1) { + if (!cell.hasRelativeWidth() + && columnWidths[i] < cell.getWidth()) { + columnWidths[i] = cell.getWidth(); + } + } else { + storeColSpannedCell(cell); + } + } + } + } + + distributeColSpanWidths(); + + minColumnWidths = cloneArray(columnWidths); + } + + private void storeRowSpannedCell(Cell cell) { + SpanList l = null; + for (SpanList list : rowSpans) { + if (list.span < cell.rowspan) { + continue; + } else { + // insert before this + l = list; + break; + } + } + if (l == null) { + l = new SpanList(cell.rowspan); + rowSpans.add(l); + } else if (l.span != cell.rowspan) { + SpanList newL = new SpanList(cell.rowspan); + rowSpans.add(rowSpans.indexOf(l), newL); + l = newL; + } + l.cells.add(cell); + } + + /** + * Iterates colspanned cells, ensures cols have enough space to accommodate + * them + */ + void distributeColSpanWidths() { + for (SpanList list : colSpans) { + for (Cell cell : list.cells) { + // cells with relative content may return non 0 here if on + // subsequent renders + int width = cell.hasRelativeWidth() ? 0 : cell.getWidth(); + distributeSpanSize(columnWidths, cell.col, cell.colspan, + getHorizontalSpacing(), width, colExpandRatioArray); + } + } + } + + /** + * Iterates rowspanned cells, ensures rows have enough space to accommodate + * them + */ + private void distributeRowSpanHeights() { + for (SpanList list : rowSpans) { + for (Cell cell : list.cells) { + // cells with relative content may return non 0 here if on + // subsequent renders + int height = cell.hasRelativeHeight() ? 0 : cell.getHeight(); + distributeSpanSize(rowHeights, cell.row, cell.rowspan, + getVerticalSpacing(), height, rowExpandRatioArray); + } + } + } + + private static void distributeSpanSize(int[] dimensions, + int spanStartIndex, int spanSize, int spacingSize, int size, + int[] expansionRatios) { + int allocated = dimensions[spanStartIndex]; + for (int i = 1; i < spanSize; i++) { + allocated += spacingSize + dimensions[spanStartIndex + i]; + } + if (allocated < size) { + // dimensions needs to be expanded due spanned cell + int neededExtraSpace = size - allocated; + int allocatedExtraSpace = 0; + + // Divide space according to expansion ratios if any span has a + // ratio + int totalExpansion = 0; + for (int i = 0; i < spanSize; i++) { + int itemIndex = spanStartIndex + i; + totalExpansion += expansionRatios[itemIndex]; + } + + for (int i = 0; i < spanSize; i++) { + int itemIndex = spanStartIndex + i; + int expansion; + if (totalExpansion == 0) { + // Divide equally among all cells if there are no + // expansion ratios + expansion = neededExtraSpace / spanSize; + } else { + expansion = neededExtraSpace * expansionRatios[itemIndex] + / totalExpansion; + } + dimensions[itemIndex] += expansion; + allocatedExtraSpace += expansion; + } + + // We might still miss a couple of pixels because of + // rounding errors... + if (neededExtraSpace > allocatedExtraSpace) { + for (int i = 0; i < spanSize; i++) { + // Add one pixel to every cell until we have + // compensated for any rounding error + int itemIndex = spanStartIndex + i; + dimensions[itemIndex] += 1; + allocatedExtraSpace += 1; + if (neededExtraSpace == allocatedExtraSpace) { + break; + } + } + } + } + } + + private LinkedList colSpans = new LinkedList(); + private LinkedList rowSpans = new LinkedList(); + + private class SpanList { + final int span; + List cells = new LinkedList(); + + public SpanList(int span) { + this.span = span; + } + } + + void storeColSpannedCell(Cell cell) { + SpanList l = null; + for (SpanList list : colSpans) { + if (list.span < cell.colspan) { + continue; + } else { + // insert before this + l = list; + break; + } + } + if (l == null) { + l = new SpanList(cell.colspan); + colSpans.add(l); + } else if (l.span != cell.colspan) { + + SpanList newL = new SpanList(cell.colspan); + colSpans.add(colSpans.indexOf(l), newL); + l = newL; + } + l.cells.add(cell); + } + + Cell[][] cells; + + /** + * Private helper class. + */ + /** For internal use only. May be removed or replaced in the future. */ + public class Cell { + public Cell(int row, int col) { + this.row = row; + this.col = col; + } + + public boolean hasRelativeHeight() { + if (slot != null) { + return slot.getChild().isRelativeHeight(); + } else { + return true; + } + } + + /** + * @return total of spanned cols + */ + private int getAvailableWidth() { + int width = columnWidths[col]; + for (int i = 1; i < colspan; i++) { + width += getHorizontalSpacing() + columnWidths[col + i]; + } + return width; + } + + /** + * @return total of spanned rows + */ + private int getAvailableHeight() { + int height = rowHeights[row]; + for (int i = 1; i < rowspan; i++) { + height += getVerticalSpacing() + rowHeights[row + i]; + } + return height; + } + + public void layoutHorizontally(int x, int marginRight) { + if (slot != null) { + slot.positionHorizontally(x, getAvailableWidth(), marginRight); + } + } + + public void layoutVertically(int y, int marginBottom) { + if (slot != null) { + slot.positionVertically(y, getAvailableHeight(), marginBottom); + } + } + + public int getWidth() { + if (slot != null) { + return slot.getUsedWidth(); + } else { + return 0; + } + } + + public int getHeight() { + if (slot != null) { + return slot.getUsedHeight(); + } else { + return 0; + } + } + + protected boolean hasRelativeWidth() { + if (slot != null) { + return slot.getChild().isRelativeWidth(); + } else { + return true; + } + } + + private int row; + private int col; + int colspan = 1; + int rowspan = 1; + + private AlignmentInfo alignment; + + /** For internal use only. May be removed or replaced in the future. */ + public ComponentConnectorLayoutSlot slot; + + public void updateCell(ChildComponentData childComponentData) { + if (row != childComponentData.row1 + || col != childComponentData.column1) { + // cell has been moved, update matrix + if (col < cells.length && cells.length != 0 + && row < cells[0].length && cells[col][row] == this) { + // Remove old reference if still relevant + cells[col][row] = null; + } + + row = childComponentData.row1; + col = childComponentData.column1; + + cells[col][row] = this; + } + + // Set cell width + colspan = 1 + childComponentData.column2 + - childComponentData.column1; + // Set cell height + rowspan = 1 + childComponentData.row2 - childComponentData.row1; + setAlignment(new AlignmentInfo(childComponentData.alignment)); + } + + public void setComponent(ComponentConnector component, + List ordering) { + if (slot == null || slot.getChild() != component) { + slot = new ComponentConnectorLayoutSlot(CLASSNAME, component, + getConnector()); + slot.setAlignment(alignment); + if (component.isRelativeWidth()) { + slot.getWrapperElement().getStyle().setWidth(100, Unit.PCT); + } + Element slotWrapper = slot.getWrapperElement(); + int childIndex = ordering.indexOf(component); + // insert new slot by proper index + // do not break default focus order + com.google.gwt.user.client.Element element = getElement(); + if (childIndex == ordering.size()) { + element.appendChild(slotWrapper); + } else if (childIndex == 0) { + element.insertAfter(slotWrapper, spacingMeasureElement); + } else { + // here we use childIndex - 1 + 1(spacingMeasureElement) + Element previousSlot = (Element) element + .getChild(childIndex); + element.insertAfter(slotWrapper, previousSlot); + } + + Widget widget = component.getWidget(); + insert(widget, slotWrapper, getWidgetCount(), false); + Cell oldCell = widgetToCell.put(widget, this); + if (oldCell != null) { + oldCell.slot.getWrapperElement().removeFromParent(); + oldCell.slot = null; + } + } + } + + public void setAlignment(AlignmentInfo alignmentInfo) { + alignment = alignmentInfo; + if (slot != null) { + slot.setAlignment(alignmentInfo); + } + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public Cell getCell(int row, int col) { + return cells[col][row]; + } + + /** + * Creates a new Cell with the given coordinates. + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param row + * @param col + * @return + */ + public Cell createNewCell(int row, int col) { + Cell cell = new Cell(row, col); + cells[col][row] = cell; + return cell; + } + + /** + * Returns the deepest nested child component which contains "element". The + * child component is also returned if "element" is part of its caption. + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param element + * An element that is a nested sub element of the root element in + * this layout + * @return The Paintable which the element is a part of. Null if the element + * belongs to the layout and not to a child. + * @deprecated As of 7.2, call or override {@link #getComponent(Element)} + * instead + */ + @Deprecated + public ComponentConnector getComponent( + com.google.gwt.user.client.Element element) { + return Util.getConnectorForElement(client, this, element); + + } + + /** + * Returns the deepest nested child component which contains "element". The + * child component is also returned if "element" is part of its caption. + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param element + * An element that is a nested sub element of the root element in + * this layout + * @return The Paintable which the element is a part of. Null if the element + * belongs to the layout and not to a child. + * + * @since 7.2 + */ + public ComponentConnector getComponent(Element element) { + return getComponent(DOM.asOld(element)); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setCaption(Widget widget, VCaption caption) { + VLayoutSlot slot = widgetToCell.get(widget).slot; + + if (caption != null) { + // Logical attach. + getChildren().add(caption); + } + + // Physical attach if not null, also removes old caption + slot.setCaption(caption); + + if (caption != null) { + // Adopt. + adopt(caption); + } + } + + private void togglePrefixedStyleName(String name, boolean enabled) { + if (enabled) { + addStyleDependentName(name); + } else { + removeStyleDependentName(name); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateMarginStyleNames(MarginInfo marginInfo) { + togglePrefixedStyleName("margin-top", marginInfo.hasTop()); + togglePrefixedStyleName("margin-right", marginInfo.hasRight()); + togglePrefixedStyleName("margin-bottom", marginInfo.hasBottom()); + togglePrefixedStyleName("margin-left", marginInfo.hasLeft()); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateSpacingStyleName(boolean spacingEnabled) { + String styleName = getStylePrimaryName(); + if (spacingEnabled) { + spacingMeasureElement.addClassName(styleName + "-spacing-on"); + spacingMeasureElement.removeClassName(styleName + "-spacing-off"); + } else { + spacingMeasureElement.removeClassName(styleName + "-spacing-on"); + spacingMeasureElement.addClassName(styleName + "-spacing-off"); + } + } + + public void setSize(int rows, int cols) { + if (cells == null) { + cells = new Cell[cols][rows]; + } else if (cells.length != cols || cells[0].length != rows) { + Cell[][] newCells = new Cell[cols][rows]; + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells[i].length; j++) { + if (i < cols && j < rows) { + newCells[i][j] = cells[i][j]; + } + } + } + cells = newCells; + } + } + + @Override + public boolean remove(Widget w) { + boolean removed = super.remove(w); + if (removed) { + Cell cell = widgetToCell.remove(w); + if (cell != null) { + cell.slot.setCaption(null); + cell.slot.getWrapperElement().removeFromParent(); + cell.slot = null; + Style style = w.getElement().getStyle(); + style.clearTop(); + style.clearLeft(); + style.clearPosition(); + + if (cells.length < cell.col && cells.length != 0 + && cells[0].length < cell.row + && cells[cell.col][cell.row] == cell) { + cells[cell.col][cell.row] = null; + } + } + } + return removed; + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VHorizontalLayout.java b/client/src/main/java/com/vaadin/client/ui/VHorizontalLayout.java new file mode 100644 index 0000000000..b890aefe3d --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VHorizontalLayout.java @@ -0,0 +1,42 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui; + +import com.vaadin.client.StyleConstants; +import com.vaadin.client.ui.orderedlayout.VAbstractOrderedLayout; + +/** + * Represents a layout where the children is ordered vertically + */ +public class VHorizontalLayout extends VAbstractOrderedLayout { + + public static final String CLASSNAME = "v-horizontallayout"; + + /** + * Default constructor + */ + public VHorizontalLayout() { + super(false); + setStyleName(CLASSNAME); + } + + @Override + public void setStyleName(String style) { + super.setStyleName(style); + addStyleName(StyleConstants.UI_LAYOUT); + addStyleName("v-horizontal"); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VImage.java b/client/src/main/java/com/vaadin/client/ui/VImage.java new file mode 100644 index 0000000000..2742182179 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VImage.java @@ -0,0 +1,27 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui; + +import com.google.gwt.user.client.ui.Image; + +public class VImage extends Image { + + public static final String CLASSNAME = "v-image"; + + public VImage() { + setStylePrimaryName(CLASSNAME); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VLabel.java b/client/src/main/java/com/vaadin/client/ui/VLabel.java new file mode 100644 index 0000000000..a3572759c4 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VLabel.java @@ -0,0 +1,72 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.HTML; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.Util; +import com.vaadin.client.VTooltip; +import com.vaadin.client.WidgetUtil; + +public class VLabel extends HTML { + + public static final String CLASSNAME = "v-label"; + private static final String CLASSNAME_UNDEFINED_WIDTH = "v-label-undef-w"; + + public VLabel() { + super(); + setStyleName(CLASSNAME); + sinkEvents(VTooltip.TOOLTIP_EVENTS); + } + + public VLabel(String text) { + super(text); + setStyleName(CLASSNAME); + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (event.getTypeInt() == Event.ONLOAD) { + Util.notifyParentOfSizeChange(this, true); + event.stopPropagation(); + return; + } + } + + @Override + public void setWidth(String width) { + super.setWidth(width); + if (width == null || width.equals("")) { + setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, true); + } else { + setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, false); + } + } + + @Override + public void setText(String text) { + if (BrowserInfo.get().isIE8()) { + // #3983 - IE8 incorrectly replaces \n with
so we do the + // escaping manually and set as HTML + super.setHTML(WidgetUtil.escapeHTML(text)); + } else { + super.setText(text); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VLazyExecutor.java b/client/src/main/java/com/vaadin/client/ui/VLazyExecutor.java new file mode 100644 index 0000000000..dfa4f574c6 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VLazyExecutor.java @@ -0,0 +1,64 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui; + +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.user.client.Timer; + +/** + * Executes the given command {@code delayMs} milliseconds after a call to + * {@link #trigger()}. Calling {@link #trigger()} again before the command has + * been executed causes the execution to be rescheduled to {@code delayMs} after + * the second call. + * + */ +public class VLazyExecutor { + + private Timer timer; + private int delayMs; + private ScheduledCommand cmd; + + /** + * @param delayMs + * Delay in milliseconds to wait before executing the command + * @param cmd + * The command to execute + */ + public VLazyExecutor(int delayMs, ScheduledCommand cmd) { + this.delayMs = delayMs; + this.cmd = cmd; + } + + /** + * Triggers execution of the command. Each call reschedules any existing + * execution to {@link #delayMs} milliseconds from that point in time. + */ + public void trigger() { + if (timer == null) { + timer = new Timer() { + @Override + public void run() { + timer = null; + cmd.execute(); + } + }; + } + // Schedule automatically cancels any old schedule + timer.schedule(delayMs); + + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VLink.java b/client/src/main/java/com/vaadin/client/ui/VLink.java new file mode 100644 index 0000000000..de99a8617f --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VLink.java @@ -0,0 +1,148 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.HasEnabled; +import com.vaadin.client.Util; +import com.vaadin.shared.ui.BorderStyle; + +public class VLink extends HTML implements ClickHandler, HasEnabled { + + public static final String CLASSNAME = "v-link"; + + @Deprecated + protected static final BorderStyle BORDER_STYLE_DEFAULT = BorderStyle.DEFAULT; + + @Deprecated + protected static final BorderStyle BORDER_STYLE_MINIMAL = BorderStyle.MINIMAL; + + @Deprecated + protected static final BorderStyle BORDER_STYLE_NONE = BorderStyle.NONE; + + /** For internal use only. May be removed or replaced in the future. */ + public String src; + + /** For internal use only. May be removed or replaced in the future. */ + public String target; + + /** For internal use only. May be removed or replaced in the future. */ + public BorderStyle borderStyle = BorderStyle.DEFAULT; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean enabled; + + /** For internal use only. May be removed or replaced in the future. */ + public int targetWidth; + + /** For internal use only. May be removed or replaced in the future. */ + public int targetHeight; + + /** For internal use only. May be removed or replaced in the future. */ + public Element errorIndicatorElement; + + /** For internal use only. May be removed or replaced in the future. */ + public final Element anchor = DOM.createAnchor(); + + /** For internal use only. May be removed or replaced in the future. */ + public final Element captionElement = DOM.createSpan(); + + /** For internal use only. May be removed or replaced in the future. */ + public Icon icon; + + public VLink() { + super(); + getElement().appendChild(anchor); + anchor.appendChild(captionElement); + addClickHandler(this); + setStyleName(CLASSNAME); + } + + @Override + public void onClick(ClickEvent event) { + if (enabled) { + if (target == null) { + target = "_self"; + } + String features; + switch (borderStyle) { + case NONE: + features = "menubar=no,location=no,status=no"; + break; + case MINIMAL: + features = "menubar=yes,location=no,status=no"; + break; + default: + features = ""; + break; + } + + if (targetWidth > 0) { + features += (features.length() > 0 ? "," : "") + "width=" + + targetWidth; + } + if (targetHeight > 0) { + features += (features.length() > 0 ? "," : "") + "height=" + + targetHeight; + } + + if (features.length() > 0) { + // if 'special features' are set, use window.open(), unless + // a modifier key is held (ctrl to open in new tab etc) + Event e = DOM.eventGetCurrentEvent(); + if (!e.getCtrlKey() && !e.getAltKey() && !e.getShiftKey() + && !e.getMetaKey()) { + Window.open(src, target, features); + e.preventDefault(); + } + } + } + } + + @Override + public void onBrowserEvent(Event event) { + final Element target = DOM.eventGetTarget(event); + if (event.getTypeInt() == Event.ONLOAD) { + Util.notifyParentOfSizeChange(this, true); + } + if (target == captionElement || target == anchor + || (icon != null && target == icon.getElement())) { + super.onBrowserEvent(event); + } + if (!enabled) { + event.preventDefault(); + } + + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VListSelect.java b/client/src/main/java/com/vaadin/client/ui/VListSelect.java new file mode 100644 index 0000000000..b6f4f0c722 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VListSelect.java @@ -0,0 +1,154 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.ArrayList; +import java.util.Iterator; + +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.user.client.ui.ListBox; +import com.vaadin.client.UIDL; + +public class VListSelect extends VOptionGroupBase { + + public static final String CLASSNAME = "v-select"; + + private static final int VISIBLE_COUNT = 10; + + protected ListBox select; + + private int lastSelectedIndex = -1; + + public VListSelect() { + super(new ListBox(true), CLASSNAME); + select = getOptionsContainer(); + select.addChangeHandler(this); + select.addClickHandler(this); + select.setVisibleItemCount(VISIBLE_COUNT); + setStyleName(CLASSNAME); + + updateEnabledState(); + } + + @Override + public void setStyleName(String style) { + super.setStyleName(style); + updateStyleNames(); + } + + @Override + public void setStylePrimaryName(String style) { + super.setStylePrimaryName(style); + updateStyleNames(); + } + + protected void updateStyleNames() { + container.setStyleName(getStylePrimaryName()); + select.setStyleName(getStylePrimaryName() + "-select"); + } + + protected ListBox getOptionsContainer() { + return (ListBox) optionsContainer; + } + + @Override + public void buildOptions(UIDL uidl) { + int scrollTop = select.getElement().getScrollTop(); + int rowCount = getRows(); + select.setMultipleSelect(isMultiselect()); + select.clear(); + if (!isMultiselect() && isNullSelectionAllowed() + && !isNullSelectionItemAvailable()) { + // can't unselect last item in singleselect mode + select.addItem("", (String) null); + } + for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { + final UIDL optionUidl = (UIDL) i.next(); + select.addItem(optionUidl.getStringAttribute("caption"), + optionUidl.getStringAttribute("key")); + if (optionUidl.hasAttribute("selected")) { + int itemIndex = select.getItemCount() - 1; + select.setItemSelected(itemIndex, true); + lastSelectedIndex = itemIndex; + } + } + if (getRows() > 0) { + select.setVisibleItemCount(getRows()); + } + // FIXME: temporary hack for preserving the scroll state when the + // contents haven't been changed obviously. This should be dealt with in + // the rewrite. + if (rowCount == getRows()) { + select.getElement().setScrollTop(scrollTop); + } + } + + @Override + protected String[] getSelectedItems() { + final ArrayList selectedItemKeys = new ArrayList(); + for (int i = 0; i < select.getItemCount(); i++) { + if (select.isItemSelected(i)) { + selectedItemKeys.add(select.getValue(i)); + } + } + return selectedItemKeys.toArray(new String[selectedItemKeys.size()]); + } + + @Override + public void onChange(ChangeEvent event) { + final int si = select.getSelectedIndex(); + if (si == -1 && !isNullSelectionAllowed()) { + select.setSelectedIndex(lastSelectedIndex); + } else { + lastSelectedIndex = si; + if (isMultiselect()) { + client.updateVariable(paintableId, "selected", + getSelectedItems(), isImmediate()); + } else { + client.updateVariable(paintableId, "selected", + new String[] { "" + getSelectedItem() }, isImmediate()); + } + } + } + + @Override + public void setHeight(String height) { + select.setHeight(height); + super.setHeight(height); + } + + @Override + public void setWidth(String width) { + select.setWidth(width); + super.setWidth(width); + } + + @Override + public void setTabIndex(int tabIndex) { + getOptionsContainer().setTabIndex(tabIndex); + } + + @Override + protected void updateEnabledState() { + select.setEnabled(isEnabled() && !isReadonly()); + } + + @Override + public void focus() { + select.setFocus(true); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VMediaBase.java b/client/src/main/java/com/vaadin/client/ui/VMediaBase.java new file mode 100644 index 0000000000..53d7cef02d --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VMediaBase.java @@ -0,0 +1,90 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.MediaElement; +import com.google.gwt.dom.client.NodeList; +import com.google.gwt.dom.client.SourceElement; +import com.google.gwt.dom.client.Text; +import com.google.gwt.user.client.ui.Widget; + +public abstract class VMediaBase extends Widget { + + private MediaElement media; + private Text altText; + + /** + * Sets the MediaElement that is to receive all commands and properties. + * + * @param element + */ + public void setMediaElement(MediaElement element) { + setElement(element); + media = element; + } + + public void play() { + media.play(); + } + + public void pause() { + media.pause(); + } + + public void setAltText(String alt) { + if (altText == null) { + altText = Document.get().createTextNode(alt); + media.appendChild(altText); + } else { + altText.setNodeValue(alt); + } + } + + public void setControls(boolean shouldShowControls) { + media.setControls(shouldShowControls); + } + + public void setAutoplay(boolean shouldAutoplay) { + media.setAutoplay(shouldAutoplay); + } + + public void setMuted(boolean mediaMuted) { + media.setMuted(mediaMuted); + } + + public void removeAllSources() { + NodeList l = media + .getElementsByTagName(SourceElement.TAG); + for (int i = l.getLength() - 1; i >= 0; i--) { + media.removeChild(l.getItem(i)); + } + + } + + public void load() { + media.load(); + } + + public void addSource(String sourceUrl, String sourceType) { + Element src = Document.get().createElement(SourceElement.TAG); + src.setAttribute("src", sourceUrl); + src.setAttribute("type", sourceType); + media.appendChild(src); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VMenuBar.java b/client/src/main/java/com/vaadin/client/ui/VMenuBar.java new file mode 100644 index 0000000000..b2b40c12f7 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VMenuBar.java @@ -0,0 +1,1718 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyPressEvent; +import com.google.gwt.event.dom.client.KeyPressHandler; +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.HasHTML; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.LayoutManager; +import com.vaadin.client.TooltipInfo; +import com.vaadin.client.UIDL; +import com.vaadin.client.Util; +import com.vaadin.client.WidgetUtil; +import com.vaadin.shared.ui.menubar.MenuBarConstants; + +public class VMenuBar extends SimpleFocusablePanel implements + CloseHandler, KeyPressHandler, KeyDownHandler, + FocusHandler, SubPartAware { + + // The hierarchy of VMenuBar is a bit weird as VMenuBar is the Paintable, + // used for the root menu but also used for the sub menus. + + /** Set the CSS class name to allow styling. */ + public static final String CLASSNAME = "v-menubar"; + public static final String SUBMENU_CLASSNAME_PREFIX = "-submenu"; + + /** + * For server connections. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public String uidlId; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public final VMenuBar hostReference = this; + + /** For internal use only. May be removed or replaced in the future. */ + public CustomMenuItem moreItem = null; + + /** For internal use only. May be removed or replaced in the future. */ + public VMenuBar collapsedRootItems; + + /** + * An empty command to be used when the item has no command associated + *

+ * For internal use only. May be removed or replaced in the future. + */ + public static final Command emptyCommand = null; + + /** Widget fields **/ + protected boolean subMenu; + protected ArrayList items; + protected Element containerElement; + protected VOverlay popup; + protected VMenuBar visibleChildMenu; + protected boolean menuVisible = false; + protected VMenuBar parentMenu; + protected CustomMenuItem selected; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean enabled = true; + + private VLazyExecutor iconLoadedExecutioner = new VLazyExecutor(100, + new ScheduledCommand() { + + @Override + public void execute() { + iLayout(true); + } + }); + + /** For internal use only. May be removed or replaced in the future. */ + public boolean openRootOnHover; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean htmlContentAllowed; + + public VMenuBar() { + // Create an empty horizontal menubar + this(false, null); + + // Navigation is only handled by the root bar + addFocusHandler(this); + + /* + * Firefox auto-repeat works correctly only if we use a key press + * handler, other browsers handle it correctly when using a key down + * handler + */ + if (BrowserInfo.get().isGecko()) { + addKeyPressHandler(this); + } else { + addKeyDownHandler(this); + } + } + + public VMenuBar(boolean subMenu, VMenuBar parentMenu) { + + items = new ArrayList(); + popup = null; + visibleChildMenu = null; + this.subMenu = subMenu; + + containerElement = getElement(); + + sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT + | Event.ONLOAD); + + if (parentMenu == null) { + // Root menu + setStyleName(CLASSNAME); + } else { + // Child menus inherits style name + setStyleName(parentMenu.getStyleName()); + } + } + + @Override + public void setStyleName(String style) { + super.setStyleName(style); + updateStyleNames(); + } + + @Override + public void setStylePrimaryName(String style) { + super.setStylePrimaryName(style); + updateStyleNames(); + } + + protected void updateStyleNames() { + String primaryStyleName = getParentMenu() != null ? getParentMenu() + .getStylePrimaryName() : getStylePrimaryName(); + + // Reset the style name for all the items + for (CustomMenuItem item : items) { + item.setStyleName(primaryStyleName + "-menuitem"); + } + + if (subMenu + && !getStylePrimaryName().endsWith(SUBMENU_CLASSNAME_PREFIX)) { + /* + * Sub-menus should get the sub-menu prefix + */ + super.setStylePrimaryName(primaryStyleName + + SUBMENU_CLASSNAME_PREFIX); + } + } + + @Override + protected void onDetach() { + super.onDetach(); + if (!subMenu) { + setSelected(null); + hideChildren(); + menuVisible = false; + } + } + + void updateSize() { + // Take from setWidth + if (!subMenu) { + // Only needed for root level menu + hideChildren(); + setSelected(null); + menuVisible = false; + } + } + + /** + * Build the HTML content for a menu item. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public String buildItemHTML(UIDL item) { + // Construct html from the text and the optional icon + StringBuffer itemHTML = new StringBuffer(); + if (item.hasAttribute("separator")) { + itemHTML.append("---"); + } else { + // Add submenu indicator + if (item.getChildCount() > 0) { + String bgStyle = ""; + itemHTML.append(""); + } + + itemHTML.append(""); + Icon icon = client.getIcon(item.getStringAttribute("icon")); + if (icon != null) { + itemHTML.append(icon.getElement().getString()); + } + String itemText = item.getStringAttribute("text"); + if (!htmlContentAllowed) { + itemText = WidgetUtil.escapeHTML(itemText); + } + itemHTML.append(itemText); + itemHTML.append(""); + } + return itemHTML.toString(); + } + + /** + * This is called by the items in the menu and it communicates the + * information to the server + * + * @param clickedItemId + * id of the item that was clicked + */ + public void onMenuClick(int clickedItemId) { + // Updating the state to the server can not be done before + // the server connection is known, i.e., before updateFromUIDL() + // has been called. + if (uidlId != null && client != null) { + // Communicate the user interaction parameters to server. This call + // will initiate an AJAX request to the server. + client.updateVariable(uidlId, "clickedId", clickedItemId, true); + } + } + + /** Widget methods **/ + + /** + * Returns a list of items in this menu + */ + public List getItems() { + return items; + } + + /** + * Remove all the items in this menu + */ + public void clearItems() { + Element e = getContainerElement(); + while (DOM.getChildCount(e) > 0) { + DOM.removeChild(e, DOM.getChild(e, 0)); + } + items.clear(); + } + + /** + * Returns the containing element of the menu + * + * @return + */ + @Override + public com.google.gwt.user.client.Element getContainerElement() { + return DOM.asOld(containerElement); + } + + /** + * Add a new item to this menu + * + * @param html + * items text + * @param cmd + * items command + * @return the item created + */ + public CustomMenuItem addItem(String html, Command cmd) { + CustomMenuItem item = GWT.create(CustomMenuItem.class); + item.setHTML(html); + item.setCommand(cmd); + addItem(item); + return item; + } + + /** + * Add a new item to this menu + * + * @param item + */ + public void addItem(CustomMenuItem item) { + if (items.contains(item)) { + return; + } + DOM.appendChild(getContainerElement(), item.getElement()); + item.setParentMenu(this); + item.setSelected(false); + items.add(item); + } + + public void addItem(CustomMenuItem item, int index) { + if (items.contains(item)) { + return; + } + DOM.insertChild(getContainerElement(), item.getElement(), index); + item.setParentMenu(this); + item.setSelected(false); + items.add(index, item); + } + + /** + * Remove the given item from this menu + * + * @param item + */ + public void removeItem(CustomMenuItem item) { + if (items.contains(item)) { + int index = items.indexOf(item); + + DOM.removeChild(getContainerElement(), + DOM.getChild(getContainerElement(), index)); + items.remove(index); + } + } + + /* + * @see + * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user + * .client.Event) + */ + @Override + public void onBrowserEvent(Event e) { + super.onBrowserEvent(e); + + // Handle onload events (icon loaded, size changes) + if (DOM.eventGetType(e) == Event.ONLOAD) { + VMenuBar parent = getParentMenu(); + if (parent != null) { + // The onload event for an image in a popup should be sent to + // the parent, which owns the popup + parent.iconLoaded(); + } else { + // Onload events for images in the root menu are handled by the + // root menu itself + iconLoaded(); + } + return; + } + + Element targetElement = DOM.eventGetTarget(e); + CustomMenuItem targetItem = null; + for (int i = 0; i < items.size(); i++) { + CustomMenuItem item = items.get(i); + if (DOM.isOrHasChild(item.getElement(), targetElement)) { + targetItem = item; + } + } + + if (targetItem != null) { + switch (DOM.eventGetType(e)) { + + case Event.ONCLICK: + if (isEnabled() && targetItem.isEnabled()) { + itemClick(targetItem); + } + if (subMenu) { + // Prevent moving keyboard focus to child menus + VMenuBar parent = parentMenu; + while (parent.getParentMenu() != null) { + parent = parent.getParentMenu(); + } + parent.setFocus(true); + } + + break; + + case Event.ONMOUSEOVER: + LazyCloser.cancelClosing(); + + if (isEnabled() && targetItem.isEnabled()) { + itemOver(targetItem); + } + break; + + case Event.ONMOUSEOUT: + itemOut(targetItem); + LazyCloser.schedule(); + break; + } + } else if (subMenu && DOM.eventGetType(e) == Event.ONCLICK && subMenu) { + // Prevent moving keyboard focus to child menus + VMenuBar parent = parentMenu; + while (parent.getParentMenu() != null) { + parent = parent.getParentMenu(); + } + parent.setFocus(true); + } + } + + private boolean isEnabled() { + return enabled; + } + + private void iconLoaded() { + iconLoadedExecutioner.trigger(); + } + + /** + * When an item is clicked + * + * @param item + */ + public void itemClick(CustomMenuItem item) { + if (item.getCommand() != null) { + setSelected(null); + + if (visibleChildMenu != null) { + visibleChildMenu.hideChildren(); + } + + hideParents(true); + menuVisible = false; + Scheduler.get().scheduleDeferred(item.getCommand()); + + } else { + if (item.getSubMenu() != null + && item.getSubMenu() != visibleChildMenu) { + setSelected(item); + showChildMenu(item); + menuVisible = true; + } else if (!subMenu) { + setSelected(null); + hideChildren(); + menuVisible = false; + } + } + } + + /** + * When the user hovers the mouse over the item + * + * @param item + */ + public void itemOver(CustomMenuItem item) { + if ((openRootOnHover || subMenu || menuVisible) && !item.isSeparator()) { + setSelected(item); + if (!subMenu && openRootOnHover && !menuVisible) { + menuVisible = true; // start opening menus + LazyCloser.prepare(this); + } + } + + if (menuVisible && visibleChildMenu != item.getSubMenu() + && popup != null) { + // #15255 - disable animation-in/out when hide in this case + popup.hide(false, false, false); + } + + if (menuVisible && item.getSubMenu() != null + && visibleChildMenu != item.getSubMenu()) { + showChildMenu(item); + } + } + + /** + * When the mouse is moved away from an item + * + * @param item + */ + public void itemOut(CustomMenuItem item) { + if (visibleChildMenu != item.getSubMenu()) { + hideChildMenu(item); + setSelected(null); + } else if (visibleChildMenu == null) { + setSelected(null); + } + } + + /** + * Used to autoclose submenus when they the menu is in a mode which opens + * root menus on mouse hover. + */ + private static class LazyCloser extends Timer { + static LazyCloser INSTANCE; + private VMenuBar activeRoot; + + @Override + public void run() { + activeRoot.hideChildren(); + activeRoot.setSelected(null); + activeRoot.menuVisible = false; + activeRoot = null; + } + + public static void cancelClosing() { + if (INSTANCE != null) { + INSTANCE.cancel(); + } + } + + public static void prepare(VMenuBar vMenuBar) { + if (INSTANCE == null) { + INSTANCE = new LazyCloser(); + } + if (INSTANCE.activeRoot == vMenuBar) { + INSTANCE.cancel(); + } else if (INSTANCE.activeRoot != null) { + INSTANCE.cancel(); + INSTANCE.run(); + } + INSTANCE.activeRoot = vMenuBar; + } + + public static void schedule() { + if (INSTANCE != null && INSTANCE.activeRoot != null) { + INSTANCE.schedule(750); + } + } + + } + + /** + * Shows the child menu of an item. The caller must ensure that the item has + * a submenu. + * + * @param item + */ + public void showChildMenu(CustomMenuItem item) { + + int left = 0; + int top = 0; + if (subMenu) { + left = item.getParentMenu().getAbsoluteLeft() + + item.getParentMenu().getOffsetWidth(); + top = item.getAbsoluteTop(); + } else { + left = item.getAbsoluteLeft(); + top = item.getParentMenu().getAbsoluteTop() + + item.getParentMenu().getOffsetHeight(); + } + showChildMenuAt(item, top, left); + } + + protected void showChildMenuAt(CustomMenuItem item, int top, int left) { + final int shadowSpace = 10; + + popup = createOverlay(); + popup.setOwner(this); + + /* + * Use parents primary style name if possible and remove the submenu + * prefix if needed + */ + String primaryStyleName = parentMenu != null ? parentMenu + .getStylePrimaryName() : getStylePrimaryName(); + if (subMenu) { + primaryStyleName = primaryStyleName.replace( + SUBMENU_CLASSNAME_PREFIX, ""); + } + popup.setStyleName(primaryStyleName + "-popup"); + + // Setting owner and handlers to support tooltips. Needed for tooltip + // handling of overlay widgets (will direct queries to parent menu) + if (parentMenu == null) { + popup.setOwner(this); + } else { + VMenuBar parent = parentMenu; + while (parent.getParentMenu() != null) { + parent = parent.getParentMenu(); + } + popup.setOwner(parent); + } + if (client != null) { + client.getVTooltip().connectHandlersToWidget(popup); + } + + popup.setWidget(item.getSubMenu()); + popup.addCloseHandler(this); + popup.addAutoHidePartner(item.getElement()); + + // at 0,0 because otherwise IE7 add extra scrollbars (#5547) + popup.setPopupPosition(0, 0); + + item.getSubMenu().onShow(); + visibleChildMenu = item.getSubMenu(); + item.getSubMenu().setParentMenu(this); + + popup.show(); + + if (left + popup.getOffsetWidth() >= RootPanel.getBodyElement() + .getOffsetWidth() - shadowSpace) { + if (subMenu) { + left = item.getParentMenu().getAbsoluteLeft() + - popup.getOffsetWidth() - shadowSpace; + } else { + left = RootPanel.getBodyElement().getOffsetWidth() + - popup.getOffsetWidth() - shadowSpace; + } + // Accommodate space for shadow + if (left < shadowSpace) { + left = shadowSpace; + } + } + + top = adjustPopupHeight(top, shadowSpace); + + popup.setPopupPosition(left, top); + + } + + /** + * Create an overlay for the menu bar. + * + * This method can be overridden to use a custom overlay. + * + * @since 7.6 + * @return overlay to use + */ + protected VOverlay createOverlay() { + return new VOverlay(true, false, true); + } + + private int adjustPopupHeight(int top, final int shadowSpace) { + // Check that the popup will fit the screen + int availableHeight = RootPanel.getBodyElement().getOffsetHeight() + - top - shadowSpace; + int missingHeight = popup.getOffsetHeight() - availableHeight; + if (missingHeight > 0) { + // First move the top of the popup to get more space + // Don't move above top of screen, don't move more than needed + int moveUpBy = Math.min(top - shadowSpace, missingHeight); + + // Update state + top -= moveUpBy; + missingHeight -= moveUpBy; + availableHeight += moveUpBy; + + if (missingHeight > 0) { + int contentWidth = visibleChildMenu.getOffsetWidth(); + + // If there's still not enough room, limit height to fit and add + // a scroll bar + Style style = popup.getElement().getStyle(); + style.setHeight(availableHeight, Unit.PX); + style.setOverflowY(Overflow.SCROLL); + + // Make room for the scroll bar by adjusting the width of the + // popup + style.setWidth( + contentWidth + WidgetUtil.getNativeScrollbarSize(), + Unit.PX); + popup.positionOrSizeUpdated(); + } + } + return top; + } + + /** + * Hides the submenu of an item + * + * @param item + */ + public void hideChildMenu(CustomMenuItem item) { + if (visibleChildMenu != null + && !(visibleChildMenu == item.getSubMenu())) { + popup.hide(); + } + } + + /** + * When the menu is shown. + */ + public void onShow() { + // remove possible previous selection + if (selected != null) { + selected.setSelected(false); + selected = null; + } + menuVisible = true; + } + + /** + * Listener method, fired when this menu is closed + */ + @Override + public void onClose(CloseEvent event) { + hideChildren(); + if (event.isAutoClosed()) { + hideParents(true); + menuVisible = false; + } + visibleChildMenu = null; + popup = null; + } + + /** + * Recursively hide all child menus + */ + public void hideChildren() { + hideChildren(true, true); + } + + /** + * + * Recursively hide all child menus + * + * @param animateIn + * enable/disable animate-in animation when hide popup + * @param animateOut + * enable/disable animate-out animation when hide popup + * @since 7.3.7 + */ + public void hideChildren(boolean animateIn, boolean animateOut) { + if (visibleChildMenu != null) { + visibleChildMenu.hideChildren(animateIn, animateOut); + popup.hide(false, animateIn, animateOut); + } + } + + /** + * Recursively hide all parent menus + */ + public void hideParents(boolean autoClosed) { + if (visibleChildMenu != null) { + popup.hide(); + setSelected(null); + menuVisible = !autoClosed; + } + + if (getParentMenu() != null) { + getParentMenu().hideParents(autoClosed); + } + } + + /** + * Returns the parent menu of this menu, or null if this is the top-level + * menu + * + * @return + */ + public VMenuBar getParentMenu() { + return parentMenu; + } + + /** + * Set the parent menu of this menu + * + * @param parent + */ + public void setParentMenu(VMenuBar parent) { + parentMenu = parent; + } + + /** + * Returns the currently selected item of this menu, or null if nothing is + * selected + * + * @return + */ + public CustomMenuItem getSelected() { + return selected; + } + + /** + * Set the currently selected item of this menu + * + * @param item + */ + public void setSelected(CustomMenuItem item) { + // If we had something selected, unselect + if (item != selected && selected != null) { + selected.setSelected(false); + } + // If we have a valid selection, select it + if (item != null) { + item.setSelected(true); + } + + selected = item; + } + + /** + * + * A class to hold information on menu items + * + */ + public static class CustomMenuItem extends Widget implements HasHTML { + + protected String html = null; + protected Command command = null; + protected VMenuBar subMenu = null; + protected VMenuBar parentMenu = null; + protected boolean enabled = true; + protected boolean isSeparator = false; + protected boolean checkable = false; + protected boolean checked = false; + protected boolean selected = false; + protected String description = null; + + private String styleName; + + /** + * Default menu item {@link Widget} constructor for GWT.create(). + * + * Use {@link #setHTML(String)} and {@link #setCommand(Command)} after + * constructing a menu item. + */ + public CustomMenuItem() { + this("", null); + } + + /** + * Creates a menu item {@link Widget}. + * + * @param html + * @param cmd + * @deprecated use the default constructor and {@link #setHTML(String)} + * and {@link #setCommand(Command)} instead + */ + @Deprecated + public CustomMenuItem(String html, Command cmd) { + // We need spans to allow inline-block in IE + setElement(DOM.createSpan()); + + setHTML(html); + setCommand(cmd); + setSelected(false); + } + + @Override + public void setStyleName(String style) { + super.setStyleName(style); + updateStyleNames(); + + // Pass stylename down to submenus + if (getSubMenu() != null) { + getSubMenu().setStyleName(style); + } + } + + public void setSelected(boolean selected) { + this.selected = selected; + updateStyleNames(); + } + + public void setChecked(boolean checked) { + if (checkable && !isSeparator) { + this.checked = checked; + } else { + this.checked = false; + } + updateStyleNames(); + } + + public boolean isChecked() { + return checked; + } + + public void setCheckable(boolean checkable) { + if (checkable && !isSeparator) { + this.checkable = true; + } else { + setChecked(false); + this.checkable = false; + } + } + + public boolean isCheckable() { + return checkable; + } + + /* + * setters and getters for the fields + */ + + public void setSubMenu(VMenuBar subMenu) { + this.subMenu = subMenu; + } + + public VMenuBar getSubMenu() { + return subMenu; + } + + public void setParentMenu(VMenuBar parentMenu) { + this.parentMenu = parentMenu; + updateStyleNames(); + } + + protected void updateStyleNames() { + if (parentMenu == null) { + // Style names depend on the parent menu's primary style name so + // don't do updates until the item has a parent + return; + } + + String primaryStyleName = parentMenu.getStylePrimaryName(); + if (parentMenu.subMenu) { + primaryStyleName = primaryStyleName.replace( + SUBMENU_CLASSNAME_PREFIX, ""); + } + + String currentStyles = super.getStyleName(); + List customStyles = new ArrayList(); + for (String style : currentStyles.split(" ")) { + if (!style.isEmpty() && !style.startsWith(primaryStyleName)) { + customStyles.add(style); + } + } + + if (isSeparator) { + super.setStyleName(primaryStyleName + "-separator"); + } else { + super.setStyleName(primaryStyleName + "-menuitem"); + } + + for (String customStyle : customStyles) { + super.addStyleName(customStyle); + } + + if (styleName != null) { + addStyleDependentName(styleName); + } + + if (enabled) { + removeStyleDependentName("disabled"); + } else { + addStyleDependentName("disabled"); + } + + if (selected && isSelectable()) { + addStyleDependentName("selected"); + // needed for IE6 to have a single style name to match for an + // element + // TODO Can be optimized now that IE6 is not supported any more + if (checkable) { + if (checked) { + removeStyleDependentName("selected-unchecked"); + addStyleDependentName("selected-checked"); + } else { + removeStyleDependentName("selected-checked"); + addStyleDependentName("selected-unchecked"); + } + } + } else { + removeStyleDependentName("selected"); + // needed for IE6 to have a single style name to match for an + // element + removeStyleDependentName("selected-checked"); + removeStyleDependentName("selected-unchecked"); + } + + if (checkable && !isSeparator) { + if (checked) { + addStyleDependentName("checked"); + removeStyleDependentName("unchecked"); + } else { + addStyleDependentName("unchecked"); + removeStyleDependentName("checked"); + } + } + } + + public VMenuBar getParentMenu() { + return parentMenu; + } + + public void setCommand(Command command) { + this.command = command; + } + + public Command getCommand() { + return command; + } + + @Override + public String getHTML() { + return html; + } + + @Override + public void setHTML(String html) { + this.html = html; + DOM.setInnerHTML(getElement(), html); + + // Sink the onload event for any icons. The onload + // events are handled by the parent VMenuBar. + WidgetUtil.sinkOnloadForImages(getElement()); + } + + @Override + public String getText() { + return html; + } + + @Override + public void setText(String text) { + setHTML(WidgetUtil.escapeHTML(text)); + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + updateStyleNames(); + } + + public boolean isEnabled() { + return enabled; + } + + private void setSeparator(boolean separator) { + isSeparator = separator; + updateStyleNames(); + if (!separator) { + setEnabled(enabled); + } + } + + public boolean isSeparator() { + return isSeparator; + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + setSeparator(uidl.hasAttribute("separator")); + setEnabled(!uidl + .hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DISABLED)); + + if (!isSeparator() + && uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_CHECKED)) { + // if the selected attribute is present (either true or false), + // the item is selectable + setCheckable(true); + setChecked(uidl + .getBooleanAttribute(MenuBarConstants.ATTRIBUTE_CHECKED)); + } else { + setCheckable(false); + } + + if (uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_STYLE)) { + styleName = uidl + .getStringAttribute(MenuBarConstants.ATTRIBUTE_ITEM_STYLE); + } + + if (uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DESCRIPTION)) { + description = uidl + .getStringAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DESCRIPTION); + } + + updateStyleNames(); + } + + public TooltipInfo getTooltip() { + if (description == null) { + return null; + } + + return new TooltipInfo(description, null, this); + } + + /** + * Checks if the item can be selected. + * + * @return true if it is possible to select this item, false otherwise + */ + public boolean isSelectable() { + return !isSeparator() && isEnabled(); + } + + } + + /** + * @author Jouni Koivuviita / Vaadin Ltd. + */ + public void iLayout() { + iLayout(false); + updateSize(); + } + + public void iLayout(boolean iconLoadEvent) { + // Only collapse if there is more than one item in the root menu and the + // menu has an explicit size + if ((getItems().size() > 1 || (collapsedRootItems != null && collapsedRootItems + .getItems().size() > 0)) + && getElement().getStyle().getProperty("width") != null + && moreItem != null) { + + // Measure the width of the "more" item + final boolean morePresent = getItems().contains(moreItem); + addItem(moreItem); + final int moreItemWidth = moreItem.getOffsetWidth(); + if (!morePresent) { + removeItem(moreItem); + } + + int availableWidth = LayoutManager.get(client).getInnerWidth( + getElement()); + + // Used width includes the "more" item if present + int usedWidth = getConsumedWidth(); + int diff = availableWidth - usedWidth; + removeItem(moreItem); + + if (diff < 0) { + // Too many items: collapse last items from root menu + int widthNeeded = usedWidth - availableWidth; + if (!morePresent) { + widthNeeded += moreItemWidth; + } + int widthReduced = 0; + + while (widthReduced < widthNeeded && getItems().size() > 0) { + // Move last root menu item to collapsed menu + CustomMenuItem collapse = getItems().get( + getItems().size() - 1); + widthReduced += collapse.getOffsetWidth(); + removeItem(collapse); + collapsedRootItems.addItem(collapse, 0); + } + } else if (collapsedRootItems.getItems().size() > 0) { + // Space available for items: expand first items from collapsed + // menu + int widthAvailable = diff + moreItemWidth; + int widthGrowth = 0; + + while (widthAvailable > widthGrowth + && collapsedRootItems.getItems().size() > 0) { + // Move first item from collapsed menu to the root menu + CustomMenuItem expand = collapsedRootItems.getItems() + .get(0); + collapsedRootItems.removeItem(expand); + addItem(expand); + widthGrowth += expand.getOffsetWidth(); + if (collapsedRootItems.getItems().size() > 0) { + widthAvailable -= moreItemWidth; + } + if (widthGrowth > widthAvailable) { + removeItem(expand); + collapsedRootItems.addItem(expand, 0); + } else { + widthAvailable = diff + moreItemWidth; + } + } + } + if (collapsedRootItems.getItems().size() > 0) { + addItem(moreItem); + } + } + + // If a popup is open we might need to adjust the shadow as well if an + // icon shown in that popup was loaded + if (popup != null) { + // Forces a recalculation of the shadow size + popup.show(); + } + if (iconLoadEvent) { + // Size have changed if the width is undefined + Util.notifyParentOfSizeChange(this, false); + } + } + + private int getConsumedWidth() { + int w = 0; + for (CustomMenuItem item : getItems()) { + if (!collapsedRootItems.getItems().contains(item)) { + w += item.getOffsetWidth(); + } + } + return w; + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google + * .gwt.event.dom.client.KeyPressEvent) + */ + @Override + public void onKeyPress(KeyPressEvent event) { + // A bug fix for #14041 + // getKeyCode and getCharCode return different values for different + // browsers + int keyCode = event.getNativeEvent().getKeyCode(); + if (keyCode == 0) { + keyCode = event.getNativeEvent().getCharCode(); + } + if (handleNavigation(keyCode, + event.isControlKeyDown() || event.isMetaKeyDown(), + event.isShiftKeyDown())) { + event.preventDefault(); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt + * .event.dom.client.KeyDownEvent) + */ + @Override + public void onKeyDown(KeyDownEvent event) { + // A bug fix for #14041 + // getKeyCode and getCharCode return different values for different + // browsers + int keyCode = event.getNativeEvent().getKeyCode(); + if (keyCode == 0) { + keyCode = event.getNativeEvent().getCharCode(); + } + if (handleNavigation(keyCode, + event.isControlKeyDown() || event.isMetaKeyDown(), + event.isShiftKeyDown())) { + event.preventDefault(); + } + } + + /** + * Get the key that moves the selection upwards. By default it is the up + * arrow key but by overriding this you can change the key to whatever you + * want. + * + * @return The keycode of the key + */ + protected int getNavigationUpKey() { + return KeyCodes.KEY_UP; + } + + /** + * Get the key that moves the selection downwards. By default it is the down + * arrow key but by overriding this you can change the key to whatever you + * want. + * + * @return The keycode of the key + */ + protected int getNavigationDownKey() { + return KeyCodes.KEY_DOWN; + } + + /** + * Get the key that moves the selection left. By default it is the left + * arrow key but by overriding this you can change the key to whatever you + * want. + * + * @return The keycode of the key + */ + protected int getNavigationLeftKey() { + return KeyCodes.KEY_LEFT; + } + + /** + * Get the key that moves the selection right. By default it is the right + * arrow key but by overriding this you can change the key to whatever you + * want. + * + * @return The keycode of the key + */ + protected int getNavigationRightKey() { + return KeyCodes.KEY_RIGHT; + } + + /** + * Get the key that selects a menu item. By default it is the Enter key but + * by overriding this you can change the key to whatever you want. + * + * @deprecated use {@link #isNavigationSelectKey(int)} instead + * @return + */ + @Deprecated + protected int getNavigationSelectKey() { + return KeyCodes.KEY_ENTER; + } + + /** + * Checks whether key code selects a menu item. By default it is the Enter + * and Space keys but by overriding this you can change the keys to whatever + * you want. + * + * @since 7.2 + * @param keycode + * @return true if key selects menu item + */ + protected boolean isNavigationSelectKey(int keycode) { + return keycode == getNavigationSelectKey() + || keycode == KeyCodes.KEY_SPACE; + } + + /** + * Get the key that closes the menu. By default it is the escape key but by + * overriding this yoy can change the key to whatever you want. + * + * @return + */ + protected int getCloseMenuKey() { + return KeyCodes.KEY_ESCAPE; + } + + /** + * Handles the keyboard events handled by the MenuBar + * + * @param event + * The keyboard event received + * @return true iff the navigation event was handled + */ + public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { + + // If tab or shift+tab close menus + if (keycode == KeyCodes.KEY_TAB) { + setSelected(null); + hideChildren(); + menuVisible = false; + return false; + } + + if (ctrl || shift || !isEnabled()) { + // Do not handle tab key, nor ctrl keys + return false; + } + + if (keycode == getNavigationLeftKey()) { + if (getSelected() == null) { + // If nothing is selected then select the last item + setSelected(items.get(items.size() - 1)); + if (!getSelected().isSelectable()) { + handleNavigation(keycode, ctrl, shift); + } + } else if (visibleChildMenu == null && getParentMenu() == null) { + // If this is the root menu then move to the left + int idx = items.indexOf(getSelected()); + if (idx > 0) { + setSelected(items.get(idx - 1)); + } else { + setSelected(items.get(items.size() - 1)); + } + + if (!getSelected().isSelectable()) { + handleNavigation(keycode, ctrl, shift); + } + } else if (visibleChildMenu != null) { + // Redirect all navigation to the submenu + visibleChildMenu.handleNavigation(keycode, ctrl, shift); + + } else if (getParentMenu().getParentMenu() == null) { + // Inside a sub menu, whose parent is a root menu item + VMenuBar root = getParentMenu(); + + root.getSelected().getSubMenu().setSelected(null); + // #15255 - disable animate-in/out when hide popup + root.hideChildren(false, false); + + // Get the root menus items and select the previous one + int idx = root.getItems().indexOf(root.getSelected()); + idx = idx > 0 ? idx : root.getItems().size(); + CustomMenuItem selected = root.getItems().get(--idx); + + while (selected.isSeparator() || !selected.isEnabled()) { + idx = idx > 0 ? idx : root.getItems().size(); + selected = root.getItems().get(--idx); + } + + root.setSelected(selected); + openMenuAndFocusFirstIfPossible(selected); + } else { + getParentMenu().getSelected().getSubMenu().setSelected(null); + getParentMenu().hideChildren(); + } + + return true; + + } else if (keycode == getNavigationRightKey()) { + + if (getSelected() == null) { + // If nothing is selected then select the first item + setSelected(items.get(0)); + if (!getSelected().isSelectable()) { + handleNavigation(keycode, ctrl, shift); + } + } else if (visibleChildMenu == null && getParentMenu() == null) { + // If this is the root menu then move to the right + int idx = items.indexOf(getSelected()); + + if (idx < items.size() - 1) { + setSelected(items.get(idx + 1)); + } else { + setSelected(items.get(0)); + } + + if (!getSelected().isSelectable()) { + handleNavigation(keycode, ctrl, shift); + } + } else if (visibleChildMenu == null + && getSelected().getSubMenu() != null) { + // If the item has a submenu then show it and move the selection + // there + showChildMenu(getSelected()); + menuVisible = true; + visibleChildMenu.handleNavigation(keycode, ctrl, shift); + } else if (visibleChildMenu == null) { + + // Get the root menu + VMenuBar root = getParentMenu(); + while (root.getParentMenu() != null) { + root = root.getParentMenu(); + } + + // Hide the submenu (#15255 - disable animate-in/out when hide + // popup) + root.hideChildren(false, false); + + // Get the root menus items and select the next one + int idx = root.getItems().indexOf(root.getSelected()); + idx = idx < root.getItems().size() - 1 ? idx : -1; + CustomMenuItem selected = root.getItems().get(++idx); + + while (selected.isSeparator() || !selected.isEnabled()) { + idx = idx < root.getItems().size() - 1 ? idx : -1; + selected = root.getItems().get(++idx); + } + + root.setSelected(selected); + openMenuAndFocusFirstIfPossible(selected); + } else if (visibleChildMenu != null) { + // Redirect all navigation to the submenu + visibleChildMenu.handleNavigation(keycode, ctrl, shift); + } + + return true; + + } else if (keycode == getNavigationUpKey()) { + + if (getSelected() == null) { + // If nothing is selected then select the last item + setSelected(items.get(items.size() - 1)); + if (!getSelected().isSelectable()) { + handleNavigation(keycode, ctrl, shift); + } + } else if (visibleChildMenu != null) { + // Redirect all navigation to the submenu + visibleChildMenu.handleNavigation(keycode, ctrl, shift); + } else { + // Select the previous item if possible or loop to the last item + int idx = items.indexOf(getSelected()); + if (idx > 0) { + setSelected(items.get(idx - 1)); + } else { + setSelected(items.get(items.size() - 1)); + } + + if (!getSelected().isSelectable()) { + handleNavigation(keycode, ctrl, shift); + } + } + + return true; + + } else if (keycode == getNavigationDownKey()) { + + if (getSelected() == null) { + // If nothing is selected then select the first item + selectFirstItem(); + } else if (visibleChildMenu == null && getParentMenu() == null) { + // If this is the root menu the show the child menu with arrow + // down, if there is a child menu + openMenuAndFocusFirstIfPossible(getSelected()); + } else if (visibleChildMenu != null) { + // Redirect all navigation to the submenu + visibleChildMenu.handleNavigation(keycode, ctrl, shift); + } else { + // Select the next item if possible or loop to the first item + int idx = items.indexOf(getSelected()); + if (idx < items.size() - 1) { + setSelected(items.get(idx + 1)); + } else { + setSelected(items.get(0)); + } + + if (!getSelected().isSelectable()) { + handleNavigation(keycode, ctrl, shift); + } + } + return true; + + } else if (keycode == getCloseMenuKey()) { + setSelected(null); + hideChildren(); + menuVisible = false; + + } else if (isNavigationSelectKey(keycode)) { + if (getSelected() == null) { + // If nothing is selected then select the first item + selectFirstItem(); + } else if (visibleChildMenu != null) { + // Redirect all navigation to the submenu + visibleChildMenu.handleNavigation(keycode, ctrl, shift); + menuVisible = false; + } else if (visibleChildMenu == null + && getSelected().getSubMenu() != null) { + // If the item has a sub menu then show it and move the + // selection there + openMenuAndFocusFirstIfPossible(getSelected()); + } else { + final Command command = getSelected().getCommand(); + + setSelected(null); + hideParents(true); + + // #17076 keyboard selected menuitem without children: do + // not leave menu to visible ("hover open") mode + menuVisible = false; + + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + if (command != null) { + command.execute(); + } + } + }); + } + } + + return false; + } + + private void selectFirstItem() { + for (int i = 0; i < items.size(); i++) { + CustomMenuItem item = items.get(i); + if (item.isSelectable()) { + setSelected(item); + break; + } + } + } + + private void openMenuAndFocusFirstIfPossible(CustomMenuItem menuItem) { + VMenuBar subMenu = menuItem.getSubMenu(); + if (subMenu == null) { + // No child menu? Nothing to do + return; + } + + VMenuBar parentMenu = menuItem.getParentMenu(); + parentMenu.showChildMenu(menuItem); + + menuVisible = true; + // Select the first item in the newly open submenu + subMenu.selectFirstItem(); + + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event + * .dom.client.FocusEvent) + */ + @Override + public void onFocus(FocusEvent event) { + + } + + private final String SUBPART_PREFIX = "item"; + + @Override + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + if (subPart.startsWith(SUBPART_PREFIX)) { + int index = Integer.parseInt(subPart.substring(SUBPART_PREFIX + .length())); + CustomMenuItem item = getItems().get(index); + + return item.getElement(); + } else { + Queue submenuItems = new LinkedList(); + for (CustomMenuItem item : getItems()) { + if (isItemNamed(item, subPart)) { + return item.getElement(); + } + if (item.getSubMenu() != null) { + submenuItems.addAll(item.getSubMenu().getItems()); + } + } + while (!submenuItems.isEmpty()) { + CustomMenuItem item = submenuItems.poll(); + if (!item.isSeparator() && isItemNamed(item, subPart)) { + return item.getElement(); + } + if (item.getSubMenu() != null && item.getSubMenu().menuVisible) { + submenuItems.addAll(item.getSubMenu().getItems()); + } + + } + return null; + } + } + + private boolean isItemNamed(CustomMenuItem item, String name) { + Element lastChildElement = getLastChildElement(item); + if (getText(lastChildElement).equals(name)) { + return true; + } + return false; + } + + /* + * Returns the text content of element without including the text of + * possible nested elements. It is assumed that the last child of element + * contains the text of interest and that the last child does not itself + * have children with text content. This method is used by + * getSubPartElement(String) so that possible text icons are not included in + * the textual matching (#14879). + */ + private native String getText(Element element) + /*-{ + var n = element.childNodes.length; + if(n > 0){ + return element.childNodes[n - 1].nodeValue; + } + else{ + return ""; + } + }-*/; + + private Element getLastChildElement(CustomMenuItem item) { + Element lastChildElement = item.getElement().getFirstChildElement(); + while (lastChildElement.getNextSiblingElement() != null) { + lastChildElement = lastChildElement.getNextSiblingElement(); + } + return lastChildElement; + } + + @Override + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + if (!getElement().isOrHasChild(subElement)) { + return null; + } + + Element menuItemRoot = subElement; + while (menuItemRoot != null && menuItemRoot.getParentElement() != null + && menuItemRoot.getParentElement() != getElement()) { + menuItemRoot = menuItemRoot.getParentElement().cast(); + } + // "menuItemRoot" is now the root of the menu item + + final int itemCount = getItems().size(); + for (int i = 0; i < itemCount; i++) { + if (getItems().get(i).getElement() == menuItemRoot) { + String name = SUBPART_PREFIX + i; + return name; + } + } + return null; + } + + /** + * Get menu item with given DOM element + * + * @param element + * Element used in search + * @return Menu item or null if not found + * @deprecated As of 7.2, call or override + * {@link #getMenuItemWithElement(Element)} instead + */ + @Deprecated + public CustomMenuItem getMenuItemWithElement( + com.google.gwt.user.client.Element element) { + for (int i = 0; i < items.size(); i++) { + CustomMenuItem item = items.get(i); + if (DOM.isOrHasChild(item.getElement(), element)) { + return item; + } + + if (item.getSubMenu() != null) { + item = item.getSubMenu().getMenuItemWithElement(element); + if (item != null) { + return item; + } + } + } + + return null; + } + + /** + * Get menu item with given DOM element + * + * @param element + * Element used in search + * @return Menu item or null if not found + * + * @since 7.2 + */ + public CustomMenuItem getMenuItemWithElement(Element element) { + return getMenuItemWithElement(DOM.asOld(element)); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VNativeButton.java b/client/src/main/java/com/vaadin/client/ui/VNativeButton.java new file mode 100644 index 0000000000..77b2515f45 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VNativeButton.java @@ -0,0 +1,162 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Button; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.MouseEventDetailsBuilder; +import com.vaadin.client.StyleConstants; +import com.vaadin.client.Util; +import com.vaadin.shared.MouseEventDetails; +import com.vaadin.shared.ui.button.ButtonServerRpc; + +public class VNativeButton extends Button implements ClickHandler { + + public static final String CLASSNAME = "v-nativebutton"; + + /** For internal use only. May be removed or replaced in the future. */ + public String paintableId; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public ButtonServerRpc buttonRpcProxy; + + /** For internal use only. May be removed or replaced in the future. */ + public Element errorIndicatorElement; + + /** For internal use only. May be removed or replaced in the future. */ + public final Element captionElement = DOM.createSpan(); + + /** For internal use only. May be removed or replaced in the future. */ + public Icon icon; + + /** + * Helper flag to handle special-case where the button is moved from under + * mouse while clicking it. In this case mouse leaves the button without + * moving. + */ + private boolean clickPending; + + private boolean cancelNextClick = false; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean disableOnClick = false; + + public VNativeButton() { + setStyleName(CLASSNAME); + + getElement().appendChild(captionElement); + captionElement.setClassName(getStyleName() + "-caption"); + + addClickHandler(this); + + sinkEvents(Event.ONMOUSEDOWN | Event.ONLOAD | Event.ONMOUSEMOVE + | Event.ONFOCUS); + } + + @Override + public void setText(String text) { + captionElement.setInnerText(text); + } + + @Override + public void setHTML(String html) { + captionElement.setInnerHTML(html); + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + + if (DOM.eventGetType(event) == Event.ONLOAD) { + Util.notifyParentOfSizeChange(this, true); + + } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN + && event.getButton() == Event.BUTTON_LEFT) { + clickPending = true; + + } else if (DOM.eventGetType(event) == Event.ONMOUSEMOVE) { + clickPending = false; + } else if (DOM.eventGetType(event) == Event.ONMOUSEOUT) { + if (clickPending) { + click(); + } + clickPending = false; + } else if (event.getTypeInt() == Event.ONFOCUS) { + if (BrowserInfo.get().isIE() + && BrowserInfo.get().getBrowserMajorVersion() < 11 + && clickPending) { + /* + * The focus event will mess up IE and IE will not trigger the + * mouse up event (which in turn triggers the click event) until + * the mouse is moved. This will result in it appearing as a + * native button not triggering the event. So we manually + * trigger the click here and cancel the next original event + * which will occur on the next mouse move. See ticket #11094 + * for details. + */ + click(); + clickPending = false; + cancelNextClick = true; + } + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event + * .dom.client.ClickEvent) + */ + @Override + public void onClick(ClickEvent event) { + if (paintableId == null || client == null || cancelNextClick) { + cancelNextClick = false; + return; + } + + if (BrowserInfo.get().isWebkit()) { + // Webkit does not focus non-text input elements on click + // (#11854) + setFocus(true); + } + if (disableOnClick) { + setEnabled(false); + // FIXME: This should be moved to NativeButtonConnector along with + // buttonRpcProxy + addStyleName(StyleConstants.DISABLED); + buttonRpcProxy.disableOnClick(); + } + + // Add mouse details + MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(event.getNativeEvent(), getElement()); + buttonRpcProxy.click(details); + + clickPending = false; + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VNativeSelect.java b/client/src/main/java/com/vaadin/client/ui/VNativeSelect.java new file mode 100644 index 0000000000..330442b550 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VNativeSelect.java @@ -0,0 +1,154 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.ArrayList; +import java.util.Iterator; + +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.user.client.ui.ListBox; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.UIDL; + +public class VNativeSelect extends VOptionGroupBase implements Field { + + public static final String CLASSNAME = "v-select"; + + protected ListBox select; + + private boolean firstValueIsTemporaryNullItem = false; + + public VNativeSelect() { + super(new ListBox(false), CLASSNAME); + select = getOptionsContainer(); + select.setVisibleItemCount(1); + select.addChangeHandler(this); + select.setStyleName(CLASSNAME + "-select"); + + updateEnabledState(); + } + + protected ListBox getOptionsContainer() { + return (ListBox) optionsContainer; + } + + @Override + public void buildOptions(UIDL uidl) { + select.clear(); + firstValueIsTemporaryNullItem = false; + + if (isNullSelectionAllowed() && !isNullSelectionItemAvailable()) { + // can't unselect last item in singleselect mode + select.addItem("", (String) null); + } + boolean selected = false; + for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { + final UIDL optionUidl = (UIDL) i.next(); + select.addItem(optionUidl.getStringAttribute("caption"), + optionUidl.getStringAttribute("key")); + if (optionUidl.hasAttribute("selected")) { + select.setItemSelected(select.getItemCount() - 1, true); + selected = true; + } + } + if (!selected && !isNullSelectionAllowed()) { + // null-select not allowed, but value not selected yet; add null and + // remove when something is selected + select.insertItem("", (String) null, 0); + select.setItemSelected(0, true); + firstValueIsTemporaryNullItem = true; + } + } + + @Override + protected String[] getSelectedItems() { + final ArrayList selectedItemKeys = new ArrayList(); + for (int i = 0; i < select.getItemCount(); i++) { + if (select.isItemSelected(i)) { + selectedItemKeys.add(select.getValue(i)); + } + } + return selectedItemKeys.toArray(new String[selectedItemKeys.size()]); + } + + @Override + public void onChange(ChangeEvent event) { + + if (select.isMultipleSelect()) { + client.updateVariable(paintableId, "selected", getSelectedItems(), + isImmediate()); + } else { + client.updateVariable(paintableId, "selected", new String[] { "" + + getSelectedItem() }, isImmediate()); + } + if (firstValueIsTemporaryNullItem) { + // remove temporary empty item + select.removeItem(0); + firstValueIsTemporaryNullItem = false; + /* + * Workaround to achrome bug that may cause value change event not + * to fire when selection is done with keyboard. + * + * http://dev.vaadin.com/ticket/10109 + * + * Problem is confirmed to exist only on Chrome-Win, but just + * execute in for all webkits. Probably exists also in other + * webkits/blinks on windows. + */ + if (BrowserInfo.get().isWebkit()) { + select.getElement().blur(); + select.getElement().focus(); + } + + } + } + + @Override + public void setHeight(String height) { + select.setHeight(height); + super.setHeight(height); + } + + @Override + public void setWidth(String width) { + select.setWidth(width); + super.setWidth(width); + } + + @Override + public void setTabIndex(int tabIndex) { + getOptionsContainer().setTabIndex(tabIndex); + } + + @Override + protected void updateEnabledState() { + select.setEnabled(isEnabled() && !isReadonly()); + } + + @Override + public void focus() { + select.setFocus(true); + } + + /** + * @return the root select widget + */ + public ListBox getSelect() { + return getOptionsContainer(); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VNotification.java b/client/src/main/java/com/vaadin/client/ui/VNotification.java new file mode 100644 index 0000000000..1ff51df8d0 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VNotification.java @@ -0,0 +1,683 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.ArrayList; +import java.util.EventObject; +import java.util.Iterator; + +import com.google.gwt.aria.client.Roles; +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.AnimationUtil; +import com.vaadin.client.AnimationUtil.AnimationEndListener; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.UIDL; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.aria.AriaHelper; +import com.vaadin.shared.Position; +import com.vaadin.shared.ui.ui.NotificationRole; +import com.vaadin.shared.ui.ui.UIConstants; +import com.vaadin.shared.ui.ui.UIState.NotificationTypeConfiguration; + +public class VNotification extends VOverlay { + + public static final Position CENTERED = Position.MIDDLE_CENTER; + public static final Position CENTERED_TOP = Position.TOP_CENTER; + public static final Position CENTERED_BOTTOM = Position.BOTTOM_CENTER; + public static final Position TOP_LEFT = Position.TOP_LEFT; + public static final Position TOP_RIGHT = Position.TOP_RIGHT; + public static final Position BOTTOM_LEFT = Position.BOTTOM_LEFT; + public static final Position BOTTOM_RIGHT = Position.BOTTOM_RIGHT; + + private static final String STYLENAME_POSITION_TOP = "v-position-top"; + private static final String STYLENAME_POSITION_RIGHT = "v-position-right"; + private static final String STYLENAME_POSITION_BOTTOM = "v-position-bottom"; + private static final String STYLENAME_POSITION_LEFT = "v-position-left"; + private static final String STYLENAME_POSITION_MIDDLE = "v-position-middle"; + private static final String STYLENAME_POSITION_CENTER = "v-position-center"; + private static final String STYLENAME_POSITION_ASSISTIVE = "v-position-assistive"; + + public static final String CAPTION = "caption"; + public static final String DESCRIPTION = "description"; + public static final String DETAILS = "details"; + + /** + * Position that is only accessible for assistive devices, invisible for + * visual users. + */ + public static final Position ASSISTIVE = Position.ASSISTIVE; + + public static final int DELAY_FOREVER = -1; + public static final int DELAY_NONE = 0; + + private static final String STYLENAME = "v-Notification"; + private static final int mouseMoveThreshold = 7; + private static final int Z_INDEX_BASE = 20000; + public static final String STYLE_SYSTEM = "system"; + + private static final ArrayList notifications = new ArrayList(); + + private boolean infiniteDelay = false; + private int hideDelay = 0; + + private Timer delay; + + private int x = -1; + private int y = -1; + + private String temporaryStyle; + + private ArrayList listeners; + private static final int TOUCH_DEVICE_IDLE_DELAY = 1000; + + /** + * Default constructor. You should use GWT.create instead. + */ + public VNotification() { + setStyleName(STYLENAME); + sinkEvents(Event.ONCLICK); + getElement().getStyle().setZIndex(Z_INDEX_BASE); + } + + /** + * @deprecated Use static {@link #createNotification(int)} instead to enable + * GWT deferred binding. + * + * @param delayMsec + */ + @Deprecated + public VNotification(int delayMsec) { + this(); + setDelay(delayMsec); + + if (BrowserInfo.get().isTouchDevice()) { + new Timer() { + @Override + public void run() { + if (isAttached()) { + hide(); + } + } + }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY); + } + } + + /** + * @deprecated Use static {@link #createNotification(int, int, int)} instead + * to enable GWT deferred binding. + * + * @param delayMsec + * @param fadeMsec + * @param startOpacity + */ + @Deprecated + public VNotification(int delayMsec, int fadeMsec, int startOpacity) { + this(delayMsec); + AnimationUtil.setAnimationDuration(getElement(), fadeMsec + "ms"); + getElement().getStyle().setOpacity(startOpacity / 100); + } + + private void setDelay(int delayMsec) { + if (delayMsec < 0) { + infiniteDelay = true; + hideDelay = 0; + } else { + infiniteDelay = false; + hideDelay = delayMsec; + } + } + + @Override + public void show() { + show(CENTERED); + } + + public void show(String style) { + show(CENTERED, style); + } + + public void show(com.vaadin.shared.Position position) { + show(position, null); + } + + public void show(Widget widget, Position position, String style) { + NotificationTypeConfiguration styleSetup = getUiState(style); + setWaiAriaRole(styleSetup); + + FlowPanel panel = new FlowPanel(); + if (hasPrefix(styleSetup)) { + panel.add(new Label(styleSetup.prefix)); + AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(), + true); + } + + panel.add(widget); + + if (hasPostfix(styleSetup)) { + panel.add(new Label(styleSetup.postfix)); + AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(), + true); + } + setWidget(panel); + show(position, style); + } + + private boolean hasPostfix(NotificationTypeConfiguration styleSetup) { + return styleSetup != null && styleSetup.postfix != null + && !styleSetup.postfix.isEmpty(); + } + + private boolean hasPrefix(NotificationTypeConfiguration styleSetup) { + return styleSetup != null && styleSetup.prefix != null + && !styleSetup.prefix.isEmpty(); + } + + public void show(String html, Position position, String style) { + NotificationTypeConfiguration styleSetup = getUiState(style); + String assistiveDeviceOnlyStyle = AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE; + + setWaiAriaRole(styleSetup); + + String type = ""; + String usage = ""; + + if (hasPrefix(styleSetup)) { + type = "" + + styleSetup.prefix + ""; + } + + if (hasPostfix(styleSetup)) { + usage = "" + + styleSetup.postfix + ""; + } + + setWidget(new HTML(type + html + usage)); + show(position, style); + } + + private NotificationTypeConfiguration getUiState(String style) { + if (getApplicationConnection() == null + || getApplicationConnection().getUIConnector() == null) { + return null; + } + + return getApplicationConnection().getUIConnector().getState().notificationConfigurations + .get(style); + } + + private void setWaiAriaRole(NotificationTypeConfiguration styleSetup) { + Roles.getAlertRole().set(getElement()); + + if (styleSetup != null && styleSetup.notificationRole != null) { + if (NotificationRole.STATUS == styleSetup.notificationRole) { + Roles.getStatusRole().set(getElement()); + } + } + } + + public void show(Position position, String style) { + if (temporaryStyle != null) { + removeStyleName(temporaryStyle); + removeStyleDependentName(temporaryStyle); + temporaryStyle = null; + } + if (style != null && style.length() > 0) { + temporaryStyle = style; + addStyleName(style); + addStyleDependentName(style); + } + + setPosition(position); + super.show(); + updatePositionOffsets(position); + notifications.add(this); + positionOrSizeUpdated(); + /** + * Android 4 fails to render notifications correctly without a little + * nudge (#8551) Chrome 41 now requires this too (#17252) + */ + if (BrowserInfo.get().isAndroid() || isChrome41OrHigher()) { + WidgetUtil.setStyleTemporarily(getElement(), "display", "none"); + } + } + + private boolean isChrome41OrHigher() { + return BrowserInfo.get().isChrome() + && BrowserInfo.get().getBrowserMajorVersion() >= 41; + } + + protected void hideAfterDelay() { + if (delay == null) { + delay = new Timer() { + @Override + public void run() { + VNotification.super.hide(); + } + }; + delay.schedule(hideDelay); + } + } + + @Override + public void hide() { + if (delay != null) { + delay.cancel(); + } + // Run only once + if (notifications.contains(this)) { + DOM.removeEventPreview(this); + + // Still animating in, wait for it to finish before touching + // the animation delay (which would restart the animation-in + // in some browsers) + if (getStyleName().contains( + VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN)) { + AnimationUtil.addAnimationEndListener(getElement(), + new AnimationEndListener() { + @Override + public void onAnimationEnd(NativeEvent event) { + if (AnimationUtil + .getAnimationName(event) + .contains( + VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN)) { + VNotification.this.hide(); + } + } + }); + } else { + VNotification.super.hide(); + fireEvent(new HideEvent(this)); + notifications.remove(this); + } + } + } + + private void updatePositionOffsets(com.vaadin.shared.Position position) { + final Element el = getElement(); + + // Remove all offsets (GWT PopupPanel defaults) + el.getStyle().clearTop(); + el.getStyle().clearLeft(); + + switch (position) { + case MIDDLE_LEFT: + case MIDDLE_RIGHT: + center(); + el.getStyle().clearLeft(); + break; + case TOP_CENTER: + case BOTTOM_CENTER: + center(); + el.getStyle().clearTop(); + break; + case MIDDLE_CENTER: + center(); + break; + } + } + + public void setPosition(com.vaadin.shared.Position position) { + final Element el = getElement(); + + // Remove any previous positions + el.removeClassName(STYLENAME_POSITION_TOP); + el.removeClassName(STYLENAME_POSITION_RIGHT); + el.removeClassName(STYLENAME_POSITION_BOTTOM); + el.removeClassName(STYLENAME_POSITION_LEFT); + el.removeClassName(STYLENAME_POSITION_MIDDLE); + el.removeClassName(STYLENAME_POSITION_CENTER); + el.removeClassName(STYLENAME_POSITION_ASSISTIVE); + + switch (position) { + case TOP_LEFT: + el.addClassName(STYLENAME_POSITION_TOP); + el.addClassName(STYLENAME_POSITION_LEFT); + break; + case TOP_RIGHT: + el.addClassName(STYLENAME_POSITION_TOP); + el.addClassName(STYLENAME_POSITION_RIGHT); + break; + case MIDDLE_LEFT: + el.addClassName(STYLENAME_POSITION_MIDDLE); + el.addClassName(STYLENAME_POSITION_LEFT); + break; + case MIDDLE_RIGHT: + el.addClassName(STYLENAME_POSITION_MIDDLE); + el.addClassName(STYLENAME_POSITION_RIGHT); + break; + case BOTTOM_RIGHT: + el.addClassName(STYLENAME_POSITION_BOTTOM); + el.addClassName(STYLENAME_POSITION_RIGHT); + break; + case BOTTOM_LEFT: + el.addClassName(STYLENAME_POSITION_BOTTOM); + el.addClassName(STYLENAME_POSITION_LEFT); + break; + case TOP_CENTER: + el.addClassName(STYLENAME_POSITION_TOP); + el.addClassName(STYLENAME_POSITION_CENTER); + break; + case BOTTOM_CENTER: + el.addClassName(STYLENAME_POSITION_BOTTOM); + el.addClassName(STYLENAME_POSITION_CENTER); + break; + case ASSISTIVE: + el.addClassName(STYLENAME_POSITION_ASSISTIVE); + break; + } + } + + @Override + public void onBrowserEvent(Event event) { + hide(); + } + + @Override + /* + * Fix for #14689: {@link #onEventPreview(Event)} method is deprecated and + * it's called now only for the very first handler (see super impl). We need + * it to work for any handler. So let's call old {@link + * #onEventPreview(Event)} method explicitly with updated logic for {@link + * #onPreviewNativeEvent(Event)}. + */ + protected void onPreviewNativeEvent(NativePreviewEvent event) { + if (!onEventPreview(Event.as(event.getNativeEvent()))) { + event.cancel(); + } + } + + @Override + public boolean onEventPreview(Event event) { + int type = DOM.eventGetType(event); + // "modal" + if (infiniteDelay || temporaryStyle == STYLE_SYSTEM) { + if (type == Event.ONCLICK || type == Event.ONTOUCHEND) { + if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) { + hide(); + return false; + } + } else if (type == Event.ONKEYDOWN + && event.getKeyCode() == KeyCodes.KEY_ESCAPE) { + hide(); + return false; + } + if (temporaryStyle == STYLE_SYSTEM) { + return true; + } else { + return false; + } + } + // default + switch (type) { + case Event.ONMOUSEMOVE: + if (x < 0) { + x = DOM.eventGetClientX(event); + y = DOM.eventGetClientY(event); + } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold + || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) { + hideAfterDelay(); + } + break; + case Event.ONMOUSEDOWN: + case Event.ONMOUSEWHEEL: + case Event.ONSCROLL: + hideAfterDelay(); + break; + case Event.ONKEYDOWN: + if (event.getRepeat()) { + return true; + } + hideAfterDelay(); + break; + default: + break; + } + return true; + } + + public void addEventListener(EventListener listener) { + if (listeners == null) { + listeners = new ArrayList(); + } + listeners.add(listener); + } + + public void removeEventListener(EventListener listener) { + if (listeners == null) { + return; + } + listeners.remove(listener); + } + + private void fireEvent(HideEvent event) { + if (listeners != null) { + for (Iterator it = listeners.iterator(); it + .hasNext();) { + EventListener l = it.next(); + l.notificationHidden(event); + } + } + } + + public static void showNotification(ApplicationConnection client, + final UIDL notification) { + boolean onlyPlainText = notification + .hasAttribute(UIConstants.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED); + String html = ""; + if (notification.hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON)) { + String iconUri = notification + .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON); + html += client.getIcon(iconUri).getElement().getString(); + } + if (notification + .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION)) { + String caption = notification + .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION); + if (onlyPlainText) { + caption = WidgetUtil.escapeHTML(caption); + caption = caption.replaceAll("\\n", "
"); + } + html += "

" + + caption + "

"; + } + if (notification + .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE)) { + String message = notification + .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE); + if (onlyPlainText) { + message = WidgetUtil.escapeHTML(message); + message = message.replaceAll("\\n", "
"); + } + html += "

" + message + "

"; + } + + final String style = notification + .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE) ? notification + .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE) + : null; + + final int pos = notification + .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_POSITION); + Position position = Position.values()[pos]; + + final int delay = notification + .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_DELAY); + createNotification(delay, client.getUIConnector().getWidget()).show( + html, position, style); + } + + /** + * Meant for internal usage only. + * + * @since 7.5.0 + * @param client + * application connection + * @param style + * the dependent style name + * @return the given dependent style name prefixed with current notification + * primary style + */ + public static String getDependentStyle(ApplicationConnection client, + String style) { + VNotification notification = createNotification(-1, client + .getUIConnector().getWidget()); + String styleName = notification.getStyleName(); + notification.addStyleDependentName(style); + String extendedStyle = notification.getStyleName(); + return extendedStyle.substring(styleName.length()).trim(); + } + + public static VNotification createNotification(int delayMsec, Widget owner) { + final VNotification notification = GWT.create(VNotification.class); + notification.setWaiAriaRole(null); + notification.setDelay(delayMsec); + + if (!notification.infiniteDelay && BrowserInfo.get().isTouchDevice()) { + new Timer() { + @Override + public void run() { + if (notification.isAttached()) { + notification.hide(); + } + } + }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY); + } + notification.setOwner(owner); + return notification; + } + + public class HideEvent extends EventObject { + + public HideEvent(Object source) { + super(source); + } + } + + public interface EventListener extends java.util.EventListener { + public void notificationHidden(HideEvent event); + } + + /** + * Moves currently visible notifications to the top of the event preview + * stack. Can be called when opening other overlays such as subwindows to + * ensure the notifications receive the events they need and don't linger + * indefinitely. See #7136. + * + * TODO Should this be a generic Overlay feature instead? + */ + public static void bringNotificationsToFront() { + for (VNotification notification : notifications) { + DOM.removeEventPreview(notification); + DOM.addEventPreview(notification); + } + } + + /** + * Shows an error notification and redirects the user to the given URL when + * she clicks on the notification. + * + * If both message and caption are null, redirects the user to the url + * immediately + * + * @since 7.5.1 + * @param connection + * A reference to the ApplicationConnection + * @param caption + * The caption for the error or null to exclude the caption + * @param message + * The message for the error or null to exclude the message + * @param details + * A details message or null to exclude the details + * @param url + * A url to redirect to after the user clicks the error + * notification + */ + public static void showError(ApplicationConnection connection, + String caption, String message, String details, String url) { + + StringBuilder html = new StringBuilder(); + if (caption != null) { + html.append("

"); + html.append(caption); + html.append("

"); + } + if (message != null) { + html.append("

"); + html.append(message); + html.append("

"); + } + + if (html.length() > 0) { + + // Add error description + if (details != null) { + html.append("

"); + html.append(""); + html.append(details); + html.append("

"); + } + + VNotification n = VNotification.createNotification(1000 * 60 * 45, + connection.getUIConnector().getWidget()); + n.addEventListener(new NotificationRedirect(url)); + n.show(html.toString(), VNotification.CENTERED_TOP, + VNotification.STYLE_SYSTEM); + } else { + WidgetUtil.redirect(url); + } + } + + /** + * Listens for Notification hide event, and redirects. Used for system + * messages, such as session expired. + * + */ + private static class NotificationRedirect implements + VNotification.EventListener { + String url; + + NotificationRedirect(String url) { + this.url = url; + } + + @Override + public void notificationHidden(HideEvent event) { + WidgetUtil.redirect(url); + } + + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VOptionGroup.java b/client/src/main/java/com/vaadin/client/ui/VOptionGroup.java new file mode 100644 index 0000000000..9a28111dc5 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VOptionGroup.java @@ -0,0 +1,311 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.google.gwt.aria.client.Roles; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.LoadEvent; +import com.google.gwt.event.dom.client.LoadHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.FocusWidget; +import com.google.gwt.user.client.ui.Focusable; +import com.google.gwt.user.client.ui.HasEnabled; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.RadioButton; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.StyleConstants; +import com.vaadin.client.UIDL; +import com.vaadin.client.Util; +import com.vaadin.client.WidgetUtil; +import com.vaadin.shared.EventId; +import com.vaadin.shared.ui.optiongroup.OptionGroupConstants; + +public class VOptionGroup extends VOptionGroupBase implements FocusHandler, + BlurHandler { + + public static final String CLASSNAME = "v-select-optiongroup"; + + /** For internal use only. May be removed or replaced in the future. */ + public final Panel panel; + + private final Map optionsToKeys; + + private final Map optionsEnabled; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean sendFocusEvents = false; + /** For internal use only. May be removed or replaced in the future. */ + public boolean sendBlurEvents = false; + /** For internal use only. May be removed or replaced in the future. */ + public List focusHandlers = null; + /** For internal use only. May be removed or replaced in the future. */ + public List blurHandlers = null; + + private final LoadHandler iconLoadHandler = new LoadHandler() { + @Override + public void onLoad(LoadEvent event) { + Util.notifyParentOfSizeChange(VOptionGroup.this, true); + } + }; + + /** + * used to check whether a blur really was a blur of the complete + * optiongroup: if a control inside this optiongroup gains focus right after + * blur of another control inside this optiongroup (meaning: if onFocus + * fires after onBlur has fired), the blur and focus won't be sent to the + * server side as only a focus change inside this optiongroup occured + */ + private boolean blurOccured = false; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean htmlContentAllowed = false; + + private boolean wasHtmlContentAllowed = false; + private boolean wasMultiselect = false; + + public VOptionGroup() { + super(CLASSNAME); + panel = (Panel) optionsContainer; + optionsToKeys = new HashMap(); + optionsEnabled = new HashMap(); + + wasMultiselect = isMultiselect(); + } + + /* + * Try to update content of existing elements, rebuild panel entirely + * otherwise + */ + @Override + public void buildOptions(UIDL uidl) { + /* + * In order to retain focus, we need to update values rather than + * recreate panel from scratch (#10451). However, the panel will be + * rebuilt (losing focus) if number of elements or their order is + * changed. + */ + HashMap keysToOptions = new HashMap(); + for (Map.Entry entry : optionsToKeys.entrySet()) { + keysToOptions.put(entry.getValue(), entry.getKey()); + } + ArrayList existingwidgets = new ArrayList(); + ArrayList newwidgets = new ArrayList(); + + // Get current order of elements + for (Widget wid : panel) { + existingwidgets.add(wid); + } + + optionsEnabled.clear(); + + if (isMultiselect()) { + Roles.getGroupRole().set(getElement()); + } else { + Roles.getRadiogroupRole().set(getElement()); + } + + for (final Iterator it = uidl.getChildIterator(); it.hasNext();) { + final UIDL opUidl = (UIDL) it.next(); + + String itemHtml = opUidl.getStringAttribute("caption"); + if (!htmlContentAllowed) { + itemHtml = WidgetUtil.escapeHTML(itemHtml); + } + + String iconUrl = opUidl.getStringAttribute("icon"); + if (iconUrl != null && iconUrl.length() != 0) { + Icon icon = client.getIcon(iconUrl); + itemHtml = icon.getElement().getString() + itemHtml; + } + + String key = opUidl.getStringAttribute("key"); + CheckBox op = keysToOptions.get(key); + + // Need to recreate object if isMultiselect is changed (#10451) + // OR if htmlContentAllowed changed due to Safari 5 issue + if ((op == null) || (htmlContentAllowed != wasHtmlContentAllowed) + || (isMultiselect() != wasMultiselect)) { + // Create a new element + if (isMultiselect()) { + op = new VCheckBox(); + } else { + op = new RadioButton(paintableId); + op.setStyleName("v-radiobutton"); + } + if (iconUrl != null && iconUrl.length() != 0) { + WidgetUtil.sinkOnloadForImages(op.getElement()); + op.addHandler(iconLoadHandler, LoadEvent.getType()); + } + + op.addStyleName(CLASSNAME_OPTION); + op.addClickHandler(this); + + optionsToKeys.put(op, key); + } + + op.setHTML(itemHtml); + op.setValue(opUidl.getBooleanAttribute("selected")); + boolean optionEnabled = !opUidl + .getBooleanAttribute(OptionGroupConstants.ATTRIBUTE_OPTION_DISABLED); + boolean enabled = optionEnabled && !isReadonly() && isEnabled(); + op.setEnabled(enabled); + optionsEnabled.put(op, optionEnabled); + + setStyleName(op.getElement(), StyleConstants.DISABLED, + !(optionEnabled && isEnabled())); + + newwidgets.add(op); + } + + if (!newwidgets.equals(existingwidgets)) { + // Rebuild the panel, losing focus + panel.clear(); + for (Widget wid : newwidgets) { + panel.add(wid); + } + } + + wasHtmlContentAllowed = htmlContentAllowed; + wasMultiselect = isMultiselect(); + } + + @Override + protected String[] getSelectedItems() { + return selectedKeys.toArray(new String[selectedKeys.size()]); + } + + @Override + public void onClick(ClickEvent event) { + super.onClick(event); + if (event.getSource() instanceof CheckBox) { + CheckBox source = (CheckBox) event.getSource(); + if (!source.isEnabled()) { + // Click events on the text are received even though the + // checkbox is disabled + return; + } + if (BrowserInfo.get().isWebkit()) { + // Webkit does not focus non-text input elements on click + // (#11854) + source.setFocus(true); + } + + final boolean selected = source.getValue(); + final String key = optionsToKeys.get(source); + if (!isMultiselect()) { + selectedKeys.clear(); + } + if (selected) { + selectedKeys.add(key); + } else { + selectedKeys.remove(key); + } + client.updateVariable(paintableId, "selected", getSelectedItems(), + isImmediate()); + } + } + + @Override + public void setTabIndex(int tabIndex) { + for (Iterator iterator = panel.iterator(); iterator.hasNext();) { + FocusWidget widget = (FocusWidget) iterator.next(); + widget.setTabIndex(tabIndex); + } + } + + @Override + protected void updateEnabledState() { + boolean optionGroupEnabled = isEnabled() && !isReadonly(); + // sets options enabled according to the widget's enabled, + // readonly and each options own enabled + for (Widget w : panel) { + if (w instanceof HasEnabled) { + HasEnabled hasEnabled = (HasEnabled) w; + Boolean isOptionEnabled = optionsEnabled.get(w); + if (isOptionEnabled == null) { + hasEnabled.setEnabled(optionGroupEnabled); + setStyleName(w.getElement(), StyleConstants.DISABLED, + !isEnabled()); + } else { + hasEnabled + .setEnabled(isOptionEnabled && optionGroupEnabled); + setStyleName(w.getElement(), StyleConstants.DISABLED, + !(isOptionEnabled && isEnabled())); + } + } + } + } + + @Override + public void focus() { + Iterator iterator = panel.iterator(); + if (iterator.hasNext()) { + ((Focusable) iterator.next()).setFocus(true); + } + } + + @Override + public void onFocus(FocusEvent arg0) { + if (!blurOccured) { + // no blur occured before this focus event + // panel was blurred => fire the event to the server side if + // requested by server side + if (sendFocusEvents) { + client.updateVariable(paintableId, EventId.FOCUS, "", true); + } + } else { + // blur occured before this focus event + // another control inside the panel (checkbox / radio box) was + // blurred => do not fire the focus and set blurOccured to false, so + // blur will not be fired, too + blurOccured = false; + } + } + + @Override + public void onBlur(BlurEvent arg0) { + blurOccured = true; + if (sendBlurEvents) { + Scheduler.get().scheduleDeferred(new Command() { + @Override + public void execute() { + // check whether blurOccured still is true and then send the + // event out to the server + if (blurOccured) { + client.updateVariable(paintableId, EventId.BLUR, "", + true); + blurOccured = false; + } + } + }); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VOptionGroupBase.java b/client/src/main/java/com/vaadin/client/ui/VOptionGroupBase.java new file mode 100644 index 0000000000..ce75043d89 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VOptionGroupBase.java @@ -0,0 +1,218 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.Set; + +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyPressEvent; +import com.google.gwt.event.dom.client.KeyPressHandler; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HasEnabled; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.Focusable; +import com.vaadin.client.UIDL; + +public abstract class VOptionGroupBase extends Composite implements Field, + ClickHandler, ChangeHandler, KeyPressHandler, Focusable, HasEnabled { + + public static final String CLASSNAME_OPTION = "v-select-option"; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public String paintableId; + + /** For internal use only. May be removed or replaced in the future. */ + public Set selectedKeys; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean immediate; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean multiselect; + + private boolean enabled; + + private boolean readonly; + + /** For internal use only. May be removed or replaced in the future. */ + public int cols = 0; + + /** For internal use only. May be removed or replaced in the future. */ + public int rows = 0; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean nullSelectionAllowed = true; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean nullSelectionItemAvailable = false; + + /** + * Widget holding the different options (e.g. ListBox or Panel for radio + * buttons) (optional, fallbacks to container Panel) + *

+ * For internal use only. May be removed or replaced in the future. + */ + public Widget optionsContainer; + + /** + * Panel containing the component. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public final Panel container; + + /** For internal use only. May be removed or replaced in the future. */ + public VTextField newItemField; + + /** For internal use only. May be removed or replaced in the future. */ + public VNativeButton newItemButton; + + public VOptionGroupBase(String classname) { + container = new FlowPanel(); + initWidget(container); + optionsContainer = container; + container.setStyleName(classname); + immediate = false; + multiselect = false; + } + + /* + * Call this if you wish to specify your own container for the option + * elements (e.g. SELECT) + */ + public VOptionGroupBase(Widget w, String classname) { + this(classname); + optionsContainer = w; + container.add(optionsContainer); + } + + protected boolean isImmediate() { + return immediate; + } + + protected boolean isMultiselect() { + return multiselect; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + public boolean isReadonly() { + return readonly; + } + + protected boolean isNullSelectionAllowed() { + return nullSelectionAllowed; + } + + protected boolean isNullSelectionItemAvailable() { + return nullSelectionItemAvailable; + } + + /** + * For internal use only. May be removed or replaced in the future. + * + * @return "cols" specified in uidl, 0 if not specified + */ + public int getColumns() { + return cols; + } + + /** + * For internal use only. May be removed or replaced in the future. + * + * @return "rows" specified in uidl, 0 if not specified + */ + public int getRows() { + return rows; + } + + public abstract void setTabIndex(int tabIndex); + + @Override + public void onClick(ClickEvent event) { + if (event.getSource() == newItemButton + && !newItemField.getText().equals("")) { + client.updateVariable(paintableId, "newitem", + newItemField.getText(), true); + newItemField.setText(""); + } + } + + @Override + public void onChange(ChangeEvent event) { + if (multiselect) { + client.updateVariable(paintableId, "selected", getSelectedItems(), + immediate); + } else { + client.updateVariable(paintableId, "selected", new String[] { "" + + getSelectedItem() }, immediate); + } + } + + @Override + public void onKeyPress(KeyPressEvent event) { + if (event.getSource() == newItemField + && event.getCharCode() == KeyCodes.KEY_ENTER) { + newItemButton.click(); + } + } + + public void setReadonly(boolean readonly) { + if (this.readonly != readonly) { + this.readonly = readonly; + updateEnabledState(); + } + } + + @Override + public void setEnabled(boolean enabled) { + if (this.enabled != enabled) { + this.enabled = enabled; + updateEnabledState(); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public abstract void buildOptions(UIDL uidl); + + protected abstract String[] getSelectedItems(); + + protected abstract void updateEnabledState(); + + protected String getSelectedItem() { + final String[] sel = getSelectedItems(); + if (sel.length > 0) { + return sel[0]; + } else { + return null; + } + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VOverlay.java b/client/src/main/java/com/vaadin/client/ui/VOverlay.java new file mode 100644 index 0000000000..5dc29f5a42 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VOverlay.java @@ -0,0 +1,177 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.logging.Logger; + +import com.google.gwt.aria.client.Roles; +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.RootPanel; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.Util; +import com.vaadin.client.widgets.Overlay; + +/** + * In Vaadin UI this VOverlay should always be used for all elements that + * temporary float over other components like context menus etc. This is to deal + * stacking order correctly with VWindow objects. + *

+ * To use this correctly, use {@link GWT#create(Class)} to create the + * {@link Overlay} superclass and the default widgetset will replace it with + * this. The widget will not be dependent on this Vaadin specific widget and can + * be used in a pure GWT environment. + * + * @deprecated as this is specifically for Vaadin only, it should not be used + * directly. + */ +@Deprecated +public class VOverlay extends Overlay implements CloseHandler { + + /* + * ApplicationConnection that this overlay belongs to, which is needed to + * create the overlay in the correct container so that the correct styles + * are applied. If not given, owner will be used to figure out, and as a + * last fallback, the overlay is created w/o container, potentially missing + * styles. + */ + protected ApplicationConnection ac; + + public VOverlay() { + super(); + } + + public VOverlay(boolean autoHide) { + super(autoHide); + } + + public VOverlay(boolean autoHide, boolean modal) { + super(autoHide, modal); + } + + /** + * @deprecated See main JavaDoc for VOverlay. Use the other constructors + * without the showShadow parameter. + */ + @Deprecated + public VOverlay(boolean autoHide, boolean modal, boolean showShadow) { + super(autoHide, modal, showShadow); + } + + /* + * A "thread local" of sorts, set temporarily so that VOverlayImpl knows + * which VOverlay is using it, so that it can be attached to the correct + * overlay container. + * + * TODO this is a strange pattern that we should get rid of when possible. + */ + protected static VOverlay current; + + /** + * Get the {@link ApplicationConnection} that this overlay belongs to. If + * it's not set, {@link #getOwner()} is used to figure it out. + * + * @return + */ + protected ApplicationConnection getApplicationConnection() { + if (ac != null) { + return ac; + } else if (getOwner() != null) { + ComponentConnector c = Util.findConnectorFor(getOwner()); + if (c != null) { + ac = c.getConnection(); + } + return ac; + } else { + return null; + } + } + + /** + * Gets the 'overlay container' element. Tries to find the current + * {@link ApplicationConnection} using {@link #getApplicationConnection()}. + * + * @return the overlay container element for the current + * {@link ApplicationConnection} or another element if the current + * {@link ApplicationConnection} cannot be determined. + */ + @Override + public com.google.gwt.user.client.Element getOverlayContainer() { + ApplicationConnection ac = getApplicationConnection(); + if (ac == null) { + // could not figure out which one we belong to, styling will + // probably fail + Logger.getLogger(getClass().getSimpleName()) + .warning( + "Could not determine ApplicationConnection for Overlay. Overlay will be attached directly to the root panel"); + return super.getOverlayContainer(); + } else { + return getOverlayContainer(ac); + } + } + + /** + * Gets the 'overlay container' element pertaining to the given + * {@link ApplicationConnection}. Each overlay should be created in a + * overlay container element, so that the correct theme and styles can be + * applied. + * + * @param ac + * A reference to {@link ApplicationConnection} + * @return The overlay container + */ + public static com.google.gwt.user.client.Element getOverlayContainer( + ApplicationConnection ac) { + String id = ac.getConfiguration().getRootPanelId(); + id = id += "-overlays"; + Element container = DOM.getElementById(id); + if (container == null) { + container = DOM.createDiv(); + container.setId(id); + String styles = ac.getUIConnector().getWidget().getParent() + .getStyleName(); + if (styles != null && !styles.equals("")) { + container.addClassName(styles); + } + container.addClassName(CLASSNAME_CONTAINER); + RootPanel.get().getElement().appendChild(container); + } + return DOM.asOld(container); + } + + /** + * Set the label of the container element, where tooltip, notification and + * dialgs are added to. + * + * @param applicationConnection + * the application connection for which to change the label + * @param overlayContainerLabel + * label for the container + */ + public static void setOverlayContainerLabel( + ApplicationConnection applicationConnection, + String overlayContainerLabel) { + Roles.getAlertRole().setAriaLabelProperty( + VOverlay.getOverlayContainer(applicationConnection), + overlayContainerLabel); + } + +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/ui/VPanel.java b/client/src/main/java/com/vaadin/client/ui/VPanel.java new file mode 100644 index 0000000000..946ff83180 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VPanel.java @@ -0,0 +1,204 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.SimplePanel; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.Focusable; +import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; +import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler; + +public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, + Focusable { + + public static final String CLASSNAME = "v-panel"; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public String id; + + /** For internal use only. May be removed or replaced in the future. */ + public final Element captionNode = DOM.createDiv(); + + private final Element captionText = DOM.createSpan(); + + private Icon icon; + + /** For internal use only. May be removed or replaced in the future. */ + public final Element bottomDecoration = DOM.createDiv(); + + /** For internal use only. May be removed or replaced in the future. */ + public final Element contentNode = DOM.createDiv(); + + private Element errorIndicatorElement; + + /** For internal use only. May be removed or replaced in the future. */ + public ShortcutActionHandler shortcutHandler; + + /** For internal use only. May be removed or replaced in the future. */ + public int scrollTop; + + /** For internal use only. May be removed or replaced in the future. */ + public int scrollLeft; + + private TouchScrollHandler touchScrollHandler; + + public VPanel() { + super(); + DivElement captionWrap = Document.get().createDivElement(); + captionWrap.appendChild(captionNode); + captionNode.appendChild(captionText); + + captionWrap.setClassName(CLASSNAME + "-captionwrap"); + captionNode.setClassName(CLASSNAME + "-caption"); + contentNode.setClassName(CLASSNAME + "-content"); + bottomDecoration.setClassName(CLASSNAME + "-deco"); + + getElement().appendChild(captionWrap); + + /* + * Make contentNode focusable only by using the setFocus() method. This + * behaviour can be changed by invoking setTabIndex() in the serverside + * implementation + */ + contentNode.setTabIndex(-1); + + getElement().appendChild(contentNode); + + getElement().appendChild(bottomDecoration); + setStyleName(CLASSNAME); + DOM.sinkEvents(getElement(), Event.ONKEYDOWN); + DOM.sinkEvents(contentNode, Event.ONSCROLL | Event.TOUCHEVENTS); + + contentNode.getStyle().setProperty("position", "relative"); + getElement().getStyle().setProperty("overflow", "hidden"); + + makeScrollable(); + } + + /** + * Sets the keyboard focus on the Panel + * + * @param focus + * Should the panel have focus or not. + */ + public void setFocus(boolean focus) { + if (focus) { + getContainerElement().focus(); + } else { + getContainerElement().blur(); + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.Focusable#focus() + */ + + @Override + public void focus() { + setFocus(true); + + } + + @Override + protected com.google.gwt.user.client.Element getContainerElement() { + return DOM.asOld(contentNode); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setCaption(String text) { + DOM.setInnerHTML(captionText, text); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setErrorIndicatorVisible(boolean showError) { + if (showError) { + if (errorIndicatorElement == null) { + errorIndicatorElement = DOM.createSpan(); + DOM.setElementProperty(errorIndicatorElement, "className", + "v-errorindicator"); + DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS); + sinkEvents(Event.MOUSEEVENTS); + } + DOM.insertBefore(captionNode, errorIndicatorElement, captionText); + } else if (errorIndicatorElement != null) { + DOM.removeChild(captionNode, errorIndicatorElement); + errorIndicatorElement = null; + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setIconUri(String iconUri, ApplicationConnection client) { + if (icon != null) { + captionNode.removeChild(icon.getElement()); + } + icon = client.getIcon(iconUri); + if (icon != null) { + DOM.insertChild(captionNode, icon.getElement(), 0); + } + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + + final int type = DOM.eventGetType(event); + if (type == Event.ONKEYDOWN && shortcutHandler != null) { + shortcutHandler.handleKeyboardEvent(event); + return; + } + if (type == Event.ONSCROLL) { + int newscrollTop = DOM.getElementPropertyInt(contentNode, + "scrollTop"); + int newscrollLeft = DOM.getElementPropertyInt(contentNode, + "scrollLeft"); + if (client != null + && (newscrollLeft != scrollLeft || newscrollTop != scrollTop)) { + scrollLeft = newscrollLeft; + scrollTop = newscrollTop; + client.updateVariable(id, "scrollTop", scrollTop, false); + client.updateVariable(id, "scrollLeft", scrollLeft, false); + } + } + } + + @Override + public ShortcutActionHandler getShortcutActionHandler() { + return shortcutHandler; + } + + /** + * Ensures the panel is scrollable eg. after style name changes. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public void makeScrollable() { + if (touchScrollHandler == null) { + touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); + } + touchScrollHandler.addElement(contentNode); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VPasswordField.java b/client/src/main/java/com/vaadin/client/ui/VPasswordField.java new file mode 100644 index 0000000000..dcbb60364c --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VPasswordField.java @@ -0,0 +1,33 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.user.client.DOM; + +/** + * This class represents a password field. + * + * @author Vaadin Ltd. + * + */ +public class VPasswordField extends VTextField { + + public VPasswordField() { + super(DOM.createInputPassword()); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VPopupCalendar.java b/client/src/main/java/com/vaadin/client/ui/VPopupCalendar.java new file mode 100644 index 0000000000..47f11b09b1 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VPopupCalendar.java @@ -0,0 +1,737 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.Date; + +import com.google.gwt.aria.client.Id; +import com.google.gwt.aria.client.LiveValue; +import com.google.gwt.aria.client.Roles; +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.DomEvent; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.MouseOutEvent; +import com.google.gwt.event.dom.client.MouseOutHandler; +import com.google.gwt.event.dom.client.MouseOverEvent; +import com.google.gwt.event.dom.client.MouseOverHandler; +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComputedStyle; +import com.vaadin.client.VConsole; +import com.vaadin.client.ui.VCalendarPanel.FocusOutListener; +import com.vaadin.client.ui.VCalendarPanel.SubmitListener; +import com.vaadin.client.ui.aria.AriaHelper; +import com.vaadin.shared.ui.datefield.PopupDateFieldState; +import com.vaadin.shared.ui.datefield.Resolution; + +/** + * Represents a date selection component with a text field and a popup date + * selector. + * + * Note: To change the keyboard assignments used in the popup dialog you + * should extend com.vaadin.client.ui.VCalendarPanel and then pass + * set it by calling the setCalendarPanel(VCalendarPanel panel) + * method. + * + */ +public class VPopupCalendar extends VTextualDate implements Field, + ClickHandler, CloseHandler, SubPartAware { + + /** For internal use only. May be removed or replaced in the future. */ + public final Button calendarToggle = new Button(); + + /** For internal use only. May be removed or replaced in the future. */ + public VCalendarPanel calendar; + + /** For internal use only. May be removed or replaced in the future. */ + public final VOverlay popup; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean parsable = true; + + private boolean open = false; + + /* + * #14857: If calendarToggle button is clicked when calendar popup is + * already open we should prevent calling openCalendarPanel() in onClick, + * since we don't want to reopen it again right after it closes. + */ + private boolean preventOpenPopupCalendar = false; + private boolean cursorOverCalendarToggleButton = false; + private boolean toggleButtonClosesWithGuarantee = false; + + private boolean textFieldEnabled = true; + + private String captionId; + + private Label selectedDate; + + private Element descriptionForAssisitveDevicesElement; + + public VPopupCalendar() { + super(); + + calendarToggle.setText(""); + calendarToggle.addClickHandler(this); + + calendarToggle.addDomHandler(new MouseOverHandler() { + @Override + public void onMouseOver(MouseOverEvent event) { + cursorOverCalendarToggleButton = true; + } + }, MouseOverEvent.getType()); + + calendarToggle.addDomHandler(new MouseOutHandler() { + @Override + public void onMouseOut(MouseOutEvent event) { + cursorOverCalendarToggleButton = false; + } + }, MouseOutEvent.getType()); + + // -2 instead of -1 to avoid FocusWidget.onAttach to reset it + calendarToggle.getElement().setTabIndex(-2); + + Roles.getButtonRole().set(calendarToggle.getElement()); + Roles.getButtonRole().setAriaHiddenState(calendarToggle.getElement(), + true); + + add(calendarToggle); + + // Description of the usage of the widget for assisitve device users + descriptionForAssisitveDevicesElement = DOM.createDiv(); + descriptionForAssisitveDevicesElement + .setInnerText(PopupDateFieldState.DESCRIPTION_FOR_ASSISTIVE_DEVICES); + AriaHelper.ensureHasId(descriptionForAssisitveDevicesElement); + Roles.getTextboxRole().setAriaDescribedbyProperty(text.getElement(), + Id.of(descriptionForAssisitveDevicesElement)); + AriaHelper.setVisibleForAssistiveDevicesOnly( + descriptionForAssisitveDevicesElement, true); + + calendar = GWT.create(VCalendarPanel.class); + calendar.setParentField(this); + calendar.setFocusOutListener(new FocusOutListener() { + @Override + public boolean onFocusOut(DomEvent event) { + event.preventDefault(); + closeCalendarPanel(); + return true; + } + }); + + // FIXME: Problem is, that the element with the provided id does not + // exist yet in html. This is the same problem as with the context menu. + // Apply here the same fix (#11795) + Roles.getTextboxRole().setAriaControlsProperty(text.getElement(), + Id.of(calendar.getElement())); + Roles.getButtonRole().setAriaControlsProperty( + calendarToggle.getElement(), Id.of(calendar.getElement())); + + calendar.setSubmitListener(new SubmitListener() { + @Override + public void onSubmit() { + // Update internal value and send valuechange event if immediate + updateValue(calendar.getDate()); + + // Update text field (a must when not immediate). + buildDate(true); + + closeCalendarPanel(); + } + + @Override + public void onCancel() { + closeCalendarPanel(); + } + }); + + popup = new VOverlay(true, false, true); + popup.setOwner(this); + + FlowPanel wrapper = new FlowPanel(); + selectedDate = new Label(); + selectedDate.setStyleName(getStylePrimaryName() + "-selecteddate"); + AriaHelper.setVisibleForAssistiveDevicesOnly(selectedDate.getElement(), + true); + + Roles.getTextboxRole().setAriaLiveProperty(selectedDate.getElement(), + LiveValue.ASSERTIVE); + Roles.getTextboxRole().setAriaAtomicProperty(selectedDate.getElement(), + true); + wrapper.add(selectedDate); + wrapper.add(calendar); + + popup.setWidget(wrapper); + popup.addCloseHandler(this); + + DOM.setElementProperty(calendar.getElement(), "id", + "PID_VAADIN_POPUPCAL"); + + sinkEvents(Event.ONKEYDOWN); + + updateStyleNames(); + } + + @Override + protected void onAttach() { + super.onAttach(); + DOM.appendChild(RootPanel.get().getElement(), + descriptionForAssisitveDevicesElement); + } + + @Override + protected void onDetach() { + super.onDetach(); + descriptionForAssisitveDevicesElement.removeFromParent(); + closeCalendarPanel(); + } + + @SuppressWarnings("deprecation") + public void updateValue(Date newDate) { + Date currentDate = getCurrentDate(); + if (currentDate == null || newDate.getTime() != currentDate.getTime()) { + setCurrentDate((Date) newDate.clone()); + getClient().updateVariable(getId(), "year", + newDate.getYear() + 1900, false); + if (getCurrentResolution().getCalendarField() > Resolution.YEAR + .getCalendarField()) { + getClient().updateVariable(getId(), "month", + newDate.getMonth() + 1, false); + if (getCurrentResolution().getCalendarField() > Resolution.MONTH + .getCalendarField()) { + getClient().updateVariable(getId(), "day", + newDate.getDate(), false); + if (getCurrentResolution().getCalendarField() > Resolution.DAY + .getCalendarField()) { + getClient().updateVariable(getId(), "hour", + newDate.getHours(), false); + if (getCurrentResolution().getCalendarField() > Resolution.HOUR + .getCalendarField()) { + getClient().updateVariable(getId(), "min", + newDate.getMinutes(), false); + if (getCurrentResolution().getCalendarField() > Resolution.MINUTE + .getCalendarField()) { + getClient().updateVariable(getId(), "sec", + newDate.getSeconds(), false); + } + } + } + } + } + } + } + + /** + * Checks whether the text field is enabled. + * + * @see VPopupCalendar#setTextFieldEnabled(boolean) + * @return The current state of the text field. + */ + public boolean isTextFieldEnabled() { + return textFieldEnabled; + } + + /** + * Sets the state of the text field of this component. By default the text + * field is enabled. Disabling it causes only the button for date selection + * to be active, thus preventing the user from entering invalid dates. See + * {@link http://dev.vaadin.com/ticket/6790}. + * + * @param state + */ + public void setTextFieldEnabled(boolean textFieldEnabled) { + this.textFieldEnabled = textFieldEnabled; + updateTextFieldEnabled(); + } + + protected void updateTextFieldEnabled() { + boolean reallyEnabled = isEnabled() && isTextFieldEnabled(); + // IE has a non input disabled themeing that can not be overridden so we + // must fake the functionality using readonly and unselectable + if (BrowserInfo.get().isIE()) { + if (!reallyEnabled) { + text.getElement().setAttribute("unselectable", "on"); + text.getElement().setAttribute("readonly", ""); + text.setTabIndex(-2); + } else if (reallyEnabled + && text.getElement().hasAttribute("unselectable")) { + text.getElement().removeAttribute("unselectable"); + text.getElement().removeAttribute("readonly"); + text.setTabIndex(0); + } + } else { + text.setEnabled(reallyEnabled); + } + + if (reallyEnabled) { + calendarToggle.setTabIndex(-1); + Roles.getButtonRole().setAriaHiddenState( + calendarToggle.getElement(), true); + } else { + calendarToggle.setTabIndex(0); + Roles.getButtonRole().setAriaHiddenState( + calendarToggle.getElement(), false); + } + + handleAriaAttributes(); + } + + /** + * Set correct tab index for disabled text field in IE as the value set in + * setTextFieldEnabled(...) gets overridden in + * TextualDateConnection.updateFromUIDL(...) + * + * @since 7.3.1 + */ + public void setTextFieldTabIndex() { + if (BrowserInfo.get().isIE() && !textFieldEnabled) { + // index needs to be -2 because FocusWidget updates -1 to 0 onAttach + text.setTabIndex(-2); + } + } + + @Override + public void bindAriaCaption( + com.google.gwt.user.client.Element captionElement) { + if (captionElement == null) { + captionId = null; + } else { + captionId = captionElement.getId(); + } + + if (isTextFieldEnabled()) { + super.bindAriaCaption(captionElement); + } else { + AriaHelper.bindCaption(calendarToggle, captionElement); + } + + handleAriaAttributes(); + } + + private void handleAriaAttributes() { + Widget removeFromWidget; + Widget setForWidget; + + if (isTextFieldEnabled()) { + setForWidget = text; + removeFromWidget = calendarToggle; + } else { + setForWidget = calendarToggle; + removeFromWidget = text; + } + + Roles.getFormRole().removeAriaLabelledbyProperty( + removeFromWidget.getElement()); + if (captionId == null) { + Roles.getFormRole().removeAriaLabelledbyProperty( + setForWidget.getElement()); + } else { + Roles.getFormRole().setAriaLabelledbyProperty( + setForWidget.getElement(), Id.of(captionId)); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String) + */ + @Override + public void setStyleName(String style) { + super.setStyleName(style); + updateStyleNames(); + } + + @Override + public void setStylePrimaryName(String style) { + removeStyleName(getStylePrimaryName() + "-popupcalendar"); + super.setStylePrimaryName(style); + updateStyleNames(); + } + + @Override + protected void updateStyleNames() { + super.updateStyleNames(); + if (getStylePrimaryName() != null && calendarToggle != null) { + addStyleName(getStylePrimaryName() + "-popupcalendar"); + calendarToggle.setStyleName(getStylePrimaryName() + "-button"); + popup.setStyleName(getStylePrimaryName() + "-popup"); + calendar.setStyleName(getStylePrimaryName() + "-calendarpanel"); + } + } + + /** + * Opens the calendar panel popup + */ + public void openCalendarPanel() { + + if (!open && !readonly && isEnabled()) { + open = true; + + if (getCurrentDate() != null) { + calendar.setDate((Date) getCurrentDate().clone()); + } else { + calendar.setDate(new Date()); + } + + // clear previous values + popup.setWidth(""); + popup.setHeight(""); + popup.setPopupPositionAndShow(new PopupPositionCallback()); + } else { + VConsole.error("Cannot reopen popup, it is already open!"); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event + * .dom.client.ClickEvent) + */ + @Override + public void onClick(ClickEvent event) { + if (event.getSource() == calendarToggle && isEnabled()) { + if (!preventOpenPopupCalendar) { + openCalendarPanel(); + } + preventOpenPopupCalendar = false; + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt + * .event.logical.shared.CloseEvent) + */ + @Override + public void onClose(CloseEvent event) { + if (event.getSource() == popup) { + buildDate(); + if (!BrowserInfo.get().isTouchDevice() && textFieldEnabled) { + /* + * Move focus to textbox, unless on touch device (avoids opening + * virtual keyboard) or if textField is disabled. + */ + focus(); + } + + open = false; + + if (cursorOverCalendarToggleButton + && !toggleButtonClosesWithGuarantee) { + preventOpenPopupCalendar = true; + } + + toggleButtonClosesWithGuarantee = false; + } + } + + /** + * Sets focus to Calendar panel. + * + * @param focus + */ + public void setFocus(boolean focus) { + calendar.setFocus(focus); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + updateTextFieldEnabled(); + calendarToggle.setEnabled(enabled); + Roles.getButtonRole().setAriaDisabledState(calendarToggle.getElement(), + !enabled); + } + + /** + * Sets the content of a special field for assistive devices, so that they + * can recognize the change and inform the user (reading out in case of + * screen reader) + * + * @param selectedDate + * Date that is currently selected + */ + public void setFocusedDate(Date selectedDate) { + this.selectedDate.setText(DateTimeFormat.getFormat("dd, MMMM, yyyy") + .format(selectedDate)); + } + + /** + * For internal use only. May be removed or replaced in the future. + * + * @see com.vaadin.client.ui.VTextualDate#buildDate() + */ + @Override + public void buildDate() { + // Save previous value + String previousValue = getText(); + super.buildDate(); + + // Restore previous value if the input could not be parsed + if (!parsable) { + setText(previousValue); + } + updateTextFieldEnabled(); + } + + /** + * Update the text field contents from the date. See {@link #buildDate()}. + * + * @param forceValid + * true to force the text field to be updated, false to only + * update if the parsable flag is true. + */ + protected void buildDate(boolean forceValid) { + if (forceValid) { + parsable = true; + } + buildDate(); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ui.VDateField#onBrowserEvent(com.google + * .gwt.user.client.Event) + */ + @Override + public void onBrowserEvent(com.google.gwt.user.client.Event event) { + super.onBrowserEvent(event); + if (DOM.eventGetType(event) == Event.ONKEYDOWN + && event.getKeyCode() == getOpenCalenderPanelKey()) { + openCalendarPanel(); + event.preventDefault(); + } + } + + /** + * Get the key code that opens the calendar panel. By default it is the down + * key but you can override this to be whatever you like + * + * @return + */ + protected int getOpenCalenderPanelKey() { + return KeyCodes.KEY_DOWN; + } + + /** + * Closes the open popup panel + */ + public void closeCalendarPanel() { + if (open) { + toggleButtonClosesWithGuarantee = true; + popup.hide(true); + } + } + + private final String CALENDAR_TOGGLE_ID = "popupButton"; + + @Override + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + if (subPart.equals(CALENDAR_TOGGLE_ID)) { + return calendarToggle.getElement(); + } + + return super.getSubPartElement(subPart); + } + + @Override + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + if (calendarToggle.getElement().isOrHasChild(subElement)) { + return CALENDAR_TOGGLE_ID; + } + + return super.getSubPartName(subElement); + } + + /** + * Set a description that explains the usage of the Widget for users of + * assistive devices. + * + * @param descriptionForAssistiveDevices + * String with the description + */ + public void setDescriptionForAssistiveDevices( + String descriptionForAssistiveDevices) { + descriptionForAssisitveDevicesElement + .setInnerText(descriptionForAssistiveDevices); + } + + /** + * Get the description that explains the usage of the Widget for users of + * assistive devices. + * + * @return String with the description + */ + public String getDescriptionForAssistiveDevices() { + return descriptionForAssisitveDevicesElement.getInnerText(); + } + + /** + * Sets the start range for this component. The start range is inclusive, + * and it depends on the current resolution, what is considered inside the + * range. + * + * @param startDate + * - the allowed range's start date + */ + public void setRangeStart(Date rangeStart) { + calendar.setRangeStart(rangeStart); + } + + /** + * Sets the end range for this component. The end range is inclusive, and it + * depends on the current resolution, what is considered inside the range. + * + * @param endDate + * - the allowed range's end date + */ + public void setRangeEnd(Date rangeEnd) { + calendar.setRangeEnd(rangeEnd); + } + + private class PopupPositionCallback implements PositionCallback { + + @Override + public void setPosition(int offsetWidth, int offsetHeight) { + final int width = offsetWidth; + final int height = offsetHeight; + final int browserWindowWidth = Window.getClientWidth() + + Window.getScrollLeft(); + final int windowHeight = Window.getClientHeight() + + Window.getScrollTop(); + int left = calendarToggle.getAbsoluteLeft(); + + // Add a little extra space to the right to avoid + // problems with IE7 scrollbars and to make it look + // nicer. + int extraSpace = 30; + + boolean overflow = left + width + extraSpace > browserWindowWidth; + if (overflow) { + // Part of the popup is outside the browser window + // (to the right) + left = browserWindowWidth - width - extraSpace; + } + + int top = calendarToggle.getAbsoluteTop(); + int extraHeight = 2; + boolean verticallyRepositioned = false; + ComputedStyle style = new ComputedStyle(popup.getElement()); + int[] margins = style.getMargin(); + int desiredPopupBottom = top + height + + calendarToggle.getOffsetHeight() + margins[0] + + margins[2]; + + if (desiredPopupBottom > windowHeight) { + int updatedLeft = left; + left = getLeftPosition(left, width, style, overflow); + + // if position has not been changed then it means there is no + // space to make popup fully visible + if (updatedLeft == left) { + // let's try to show popup on the top of the field + int updatedTop = top - extraHeight - height - margins[0] + - margins[2]; + verticallyRepositioned = updatedTop >= 0; + if (verticallyRepositioned) { + top = updatedTop; + } + } + // Part of the popup is outside the browser window + // (below) + if (!verticallyRepositioned) { + verticallyRepositioned = true; + top = windowHeight - height - extraSpace + extraHeight; + } + } + if (verticallyRepositioned) { + popup.setPopupPosition(left, top); + } else { + popup.setPopupPosition(left, + top + calendarToggle.getOffsetHeight() + extraHeight); + } + doSetFocus(); + } + + private int getLeftPosition(int left, int width, ComputedStyle style, + boolean overflow) { + if (positionRightSide()) { + // Show to the right of the popup button unless we + // are in the lower right corner of the screen + if (overflow) { + return left; + } else { + return left + calendarToggle.getOffsetWidth(); + } + } else { + int[] margins = style.getMargin(); + int desiredLeftPosition = calendarToggle.getAbsoluteLeft() + - width - margins[1] - margins[3]; + if (desiredLeftPosition >= 0) { + return desiredLeftPosition; + } else { + return left; + } + } + } + + private boolean positionRightSide() { + int buttonRightSide = calendarToggle.getAbsoluteLeft() + + calendarToggle.getOffsetWidth(); + int textRightSide = text.getAbsoluteLeft() + text.getOffsetWidth(); + return buttonRightSide >= textRightSide; + } + + private void doSetFocus() { + /* + * We have to wait a while before focusing since the popup needs to + * be opened before we can focus + */ + Timer focusTimer = new Timer() { + @Override + public void run() { + setFocus(true); + } + }; + + focusTimer.schedule(100); + } + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VPopupView.java b/client/src/main/java/com/vaadin/client/ui/VPopupView.java new file mode 100644 index 0000000000..0f4e68acab --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VPopupView.java @@ -0,0 +1,466 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Focusable; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.HasEnabled; +import com.google.gwt.user.client.ui.HasWidgets; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.DeferredWorker; +import com.vaadin.client.Util; +import com.vaadin.client.VCaptionWrapper; +import com.vaadin.client.VConsole; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; +import com.vaadin.client.ui.popupview.VisibilityChangeEvent; +import com.vaadin.client.ui.popupview.VisibilityChangeHandler; + +public class VPopupView extends HTML implements HasEnabled, Iterable, + DeferredWorker { + + public static final String CLASSNAME = "v-popupview"; + + /** + * For server-client communication. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public String uidlId; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** + * Helps to communicate popup visibility to the server. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public boolean hostPopupVisible; + + /** For internal use only. May be removed or replaced in the future. */ + public final CustomPopup popup; + private final Label loading = new Label(); + + private boolean popupShowInProgress; + private boolean enabled = true; + + /** + * loading constructor + */ + public VPopupView() { + super(); + popup = new CustomPopup(); + + setStyleName(CLASSNAME); + popup.setStyleName(CLASSNAME + "-popup"); + loading.setStyleName(CLASSNAME + "-loading"); + + setHTML(""); + popup.setWidget(loading); + + // When we click to open the popup... + addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + if (isEnabled()) { + preparePopup(popup); + showPopup(popup); + center(); + fireEvent(new VisibilityChangeEvent(true)); + } + } + }); + + // ..and when we close it + popup.addCloseHandler(new CloseHandler() { + @Override + public void onClose(CloseEvent event) { + fireEvent(new VisibilityChangeEvent(false)); + } + }); + + // TODO: Enable animations once GWT fix has been merged + popup.setAnimationEnabled(false); + + popup.setAutoHideOnHistoryEventsEnabled(false); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void preparePopup(final CustomPopup popup) { + popup.setVisible(true); + popup.setWidget(loading); + popup.show(); + } + + /** + * Determines the correct position for a popup and displays the popup at + * that position. + * + * By default, the popup is shown centered relative to its host component, + * ensuring it is visible on the screen if possible. + * + * Can be overridden to customize the popup position. + * + * @param popup + */ + public void showPopup(final CustomPopup popup) { + popup.setPopupPosition(0, 0); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void center() { + int windowTop = RootPanel.get().getAbsoluteTop(); + int windowLeft = RootPanel.get().getAbsoluteLeft(); + int windowRight = windowLeft + RootPanel.get().getOffsetWidth(); + int windowBottom = windowTop + RootPanel.get().getOffsetHeight(); + + int offsetWidth = popup.getOffsetWidth(); + int offsetHeight = popup.getOffsetHeight(); + + int hostHorizontalCenter = VPopupView.this.getAbsoluteLeft() + + VPopupView.this.getOffsetWidth() / 2; + int hostVerticalCenter = VPopupView.this.getAbsoluteTop() + + VPopupView.this.getOffsetHeight() / 2; + + int left = hostHorizontalCenter - offsetWidth / 2; + int top = hostVerticalCenter - offsetHeight / 2; + + // Don't show the popup outside the screen. + if ((left + offsetWidth) > windowRight) { + left -= (left + offsetWidth) - windowRight; + } + + if ((top + offsetHeight) > windowBottom) { + top -= (top + offsetHeight) - windowBottom; + } + + if (left < 0) { + left = 0; + } + + if (top < 0) { + top = 0; + } + + popup.setPopupPosition(left, top); + } + + /** + * Make sure that we remove the popup when the main widget is removed. + * + * @see com.google.gwt.user.client.ui.Widget#onUnload() + */ + @Override + protected void onDetach() { + popup.hide(); + super.onDetach(); + } + + private static native void nativeBlur(Element e) + /*-{ + if(e && e.blur) { + e.blur(); + } + }-*/; + + /** + * Returns true if the popup is enabled, false if not. + * + * @since 7.3.4 + */ + @Override + public boolean isEnabled() { + return enabled; + } + + /** + * Sets whether this popup is enabled. + * + * @param enabled + * true to enable the popup, false to + * disable it + * @since 7.3.4 + */ + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * This class is only public to enable overriding showPopup, and is + * currently not intended to be extended or otherwise used directly. Its API + * (other than it being a VOverlay) is to be considered private and + * potentially subject to change. + */ + public class CustomPopup extends VOverlay implements + StateChangeEvent.StateChangeHandler { + + private ComponentConnector popupComponentConnector = null; + + /** For internal use only. May be removed or replaced in the future. */ + public Widget popupComponentWidget = null; + + /** For internal use only. May be removed or replaced in the future. */ + public VCaptionWrapper captionWrapper = null; + + private boolean hasHadMouseOver = false; + private boolean hideOnMouseOut = true; + private final Set activeChildren = new HashSet(); + + private ShortcutActionHandler shortcutActionHandler; + + public CustomPopup() { + super(true, false, true); // autoHide, not modal, dropshadow + setOwner(VPopupView.this); + // Delegate popup keyboard events to the relevant handler. The + // events do not propagate automatically because the popup is + // directly attached to the RootPanel. + addDomHandler(new KeyDownHandler() { + @Override + public void onKeyDown(KeyDownEvent event) { + if (shortcutActionHandler != null) { + shortcutActionHandler.handleKeyboardEvent(Event + .as(event.getNativeEvent())); + } + } + }, KeyDownEvent.getType()); + } + + // For some reason ONMOUSEOUT events are not always received, so we have + // to use ONMOUSEMOVE that doesn't target the popup + @Override + public boolean onEventPreview(Event event) { + Element target = DOM.eventGetTarget(event); + boolean eventTargetsPopup = DOM.isOrHasChild(getElement(), target); + int type = DOM.eventGetType(event); + + // Catch children that use keyboard, so we can unfocus them when + // hiding + if (eventTargetsPopup && type == Event.ONKEYPRESS) { + activeChildren.add(target); + } + + if (eventTargetsPopup && type == Event.ONMOUSEMOVE) { + hasHadMouseOver = true; + } + + if (!eventTargetsPopup && type == Event.ONMOUSEMOVE) { + if (hasHadMouseOver && hideOnMouseOut) { + hide(); + return true; + } + } + + // Was the TAB key released outside of our popup? + if (!eventTargetsPopup && type == Event.ONKEYUP + && event.getKeyCode() == KeyCodes.KEY_TAB) { + // Should we hide on focus out (mouse out)? + if (hideOnMouseOut) { + hide(); + return true; + } + } + + return super.onEventPreview(event); + } + + @Override + public void hide(boolean autoClosed) { + VConsole.log("Hiding popupview"); + syncChildren(); + clearPopupComponentConnector(); + hasHadMouseOver = false; + shortcutActionHandler = null; + super.hide(autoClosed); + } + + @Override + public void show() { + popupShowInProgress = true; + // Find the shortcut action handler that should handle keyboard + // events from the popup. The events do not propagate automatically + // because the popup is directly attached to the RootPanel. + + super.show(); + + /* + * Shortcut actions could be set (and currently in 7.2 they ARE SET + * via old style "updateFromUIDL" method, see f.e. UIConnector) + * AFTER method show() has been invoked (which is called as a + * reaction on change in component hierarchy). As a result there + * could be no shortcutActionHandler set yet. So let's postpone + * search of shortcutActionHandler. + */ + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + try { + if (shortcutActionHandler == null) { + shortcutActionHandler = findShortcutActionHandler(); + } + } finally { + popupShowInProgress = false; + } + } + }); + } + + /** + * Try to sync all known active child widgets to server + */ + public void syncChildren() { + // Notify children with focus + if ((popupComponentWidget instanceof Focusable)) { + ((Focusable) popupComponentWidget).setFocus(false); + } else { + + checkForRTE(popupComponentWidget); + } + + // Notify children that have used the keyboard + for (Element e : activeChildren) { + try { + nativeBlur(e); + } catch (Exception ignored) { + } + } + activeChildren.clear(); + } + + private void checkForRTE(Widget popupComponentWidget2) { + if (popupComponentWidget2 instanceof VRichTextArea) { + ComponentConnector rtaConnector = Util + .findConnectorFor(popupComponentWidget2); + if (rtaConnector != null) { + rtaConnector.flush(); + } + } else if (popupComponentWidget2 instanceof HasWidgets) { + HasWidgets hw = (HasWidgets) popupComponentWidget2; + Iterator iterator = hw.iterator(); + while (iterator.hasNext()) { + checkForRTE(iterator.next()); + } + } + } + + private void clearPopupComponentConnector() { + if (popupComponentConnector != null) { + popupComponentConnector.removeStateChangeHandler(this); + } + popupComponentConnector = null; + popupComponentWidget = null; + captionWrapper = null; + } + + @Override + public boolean remove(Widget w) { + clearPopupComponentConnector(); + return super.remove(w); + } + + public void setPopupConnector(ComponentConnector newPopupComponent) { + + if (newPopupComponent != popupComponentConnector) { + if (popupComponentConnector != null) { + popupComponentConnector.removeStateChangeHandler(this); + } + Widget newWidget = newPopupComponent.getWidget(); + setWidget(newWidget); + popupComponentWidget = newWidget; + popupComponentConnector = newPopupComponent; + popupComponentConnector.addStateChangeHandler("height", this); + popupComponentConnector.addStateChangeHandler("width", this); + } + + } + + public void setHideOnMouseOut(boolean hideOnMouseOut) { + this.hideOnMouseOut = hideOnMouseOut; + } + + @Override + public com.google.gwt.user.client.Element getContainerElement() { + return super.getContainerElement(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + positionOrSizeUpdated(); + } + + private ShortcutActionHandler findShortcutActionHandler() { + Widget widget = VPopupView.this; + ShortcutActionHandler handler = null; + while (handler == null && widget != null) { + if (widget instanceof ShortcutActionHandlerOwner) { + handler = ((ShortcutActionHandlerOwner) widget) + .getShortcutActionHandler(); + } + widget = widget.getParent(); + } + return handler; + } + }// class CustomPopup + + public HandlerRegistration addVisibilityChangeHandler( + final VisibilityChangeHandler visibilityChangeHandler) { + return addHandler(visibilityChangeHandler, + VisibilityChangeEvent.getType()); + } + + @Override + public Iterator iterator() { + return Collections.singleton((Widget) popup).iterator(); + } + + /** + * Checks whether there are operations pending for this widget that must be + * executed before reaching a steady state. + * + * @returns true iff there are operations pending which must be + * executed before reaching a steady state + * @since 7.3.4 + */ + @Override + public boolean isWorkPending() { + return popupShowInProgress; + } + +}// class VPopupView diff --git a/client/src/main/java/com/vaadin/client/ui/VProgressBar.java b/client/src/main/java/com/vaadin/client/ui/VProgressBar.java new file mode 100644 index 0000000000..348791728f --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VProgressBar.java @@ -0,0 +1,101 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.HasEnabled; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.StyleConstants; + +/** + * Widget for showing the current progress of a long running task. + *

+ * The default mode is to show the current progress internally represented by a + * floating point value between 0 and 1 (inclusive). The progress bar can also + * be in an indeterminate mode showing an animation indicating that the task is + * running but without providing any information about the current progress. + * + * @since 7.1 + * @author Vaadin Ltd + */ +public class VProgressBar extends Widget implements HasEnabled { + + public static final String PRIMARY_STYLE_NAME = "v-progressbar"; + + Element wrapper = DOM.createDiv(); + Element indicator = DOM.createDiv(); + + private boolean indeterminate = false; + private float state = 0.0f; + private boolean enabled; + + public VProgressBar() { + setElement(DOM.createDiv()); + getElement().appendChild(wrapper); + wrapper.appendChild(indicator); + + setStylePrimaryName(PRIMARY_STYLE_NAME); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.user.client.ui.UIObject#setStylePrimaryName(java.lang. + * String) + */ + @Override + public void setStylePrimaryName(String style) { + super.setStylePrimaryName(style); + indicator.setClassName(getStylePrimaryName() + "-indicator"); + wrapper.setClassName(getStylePrimaryName() + "-wrapper"); + + } + + public void setIndeterminate(boolean indeterminate) { + this.indeterminate = indeterminate; + setStyleName(getStylePrimaryName() + "-indeterminate", indeterminate); + } + + public void setState(float state) { + final int size = Math.round(100 * state); + indicator.getStyle().setWidth(size, Unit.PCT); + } + + public boolean isIndeterminate() { + return indeterminate; + } + + public float getState() { + return state; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void setEnabled(boolean enabled) { + if (this.enabled != enabled) { + this.enabled = enabled; + setStyleName(StyleConstants.DISABLED, !enabled); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VProgressIndicator.java b/client/src/main/java/com/vaadin/client/ui/VProgressIndicator.java new file mode 100644 index 0000000000..f93fa37af6 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VProgressIndicator.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.vaadin.shared.ui.progressindicator.ProgressIndicatorState; + +/** + * + * @author Vaadin Ltd + * + * @deprecated as of 7.1, renamed to VProgressBar + */ +@Deprecated +public class VProgressIndicator extends VProgressBar { + + public VProgressIndicator() { + super(); + setStylePrimaryName(ProgressIndicatorState.PRIMARY_STYLE_NAME); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VRichTextArea.java b/client/src/main/java/com/vaadin/client/ui/VRichTextArea.java new file mode 100644 index 0000000000..cb4482f7cb --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VRichTextArea.java @@ -0,0 +1,363 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyPressEvent; +import com.google.gwt.event.dom.client.KeyPressHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Focusable; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.RichTextArea; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ConnectorMap; +import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; +import com.vaadin.client.ui.richtextarea.VRichTextToolbar; + +/** + * This class implements a basic client side rich text editor component. + * + * @author Vaadin Ltd. + * + */ +public class VRichTextArea extends Composite implements Field, KeyPressHandler, + KeyDownHandler, Focusable { + + /** + * The input node CSS classname. + */ + public static final String CLASSNAME = "v-richtextarea"; + + /** For internal use only. May be removed or replaced in the future. */ + public String id; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean immediate = false; + + /** For internal use only. May be removed or replaced in the future. */ + public RichTextArea rta; + + /** For internal use only. May be removed or replaced in the future. */ + public VRichTextToolbar formatter; + + /** For internal use only. May be removed or replaced in the future. */ + public HTML html = new HTML(); + + private final FlowPanel fp = new FlowPanel(); + + private boolean enabled = true; + + /** For internal use only. May be removed or replaced in the future. */ + public int maxLength = -1; + + private int toolbarNaturalWidth = 500; + + /** For internal use only. May be removed or replaced in the future. */ + public HandlerRegistration keyPressHandler; + + private ShortcutActionHandlerOwner hasShortcutActionHandler; + + private boolean readOnly = false; + + private final Map blurHandlers = new HashMap(); + + public VRichTextArea() { + createRTAComponents(); + fp.add(formatter); + fp.add(rta); + + initWidget(fp); + setStyleName(CLASSNAME); + + TouchScrollDelegate.enableTouchScrolling(html, html.getElement()); + } + + private void createRTAComponents() { + rta = new RichTextArea(); + rta.setWidth("100%"); + rta.addKeyDownHandler(this); + formatter = new VRichTextToolbar(rta); + + // Add blur handlers + for (Entry handler : blurHandlers + .entrySet()) { + + // Remove old registration + handler.getValue().removeHandler(); + + // Add blur handlers + addBlurHandler(handler.getKey()); + } + } + + public void setEnabled(boolean enabled) { + if (this.enabled != enabled) { + // rta.setEnabled(enabled); + swapEditableArea(); + this.enabled = enabled; + } + } + + /** + * Swaps html to rta and visa versa. + */ + private void swapEditableArea() { + String value = getValue(); + if (html.isAttached()) { + fp.remove(html); + if (BrowserInfo.get().isWebkit()) { + fp.remove(formatter); + createRTAComponents(); // recreate new RTA to bypass #5379 + fp.add(formatter); + } + fp.add(rta); + } else { + fp.remove(rta); + fp.add(html); + } + setValue(value); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void selectAll() { + /* + * There is a timing issue if trying to select all immediately on first + * render. Simple deferred command is not enough. Using Timer with + * moderated timeout. If this appears to fail on many (most likely slow) + * environments, consider increasing the timeout. + * + * FF seems to require the most time to stabilize its RTA. On Vaadin + * tiergarden test machines, 200ms was not enough always (about 50% + * success rate) - 300 ms was 100% successful. This however was not + * enough on a sluggish old non-virtualized XP test machine. A bullet + * proof solution would be nice, GWT 2.1 might however solve these. At + * least setFocus has a workaround for this kind of issue. + */ + new Timer() { + @Override + public void run() { + rta.getFormatter().selectAll(); + } + }.schedule(320); + } + + public void setReadOnly(boolean b) { + if (isReadOnly() != b) { + swapEditableArea(); + readOnly = b; + } + // reset visibility in case enabled state changed and the formatter was + // recreated + formatter.setVisible(!readOnly); + } + + private boolean isReadOnly() { + return readOnly; + } + + @Override + public void setHeight(String height) { + super.setHeight(height); + if (height == null || height.equals("")) { + rta.setHeight(""); + } + } + + @Override + public void setWidth(String width) { + if (width.equals("")) { + /* + * IE cannot calculate the width of the 100% iframe correctly if + * there is no width specified for the parent. In this case we would + * use the toolbar but IE cannot calculate the width of that one + * correctly either in all cases. So we end up using a default width + * for a RichTextArea with no width definition in all browsers (for + * compatibility). + */ + + super.setWidth(toolbarNaturalWidth + "px"); + } else { + super.setWidth(width); + } + } + + @Override + public void onKeyPress(KeyPressEvent event) { + if (maxLength >= 0) { + Scheduler.get().scheduleDeferred(new Command() { + @Override + public void execute() { + if (rta.getHTML().length() > maxLength) { + rta.setHTML(rta.getHTML().substring(0, maxLength)); + } + } + }); + } + } + + @Override + public void onKeyDown(KeyDownEvent event) { + // delegate to closest shortcut action handler + // throw event from the iframe forward to the shortcuthandler + ShortcutActionHandler shortcutHandler = getShortcutHandlerOwner() + .getShortcutActionHandler(); + if (shortcutHandler != null) { + shortcutHandler + .handleKeyboardEvent(com.google.gwt.user.client.Event + .as(event.getNativeEvent()), + ConnectorMap.get(client).getConnector(this)); + } + } + + private ShortcutActionHandlerOwner getShortcutHandlerOwner() { + if (hasShortcutActionHandler == null) { + Widget parent = getParent(); + while (parent != null) { + if (parent instanceof ShortcutActionHandlerOwner) { + break; + } + parent = parent.getParent(); + } + hasShortcutActionHandler = (ShortcutActionHandlerOwner) parent; + } + return hasShortcutActionHandler; + } + + @Override + public int getTabIndex() { + return rta.getTabIndex(); + } + + @Override + public void setAccessKey(char key) { + rta.setAccessKey(key); + } + + @Override + public void setFocus(boolean focused) { + /* + * Similar issue as with selectAll. Focusing must happen before possible + * selectall, so keep the timeout here lower. + */ + new Timer() { + + @Override + public void run() { + rta.setFocus(true); + } + }.schedule(300); + } + + @Override + public void setTabIndex(int index) { + rta.setTabIndex(index); + } + + /** + * Set the value of the text area + * + * @param value + * The text value. Can be html. + */ + public void setValue(String value) { + if (rta.isAttached()) { + rta.setHTML(value); + } else { + html.setHTML(value); + } + } + + /** + * Get the value the text area + */ + public String getValue() { + if (rta.isAttached()) { + return rta.getHTML(); + } else { + return html.getHTML(); + } + } + + /** + * Browsers differ in what they return as the content of a visually empty + * rich text area. This method is used to normalize these to an empty + * string. See #8004. + * + * @return cleaned html string + */ + public String getSanitizedValue() { + BrowserInfo browser = BrowserInfo.get(); + String result = getValue(); + if (browser.isFirefox()) { + if ("
".equals(result)) { + result = ""; + } + } else if (browser.isWebkit() || browser.isEdge()) { + if ("
".equals(result) || "


".equals(result)) { + result = ""; + } + } else if (browser.isIE()) { + if ("

 

".equals(result)) { + result = ""; + } + } else if (browser.isOpera()) { + if ("
".equals(result) || "


".equals(result)) { + result = ""; + } + } + return result; + } + + /** + * Adds a blur handler to the component. + * + * @param blurHandler + * the blur handler to add + */ + public void addBlurHandler(BlurHandler blurHandler) { + blurHandlers.put(blurHandler, rta.addBlurHandler(blurHandler)); + } + + /** + * Removes a blur handler. + * + * @param blurHandler + * the handler to remove + */ + public void removeBlurHandler(BlurHandler blurHandler) { + HandlerRegistration registration = blurHandlers.remove(blurHandler); + if (registration != null) { + registration.removeHandler(); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VScrollTable.java b/client/src/main/java/com/vaadin/client/ui/VScrollTable.java new file mode 100644 index 0000000000..5e5ae8a259 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VScrollTable.java @@ -0,0 +1,8370 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.NodeList; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.TextAlign; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.dom.client.TableCellElement; +import com.google.gwt.dom.client.TableRowElement; +import com.google.gwt.dom.client.TableSectionElement; +import com.google.gwt.dom.client.Touch; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyPressEvent; +import com.google.gwt.event.dom.client.KeyPressHandler; +import com.google.gwt.event.dom.client.KeyUpEvent; +import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.event.dom.client.ScrollEvent; +import com.google.gwt.event.dom.client.ScrollHandler; +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.regexp.shared.MatchResult; +import com.google.gwt.regexp.shared.RegExp; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Event.NativePreviewHandler; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HasWidgets; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.UIObject; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorMap; +import com.vaadin.client.DeferredWorker; +import com.vaadin.client.Focusable; +import com.vaadin.client.HasChildMeasurementHintConnector.ChildMeasurementHint; +import com.vaadin.client.MouseEventDetailsBuilder; +import com.vaadin.client.StyleConstants; +import com.vaadin.client.TooltipInfo; +import com.vaadin.client.UIDL; +import com.vaadin.client.Util; +import com.vaadin.client.VConsole; +import com.vaadin.client.VTooltip; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; +import com.vaadin.client.ui.dd.DDUtil; +import com.vaadin.client.ui.dd.VAbstractDropHandler; +import com.vaadin.client.ui.dd.VAcceptCallback; +import com.vaadin.client.ui.dd.VDragAndDropManager; +import com.vaadin.client.ui.dd.VDragEvent; +import com.vaadin.client.ui.dd.VHasDropHandler; +import com.vaadin.client.ui.dd.VTransferable; +import com.vaadin.shared.AbstractComponentState; +import com.vaadin.shared.MouseEventDetails; +import com.vaadin.shared.ui.dd.VerticalDropLocation; +import com.vaadin.shared.ui.table.CollapseMenuContent; +import com.vaadin.shared.ui.table.TableConstants; + +/** + * VScrollTable + * + * VScrollTable is a FlowPanel having two widgets in it: * TableHead component * + * ScrollPanel + * + * TableHead contains table's header and widgets + logic for resizing, + * reordering and hiding columns. + * + * ScrollPanel contains VScrollTableBody object which handles content. To save + * some bandwidth and to improve clients responsiveness with loads of data, in + * VScrollTableBody all rows are not necessary rendered. There are "spacers" in + * VScrollTableBody to use the exact same space as non-rendered rows would use. + * This way we can use seamlessly traditional scrollbars and scrolling to fetch + * more rows instead of "paging". + * + * In VScrollTable we listen to scroll events. On horizontal scrolling we also + * update TableHeads scroll position which has its scrollbars hidden. On + * vertical scroll events we will check if we are reaching the end of area where + * we have rows rendered and + * + * TODO implement unregistering for child components in Cells + */ +public class VScrollTable extends FlowPanel implements HasWidgets, + ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable, + ActionOwner, SubPartAware, DeferredWorker { + + /** + * Simple interface for parts of the table capable of owning a context menu. + * + * @since 7.2 + * @author Vaadin Ltd + */ + private interface ContextMenuOwner { + public void showContextMenu(Event event); + } + + /** + * Handles showing context menu on "long press" from a touch screen. + * + * @since 7.2 + * @author Vaadin Ltd + */ + private class TouchContextProvider { + private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500; + private Timer contextTouchTimeout; + + private Event touchStart; + private int touchStartY; + private int touchStartX; + + private ContextMenuOwner target; + + /** + * Initializes a handler for a certain context menu owner. + * + * @param target + * the owner of the context menu + */ + public TouchContextProvider(ContextMenuOwner target) { + this.target = target; + } + + /** + * Cancels the current context touch timeout. + */ + public void cancel() { + if (contextTouchTimeout != null) { + contextTouchTimeout.cancel(); + contextTouchTimeout = null; + } + touchStart = null; + } + + /** + * A function to handle touch context events in a table. + * + * @param event + * browser event to handle + */ + public void handleTouchEvent(final Event event) { + int type = event.getTypeInt(); + + switch (type) { + case Event.ONCONTEXTMENU: + target.showContextMenu(event); + break; + case Event.ONTOUCHSTART: + // save position to fields, touches in events are same + // instance during the operation. + touchStart = event; + + Touch touch = event.getChangedTouches().get(0); + touchStartX = touch.getClientX(); + touchStartY = touch.getClientY(); + + if (contextTouchTimeout == null) { + contextTouchTimeout = new Timer() { + + @Override + public void run() { + if (touchStart != null) { + // Open the context menu if finger + // is held in place long enough. + target.showContextMenu(touchStart); + event.preventDefault(); + touchStart = null; + } + } + }; + } + contextTouchTimeout.schedule(TOUCH_CONTEXT_MENU_TIMEOUT); + break; + case Event.ONTOUCHCANCEL: + case Event.ONTOUCHEND: + cancel(); + break; + case Event.ONTOUCHMOVE: + if (isSignificantMove(event)) { + // Moved finger before the context menu timer + // expired, so let the browser handle the event. + cancel(); + } + } + } + + /** + * Calculates how many pixels away the user's finger has traveled. This + * reduces the chance of small non-intentional movements from canceling + * the long press detection. + * + * @param event + * the Event for which to check the move distance + * @return true if this is considered an intentional move by the user + */ + protected boolean isSignificantMove(Event event) { + if (touchStart == null) { + // no touch start + return false; + } + + // Calculate the distance between touch start and the current touch + // position + Touch touch = event.getChangedTouches().get(0); + int deltaX = touch.getClientX() - touchStartX; + int deltaY = touch.getClientY() - touchStartY; + int delta = deltaX * deltaX + deltaY * deltaY; + + // Compare to the square of the significant move threshold to remove + // the need for a square root + if (delta > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD + * TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) { + return true; + } + return false; + } + } + + public static final String STYLENAME = "v-table"; + + public enum SelectMode { + NONE(0), SINGLE(1), MULTI(2); + private int id; + + private SelectMode(int id) { + this.id = id; + } + + public int getId() { + return id; + } + } + + private static final String ROW_HEADER_COLUMN_KEY = "0"; + + private static final double CACHE_RATE_DEFAULT = 2; + + /** + * The default multi select mode where simple left clicks only selects one + * item, CTRL+left click selects multiple items and SHIFT-left click selects + * a range of items. + */ + private static final int MULTISELECT_MODE_DEFAULT = 0; + + /** + * The simple multiselect mode is what the table used to have before + * ctrl/shift selections were added. That is that when this is set clicking + * on an item selects/deselects the item and no ctrl/shift selections are + * available. + */ + private static final int MULTISELECT_MODE_SIMPLE = 1; + + /** + * multiple of pagelength which component will cache when requesting more + * rows + */ + private double cache_rate = CACHE_RATE_DEFAULT; + /** + * fraction of pageLength which can be scrolled without making new request + */ + private double cache_react_rate = 0.75 * cache_rate; + + public static final char ALIGN_CENTER = 'c'; + public static final char ALIGN_LEFT = 'b'; + public static final char ALIGN_RIGHT = 'e'; + private static final int CHARCODE_SPACE = 32; + private int firstRowInViewPort = 0; + private int pageLength = 15; + private int lastRequestedFirstvisible = 0; // to detect "serverside scroll" + private int firstvisibleOnLastPage = -1; // To detect if the first visible + // is on the last page + + /** For internal use only. May be removed or replaced in the future. */ + public boolean showRowHeaders = false; + + private String[] columnOrder; + + protected ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public String paintableId; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean immediate; + + private boolean updatedReqRows = true; + + private boolean nullSelectionAllowed = true; + + private SelectMode selectMode = SelectMode.NONE; + + public final HashSet selectedRowKeys = new HashSet(); + + /* + * When scrolling and selecting at the same time, the selections are not in + * sync with the server while retrieving new rows (until key is released). + */ + private HashSet unSyncedselectionsBeforeRowFetch; + + /* + * These are used when jumping between pages when pressing Home and End + */ + + /** For internal use only. May be removed or replaced in the future. */ + public boolean selectLastItemInNextRender = false; + /** For internal use only. May be removed or replaced in the future. */ + public boolean selectFirstItemInNextRender = false; + /** For internal use only. May be removed or replaced in the future. */ + public boolean focusFirstItemInNextRender = false; + /** For internal use only. May be removed or replaced in the future. */ + public boolean focusLastItemInNextRender = false; + + /** + * The currently focused row. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public VScrollTableRow focusedRow; + + /** + * Helper to store selection range start in when using the keyboard + *

+ * For internal use only. May be removed or replaced in the future. + */ + public VScrollTableRow selectionRangeStart; + + /** + * Flag for notifying when the selection has changed and should be sent to + * the server + *

+ * For internal use only. May be removed or replaced in the future. + */ + public boolean selectionChanged = false; + + /* + * The speed (in pixels) which the scrolling scrolls vertically/horizontally + */ + private int scrollingVelocity = 10; + + private Timer scrollingVelocityTimer = null; + + /** For internal use only. May be removed or replaced in the future. */ + public String[] bodyActionKeys; + + private boolean enableDebug = false; + + private static final boolean hasNativeTouchScrolling = BrowserInfo.get() + .isTouchDevice() + && !BrowserInfo.get().requiresTouchScrollDelegate(); + + private Set noncollapsibleColumns; + + /** + * The last known row height used to preserve the height of a table with + * custom row heights and a fixed page length after removing the last row + * from the table. + * + * A new VScrollTableBody instance is created every time the number of rows + * changes causing {@link VScrollTableBody#rowHeight} to be discarded and + * the height recalculated by {@link VScrollTableBody#getRowHeight(boolean)} + * to avoid some rounding problems, e.g. round(2 * 19.8) / 2 = 20 but + * round(3 * 19.8) / 3 = 19.66. + */ + private double lastKnownRowHeight = Double.NaN; + + /** + * Remember scroll position when getting detached to properly scroll back to + * the location that there is data for if getting attached again. + */ + private int detachedScrollPosition = 0; + + /** + * Represents a select range of rows + */ + private class SelectionRange { + private VScrollTableRow startRow; + private final int length; + + /** + * Constuctor. + */ + public SelectionRange(VScrollTableRow row1, VScrollTableRow row2) { + VScrollTableRow endRow; + if (row2.isBefore(row1)) { + startRow = row2; + endRow = row1; + } else { + startRow = row1; + endRow = row2; + } + length = endRow.getIndex() - startRow.getIndex() + 1; + } + + public SelectionRange(VScrollTableRow row, int length) { + startRow = row; + this.length = length; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + + @Override + public String toString() { + return startRow.getKey() + "-" + length; + } + + private boolean inRange(VScrollTableRow row) { + return row.getIndex() >= startRow.getIndex() + && row.getIndex() < startRow.getIndex() + length; + } + + public Collection split(VScrollTableRow row) { + assert row.isAttached(); + ArrayList ranges = new ArrayList(2); + + int endOfFirstRange = row.getIndex() - 1; + if (endOfFirstRange >= startRow.getIndex()) { + // create range of first part unless its length is < 1 + ranges.add(new SelectionRange(startRow, endOfFirstRange + - startRow.getIndex() + 1)); + } + int startOfSecondRange = row.getIndex() + 1; + if (getEndIndex() >= startOfSecondRange) { + // create range of second part unless its length is < 1 + VScrollTableRow startOfRange = scrollBody + .getRowByRowIndex(startOfSecondRange); + if (startOfRange != null) { + ranges.add(new SelectionRange(startOfRange, getEndIndex() + - startOfSecondRange + 1)); + } + } + return ranges; + } + + private int getEndIndex() { + return startRow.getIndex() + length - 1; + } + + } + + private final HashSet selectedRowRanges = new HashSet(); + + /** For internal use only. May be removed or replaced in the future. */ + public boolean initializedAndAttached = false; + + /** + * Flag to indicate if a column width recalculation is needed due update. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public boolean headerChangedDuringUpdate = false; + + /** For internal use only. May be removed or replaced in the future. */ + public final TableHead tHead = new TableHead(); + + /** For internal use only. May be removed or replaced in the future. */ + public final TableFooter tFoot = new TableFooter(); + + /** Handles context menu for table body */ + private ContextMenuOwner contextMenuOwner = new ContextMenuOwner() { + + @Override + public void showContextMenu(Event event) { + int left = WidgetUtil.getTouchOrMouseClientX(event); + int top = WidgetUtil.getTouchOrMouseClientY(event); + boolean menuShown = handleBodyContextMenu(left, top); + if (menuShown) { + event.stopPropagation(); + event.preventDefault(); + } + } + }; + + /** Handles touch events to display a context menu for table body */ + private TouchContextProvider touchContextProvider = new TouchContextProvider( + contextMenuOwner); + + /** + * For internal use only. May be removed or replaced in the future. + * + * Overwrites onBrowserEvent function on FocusableScrollPanel to give event + * access to touchContextProvider. Has to be public to give TableConnector + * access to the scrollBodyPanel field. + * + * @since 7.2 + * @author Vaadin Ltd + */ + public class FocusableScrollContextPanel extends FocusableScrollPanel { + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + touchContextProvider.handleTouchEvent(event); + }; + + public FocusableScrollContextPanel(boolean useFakeFocusElement) { + super(useFakeFocusElement); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public final FocusableScrollContextPanel scrollBodyPanel = new FocusableScrollContextPanel( + true); + + private KeyPressHandler navKeyPressHandler = new KeyPressHandler() { + + @Override + public void onKeyPress(KeyPressEvent keyPressEvent) { + // This is used for Firefox only, since Firefox auto-repeat + // works correctly only if we use a key press handler, other + // browsers handle it correctly when using a key down handler + if (!BrowserInfo.get().isGecko()) { + return; + } + + NativeEvent event = keyPressEvent.getNativeEvent(); + if (!enabled) { + // Cancel default keyboard events on a disabled Table + // (prevents scrolling) + event.preventDefault(); + } else if (hasFocus) { + // Key code in Firefox/onKeyPress is present only for + // special keys, otherwise 0 is returned + int keyCode = event.getKeyCode(); + if (keyCode == 0 && event.getCharCode() == ' ') { + // Provide a keyCode for space to be compatible with + // FireFox keypress event + keyCode = CHARCODE_SPACE; + } + + if (handleNavigation(keyCode, + event.getCtrlKey() || event.getMetaKey(), + event.getShiftKey())) { + event.preventDefault(); + } + + startScrollingVelocityTimer(); + } + } + + }; + + private KeyUpHandler navKeyUpHandler = new KeyUpHandler() { + + @Override + public void onKeyUp(KeyUpEvent keyUpEvent) { + NativeEvent event = keyUpEvent.getNativeEvent(); + int keyCode = event.getKeyCode(); + + if (!isFocusable()) { + cancelScrollingVelocityTimer(); + } else if (isNavigationKey(keyCode)) { + if (keyCode == getNavigationDownKey() + || keyCode == getNavigationUpKey()) { + /* + * in multiselect mode the server may still have value from + * previous page. Clear it unless doing multiselection or + * just moving focus. + */ + if (!event.getShiftKey() && !event.getCtrlKey()) { + instructServerToForgetPreviousSelections(); + } + sendSelectedRows(); + } + cancelScrollingVelocityTimer(); + navKeyDown = false; + } + } + }; + + private KeyDownHandler navKeyDownHandler = new KeyDownHandler() { + + @Override + public void onKeyDown(KeyDownEvent keyDownEvent) { + NativeEvent event = keyDownEvent.getNativeEvent(); + // This is not used for Firefox + if (BrowserInfo.get().isGecko()) { + return; + } + + if (!enabled) { + // Cancel default keyboard events on a disabled Table + // (prevents scrolling) + event.preventDefault(); + } else if (hasFocus) { + if (handleNavigation(event.getKeyCode(), event.getCtrlKey() + || event.getMetaKey(), event.getShiftKey())) { + navKeyDown = true; + event.preventDefault(); + } + + startScrollingVelocityTimer(); + } + } + }; + + /** For internal use only. May be removed or replaced in the future. */ + public int totalRows; + + private Set collapsedColumns; + + /** For internal use only. May be removed or replaced in the future. */ + public final RowRequestHandler rowRequestHandler; + + /** For internal use only. May be removed or replaced in the future. */ + public VScrollTableBody scrollBody; + + private int firstvisible = 0; + private boolean sortAscending; + private String sortColumn; + private String oldSortColumn; + private boolean columnReordering; + + /** + * This map contains captions and icon urls for actions like: * "33_c" -> + * "Edit" * "33_i" -> "http://dom.com/edit.png" + */ + private final HashMap actionMap = new HashMap(); + private String[] visibleColOrder; + private boolean initialContentReceived = false; + private Element scrollPositionElement; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean enabled; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean showColHeaders; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean showColFooters; + + /** flag to indicate that table body has changed */ + private boolean isNewBody = true; + + /** + * Read from the "recalcWidths" -attribute. When it is true, the table will + * recalculate the widths for columns - desirable in some cases. For #1983, + * marked experimental. See also variable refreshContentWidths + * in method {@link TableHead#updateCellsFromUIDL(UIDL)}. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public boolean recalcWidths = false; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean rendering = false; + + private boolean hasFocus = false; + private int dragmode; + + private int multiselectmode; + + /** + * Hint for how to handle measurement of child components + */ + private ChildMeasurementHint childMeasurementHint = ChildMeasurementHint.MEASURE_ALWAYS; + + /** For internal use only. May be removed or replaced in the future. */ + public int tabIndex; + + private TouchScrollDelegate touchScrollDelegate; + + /** For internal use only. May be removed or replaced in the future. */ + public int lastRenderedHeight; + + /** + * Values (serverCacheFirst+serverCacheLast) sent by server that tells which + * rows (indexes) are in the server side cache (page buffer). -1 means + * unknown. The server side cache row MUST MATCH the client side cache rows. + * + * If the client side cache contains additional rows with e.g. buttons, it + * will cause out of sync when such a button is pressed. + * + * If the server side cache contains additional rows with e.g. buttons, + * scrolling in the client will cause empty buttons to be rendered + * (cached=true request for non-existing components) + * + * For internal use only. May be removed or replaced in the future. + */ + public int serverCacheFirst = -1; + public int serverCacheLast = -1; + + /** + * In several cases TreeTable depends on the scrollBody.lastRendered being + * 'out of sync' while the update is being done. In those cases the sanity + * check must be performed afterwards. + */ + public boolean postponeSanityCheckForLastRendered; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean sizeNeedsInit = true; + + /** + * Used to recall the position of an open context menu if we need to close + * and reopen it during a row update. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public class ContextMenuDetails implements CloseHandler { + public String rowKey; + public int left; + public int top; + HandlerRegistration closeRegistration; + + public ContextMenuDetails(VContextMenu menu, String rowKey, int left, + int top) { + this.rowKey = rowKey; + this.left = left; + this.top = top; + closeRegistration = menu.addCloseHandler(this); + } + + @Override + public void onClose(CloseEvent event) { + contextMenu = null; + closeRegistration.removeHandler(); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public ContextMenuDetails contextMenu = null; + + private boolean hadScrollBars = false; + + private HandlerRegistration addCloseHandler; + + /** + * Changes to manage mouseDown and mouseUp + */ + /** + * The element where the last mouse down event was registered. + */ + private Element lastMouseDownTarget; + + /** + * Set to true by {@link #mouseUpPreviewHandler} if it gets a mouseup at the + * same element as {@link #lastMouseDownTarget}. + */ + private boolean mouseUpPreviewMatched = false; + + private HandlerRegistration mouseUpEventPreviewRegistration; + + /** + * Previews events after a mousedown to detect where the following mouseup + * hits. + */ + private final NativePreviewHandler mouseUpPreviewHandler = new NativePreviewHandler() { + + @Override + public void onPreviewNativeEvent(NativePreviewEvent event) { + if (event.getTypeInt() == Event.ONMOUSEUP) { + mouseUpEventPreviewRegistration.removeHandler(); + + // Event's reported target not always correct if event + // capture is in use + Element elementUnderMouse = WidgetUtil + .getElementUnderMouse(event.getNativeEvent()); + if (lastMouseDownTarget != null + && lastMouseDownTarget.isOrHasChild(elementUnderMouse)) { + mouseUpPreviewMatched = true; + } else { + getLogger().log( + Level.FINEST, + "Ignoring mouseup from " + elementUnderMouse + + " when mousedown was on " + + lastMouseDownTarget); + } + } + } + }; + + public VScrollTable() { + setMultiSelectMode(MULTISELECT_MODE_DEFAULT); + + scrollBodyPanel.addFocusHandler(this); + scrollBodyPanel.addBlurHandler(this); + + scrollBodyPanel.addScrollHandler(this); + + /* + * Firefox auto-repeat works correctly only if we use a key press + * handler, other browsers handle it correctly when using a key down + * handler + */ + if (BrowserInfo.get().isGecko()) { + scrollBodyPanel.addKeyPressHandler(navKeyPressHandler); + } else { + scrollBodyPanel.addKeyDownHandler(navKeyDownHandler); + } + scrollBodyPanel.addKeyUpHandler(navKeyUpHandler); + + scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS | Event.ONCONTEXTMENU); + + setStyleName(STYLENAME); + + add(tHead); + add(scrollBodyPanel); + add(tFoot); + + rowRequestHandler = new RowRequestHandler(); + } + + @Override + public void setStyleName(String style) { + updateStyleNames(style, false); + } + + @Override + public void setStylePrimaryName(String style) { + updateStyleNames(style, true); + } + + private void updateStyleNames(String newStyle, boolean isPrimary) { + scrollBodyPanel + .removeStyleName(getStylePrimaryName() + "-body-wrapper"); + scrollBodyPanel.removeStyleName(getStylePrimaryName() + "-body"); + + if (scrollBody != null) { + scrollBody.removeStyleName(getStylePrimaryName() + + "-body-noselection"); + } + + if (isPrimary) { + super.setStylePrimaryName(newStyle); + } else { + super.setStyleName(newStyle); + } + + scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body-wrapper"); + scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body"); + + tHead.updateStyleNames(getStylePrimaryName()); + tFoot.updateStyleNames(getStylePrimaryName()); + + if (scrollBody != null) { + scrollBody.updateStyleNames(getStylePrimaryName()); + } + } + + public void init(ApplicationConnection client) { + this.client = client; + // Add a handler to clear saved context menu details when the menu + // closes. See #8526. + addCloseHandler = client.getContextMenu().addCloseHandler( + new CloseHandler() { + + @Override + public void onClose(CloseEvent event) { + contextMenu = null; + } + }); + } + + /** + * Handles a context menu event on table body. + * + * @param left + * left position of the context menu + * @param top + * top position of the context menu + * @return true if a context menu was shown, otherwise false + */ + private boolean handleBodyContextMenu(int left, int top) { + if (enabled && bodyActionKeys != null) { + top += Window.getScrollTop(); + left += Window.getScrollLeft(); + client.getContextMenu().showAt(this, left, top); + return true; + } + return false; + } + + /** + * Fires a column resize event which sends the resize information to the + * server. + * + * @param columnId + * The columnId of the column which was resized + * @param originalWidth + * The width in pixels of the column before the resize event + * @param newWidth + * The width in pixels of the column after the resize event + */ + private void fireColumnResizeEvent(String columnId, int originalWidth, + int newWidth) { + client.updateVariable(paintableId, "columnResizeEventColumn", columnId, + false); + client.updateVariable(paintableId, "columnResizeEventPrev", + originalWidth, false); + client.updateVariable(paintableId, "columnResizeEventCurr", newWidth, + immediate); + + } + + /** + * Non-immediate variable update of column widths for a collection of + * columns. + * + * @param columns + * the columns to trigger the events for. + */ + private void sendColumnWidthUpdates(Collection columns) { + String[] newSizes = new String[columns.size()]; + int ix = 0; + for (HeaderCell cell : columns) { + newSizes[ix++] = cell.getColKey() + ":" + cell.getWidth(); + } + client.updateVariable(paintableId, "columnWidthUpdates", newSizes, + false); + } + + /** + * Moves the focus one step down + * + * @return Returns true if succeeded + */ + private boolean moveFocusDown() { + return moveFocusDown(0); + } + + /** + * Moves the focus down by 1+offset rows + * + * @return Returns true if succeeded, else false if the selection could not + * be move downwards + */ + private boolean moveFocusDown(int offset) { + if (isSelectable()) { + if (focusedRow == null && scrollBody.iterator().hasNext()) { + // FIXME should focus first visible from top, not first rendered + // ?? + return setRowFocus((VScrollTableRow) scrollBody.iterator() + .next()); + } else { + VScrollTableRow next = getNextRow(focusedRow, offset); + if (next != null) { + return setRowFocus(next); + } + } + } + + return false; + } + + /** + * Moves the selection one step up + * + * @return Returns true if succeeded + */ + private boolean moveFocusUp() { + return moveFocusUp(0); + } + + /** + * Moves the focus row upwards + * + * @return Returns true if succeeded, else false if the selection could not + * be move upwards + * + */ + private boolean moveFocusUp(int offset) { + if (isSelectable()) { + if (focusedRow == null && scrollBody.iterator().hasNext()) { + // FIXME logic is exactly the same as in moveFocusDown, should + // be the opposite?? + return setRowFocus((VScrollTableRow) scrollBody.iterator() + .next()); + } else { + VScrollTableRow prev = getPreviousRow(focusedRow, offset); + if (prev != null) { + return setRowFocus(prev); + } else { + VConsole.log("no previous available"); + } + } + } + + return false; + } + + /** + * Selects a row where the current selection head is + * + * @param ctrlSelect + * Is the selection a ctrl+selection + * @param shiftSelect + * Is the selection a shift+selection + * @return Returns truw + */ + private void selectFocusedRow(boolean ctrlSelect, boolean shiftSelect) { + if (focusedRow != null) { + // Arrows moves the selection and clears previous selections + if (isSelectable() && !ctrlSelect && !shiftSelect) { + deselectAll(); + focusedRow.toggleSelection(); + selectionRangeStart = focusedRow; + } else if (isSelectable() && ctrlSelect && !shiftSelect) { + // Ctrl+arrows moves selection head + selectionRangeStart = focusedRow; + // No selection, only selection head is moved + } else if (isMultiSelectModeAny() && !ctrlSelect && shiftSelect) { + // Shift+arrows selection selects a range + focusedRow.toggleShiftSelection(shiftSelect); + } + } + } + + /** + * Sends the selection to the server if changed since the last update/visit. + */ + protected void sendSelectedRows() { + sendSelectedRows(immediate); + } + + private void updateFirstVisibleAndSendSelectedRows() { + updateFirstVisibleRow(); + sendSelectedRows(immediate); + } + + /** + * Sends the selection to the server if it has been changed since the last + * update/visit. + * + * @param immediately + * set to true to immediately send the rows + */ + protected void sendSelectedRows(boolean immediately) { + // Don't send anything if selection has not changed + if (!selectionChanged) { + return; + } + + // Reset selection changed flag + selectionChanged = false; + + // Note: changing the immediateness of this might require changes to + // "clickEvent" immediateness also. + if (isMultiSelectModeDefault()) { + // Convert ranges to a set of strings + Set ranges = new HashSet(); + for (SelectionRange range : selectedRowRanges) { + ranges.add(range.toString()); + } + + // Send the selected row ranges + client.updateVariable(paintableId, "selectedRanges", + ranges.toArray(new String[selectedRowRanges.size()]), false); + selectedRowRanges.clear(); + + // clean selectedRowKeys so that they don't contain excess values + for (Iterator iterator = selectedRowKeys.iterator(); iterator + .hasNext();) { + String key = iterator.next(); + VScrollTableRow renderedRowByKey = getRenderedRowByKey(key); + if (renderedRowByKey != null) { + for (SelectionRange range : selectedRowRanges) { + if (range.inRange(renderedRowByKey)) { + iterator.remove(); + } + } + } else { + // orphaned selected key, must be in a range, ignore + iterator.remove(); + } + + } + } + + // Send the selected rows + client.updateVariable(paintableId, "selected", + selectedRowKeys.toArray(new String[selectedRowKeys.size()]), + immediately); + + } + + /** + * Get the key that moves the selection head upwards. By default it is the + * up arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationUpKey() { + return KeyCodes.KEY_UP; + } + + /** + * Get the key that moves the selection head downwards. By default it is the + * down arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationDownKey() { + return KeyCodes.KEY_DOWN; + } + + /** + * Get the key that scrolls to the left in the table. By default it is the + * left arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationLeftKey() { + return KeyCodes.KEY_LEFT; + } + + /** + * Get the key that scroll to the right on the table. By default it is the + * right arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationRightKey() { + return KeyCodes.KEY_RIGHT; + } + + /** + * Get the key that selects an item in the table. By default it is the space + * bar key but by overriding this you can change the key to whatever you + * want. + * + * @return + */ + protected int getNavigationSelectKey() { + return CHARCODE_SPACE; + } + + /** + * Get the key the moves the selection one page up in the table. By default + * this is the Page Up key but by overriding this you can change the key to + * whatever you want. + * + * @return + */ + protected int getNavigationPageUpKey() { + return KeyCodes.KEY_PAGEUP; + } + + /** + * Get the key the moves the selection one page down in the table. By + * default this is the Page Down key but by overriding this you can change + * the key to whatever you want. + * + * @return + */ + protected int getNavigationPageDownKey() { + return KeyCodes.KEY_PAGEDOWN; + } + + /** + * Get the key the moves the selection to the beginning of the table. By + * default this is the Home key but by overriding this you can change the + * key to whatever you want. + * + * @return + */ + protected int getNavigationStartKey() { + return KeyCodes.KEY_HOME; + } + + /** + * Get the key the moves the selection to the end of the table. By default + * this is the End key but by overriding this you can change the key to + * whatever you want. + * + * @return + */ + protected int getNavigationEndKey() { + return KeyCodes.KEY_END; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void initializeRows(UIDL uidl, UIDL rowData) { + if (scrollBody != null) { + scrollBody.removeFromParent(); + } + + // Without this call the scroll position is messed up in IE even after + // the lazy scroller has set the scroll position to the first visible + // item + int pos = scrollBodyPanel.getScrollPosition(); + + // Reset first row in view port so client requests correct last row. + if (pos == 0) { + firstRowInViewPort = 0; + } + + scrollBody = createScrollBody(); + + scrollBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"), + uidl.getIntAttribute("rows")); + scrollBodyPanel.add(scrollBody); + + initialContentReceived = true; + sizeNeedsInit = true; + scrollBody.restoreRowVisibility(); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateColumnProperties(UIDL uidl) { + updateColumnOrder(uidl); + + updateCollapsedColumns(uidl); + + UIDL vc = uidl.getChildByTagName("visiblecolumns"); + if (vc != null) { + tHead.updateCellsFromUIDL(vc); + tFoot.updateCellsFromUIDL(vc); + } + + updateHeader(uidl.getStringArrayAttribute("vcolorder")); + updateFooter(uidl.getStringArrayAttribute("vcolorder")); + if (uidl.hasVariable("noncollapsiblecolumns")) { + noncollapsibleColumns = uidl + .getStringArrayVariableAsSet("noncollapsiblecolumns"); + } + } + + private void updateCollapsedColumns(UIDL uidl) { + if (uidl.hasVariable("collapsedcolumns")) { + tHead.setColumnCollapsingAllowed(true); + collapsedColumns = uidl + .getStringArrayVariableAsSet("collapsedcolumns"); + } else { + tHead.setColumnCollapsingAllowed(false); + } + } + + private void updateColumnOrder(UIDL uidl) { + if (uidl.hasVariable("columnorder")) { + columnReordering = true; + columnOrder = uidl.getStringArrayVariable("columnorder"); + } else { + columnReordering = false; + columnOrder = null; + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public boolean selectSelectedRows(UIDL uidl) { + boolean keyboardSelectionOverRowFetchInProgress = false; + + if (uidl.hasVariable("selected")) { + final Set selectedKeys = uidl + .getStringArrayVariableAsSet("selected"); + // Do not update focus if there is a single selected row + // that is the same as the previous selection. This prevents + // unwanted scrolling (#18247). + boolean rowsUnSelected = removeUnselectedRowKeys(selectedKeys); + boolean updateFocus = rowsUnSelected || selectedRowKeys.size() == 0 + || focusedRow == null; + if (scrollBody != null) { + Iterator iterator = scrollBody.iterator(); + while (iterator.hasNext()) { + /* + * Make the focus reflect to the server side state unless we + * are currently selecting multiple rows with keyboard. + */ + VScrollTableRow row = (VScrollTableRow) iterator.next(); + boolean selected = selectedKeys.contains(row.getKey()); + if (!selected + && unSyncedselectionsBeforeRowFetch != null + && unSyncedselectionsBeforeRowFetch.contains(row + .getKey())) { + selected = true; + keyboardSelectionOverRowFetchInProgress = true; + } + if (selected && selectedKeys.size() == 1 && updateFocus) { + /* + * If a single item is selected, move focus to the + * selected row. (#10522) + */ + setRowFocus(row); + } + + if (selected != row.isSelected()) { + row.toggleSelection(); + + if (!isSingleSelectMode() && !selected) { + // Update selection range in case a row is + // unselected from the middle of a range - #8076 + removeRowFromUnsentSelectionRanges(row); + } + } + } + + } + } + unSyncedselectionsBeforeRowFetch = null; + return keyboardSelectionOverRowFetchInProgress; + } + + private boolean removeUnselectedRowKeys(final Set selectedKeys) { + List unselectedKeys = new ArrayList(0); + for (String key : selectedRowKeys) { + if (!selectedKeys.contains(key)) { + unselectedKeys.add(key); + } + } + return selectedRowKeys.removeAll(unselectedKeys); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateSortingProperties(UIDL uidl) { + oldSortColumn = sortColumn; + if (uidl.hasVariable("sortascending")) { + sortAscending = uidl.getBooleanVariable("sortascending"); + sortColumn = uidl.getStringVariable("sortcolumn"); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void resizeSortedColumnForSortIndicator() { + // Force recalculation of the captionContainer element inside the header + // cell to accomodate for the size of the sort arrow. + HeaderCell sortedHeader = tHead.getHeaderCell(sortColumn); + if (sortedHeader != null) { + // Mark header as sorted now. Any earlier marking would lead to + // columns with wrong sizes + sortedHeader.setSorted(true); + tHead.resizeCaptionContainer(sortedHeader); + } + // Also recalculate the width of the captionContainer element in the + // previously sorted header, since this now has more room. + HeaderCell oldSortedHeader = tHead.getHeaderCell(oldSortColumn); + if (oldSortedHeader != null) { + tHead.resizeCaptionContainer(oldSortedHeader); + } + } + + private boolean lazyScrollerIsActive; + + private void disableLazyScroller() { + lazyScrollerIsActive = false; + scrollBodyPanel.getElement().getStyle().clearOverflowX(); + scrollBodyPanel.getElement().getStyle().clearOverflowY(); + } + + private void enableLazyScroller() { + Scheduler.get().scheduleDeferred(lazyScroller); + lazyScrollerIsActive = true; + // prevent scrolling to jump in IE11 + scrollBodyPanel.getElement().getStyle().setOverflowX(Overflow.HIDDEN); + scrollBodyPanel.getElement().getStyle().setOverflowY(Overflow.HIDDEN); + } + + private boolean isLazyScrollerActive() { + return lazyScrollerIsActive; + } + + private ScheduledCommand lazyScroller = new ScheduledCommand() { + + @Override + public void execute() { + if (firstvisible >= 0) { + firstRowInViewPort = firstvisible; + if (firstvisibleOnLastPage > -1) { + scrollBodyPanel + .setScrollPosition(measureRowHeightOffset(firstvisibleOnLastPage)); + } else { + scrollBodyPanel + .setScrollPosition(measureRowHeightOffset(firstvisible)); + } + } + disableLazyScroller(); + } + }; + + /** For internal use only. May be removed or replaced in the future. */ + public void updateFirstVisibleAndScrollIfNeeded(UIDL uidl) { + firstvisible = uidl.hasVariable("firstvisible") ? uidl + .getIntVariable("firstvisible") : 0; + firstvisibleOnLastPage = uidl.hasVariable("firstvisibleonlastpage") ? uidl + .getIntVariable("firstvisibleonlastpage") : -1; + if (firstvisible != lastRequestedFirstvisible && scrollBody != null) { + + // Update lastRequestedFirstvisible right away here + // (don't rely on update in the timer which could be cancelled). + lastRequestedFirstvisible = firstRowInViewPort; + + // Only scroll if the first visible changes from the server side. + // Else we might unintentionally scroll even when the scroll + // position has not changed. + enableLazyScroller(); + } + } + + protected int measureRowHeightOffset(int rowIx) { + return (int) (rowIx * scrollBody.getRowHeight()); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updatePageLength(UIDL uidl) { + int oldPageLength = pageLength; + if (uidl.hasAttribute("pagelength")) { + pageLength = uidl.getIntAttribute("pagelength"); + } else { + // pagelength is "0" meaning scrolling is turned off + pageLength = totalRows; + } + + if (oldPageLength != pageLength && initializedAndAttached) { + // page length changed, need to update size + sizeNeedsInit = true; + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateSelectionProperties(UIDL uidl, + AbstractComponentState state, boolean readOnly) { + setMultiSelectMode(uidl.hasAttribute("multiselectmode") ? uidl + .getIntAttribute("multiselectmode") : MULTISELECT_MODE_DEFAULT); + + nullSelectionAllowed = uidl.hasAttribute("nsa") ? uidl + .getBooleanAttribute("nsa") : true; + + if (uidl.hasAttribute("selectmode")) { + if (readOnly) { + selectMode = SelectMode.NONE; + } else if (uidl.getStringAttribute("selectmode").equals("multi")) { + selectMode = SelectMode.MULTI; + } else if (uidl.getStringAttribute("selectmode").equals("single")) { + selectMode = SelectMode.SINGLE; + } else { + selectMode = SelectMode.NONE; + } + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateDragMode(UIDL uidl) { + dragmode = uidl.hasAttribute("dragmode") ? uidl + .getIntAttribute("dragmode") : 0; + if (BrowserInfo.get().isIE()) { + if (dragmode > 0) { + getElement().setPropertyJSO("onselectstart", + getPreventTextSelectionIEHack()); + } else { + getElement().setPropertyJSO("onselectstart", null); + } + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateTotalRows(UIDL uidl) { + int newTotalRows = uidl.getIntAttribute("totalrows"); + if (newTotalRows != getTotalRows()) { + if (scrollBody != null) { + if (getTotalRows() == 0) { + tHead.clear(); + tFoot.clear(); + } + initializedAndAttached = false; + initialContentReceived = false; + isNewBody = true; + } + setTotalRows(newTotalRows); + } + } + + protected void setTotalRows(int newTotalRows) { + totalRows = newTotalRows; + } + + public int getTotalRows() { + return totalRows; + } + + /** + * Returns the extra space that is given to the header column when column + * width is determined by header text. + * + * @return extra space in pixels + */ + private int getHeaderPadding() { + return scrollBody.getCellExtraWidth(); + } + + /** + * This method exists for the needs of {@link VTreeTable} only. Not part of + * the official API, extend at your own risk. May be removed or + * replaced in the future. + * + * @return index of TreeTable's hierarchy column, or -1 if not applicable + */ + protected int getHierarchyColumnIndex() { + return -1; + } + + /** + * For internal use only. May be removed or replaced in the future. + */ + public void updateMaxIndent() { + int oldIndent = scrollBody.getMaxIndent(); + scrollBody.calculateMaxIndent(); + if (oldIndent != scrollBody.getMaxIndent()) { + // indent updated, headers might need adjusting + triggerLazyColumnAdjustment(true); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void focusRowFromBody() { + if (selectedRowKeys.size() == 1) { + // try to focus a row currently selected and in viewport + String selectedRowKey = selectedRowKeys.iterator().next(); + if (selectedRowKey != null) { + VScrollTableRow renderedRow = getRenderedRowByKey(selectedRowKey); + if (renderedRow == null || !renderedRow.isInViewPort()) { + setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort)); + } else { + setRowFocus(renderedRow); + } + } + } else { + // multiselect mode + setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort)); + } + } + + protected VScrollTableBody createScrollBody() { + return new VScrollTableBody(); + } + + /** + * Selects the last row visible in the table + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param focusOnly + * Should the focus only be moved to the last row + */ + public void selectLastRenderedRowInViewPort(boolean focusOnly) { + int index = firstRowInViewPort + getFullyVisibleRowCount(); + VScrollTableRow lastRowInViewport = scrollBody.getRowByRowIndex(index); + if (lastRowInViewport == null) { + // this should not happen in normal situations (white space at the + // end of viewport). Select the last rendered as a fallback. + lastRowInViewport = scrollBody.getRowByRowIndex(scrollBody + .getLastRendered()); + if (lastRowInViewport == null) { + return; // empty table + } + } + setRowFocus(lastRowInViewport); + if (!focusOnly) { + selectFocusedRow(false, multiselectPending); + sendSelectedRows(); + } + } + + /** + * Selects the first row visible in the table + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param focusOnly + * Should the focus only be moved to the first row + */ + public void selectFirstRenderedRowInViewPort(boolean focusOnly) { + int index = firstRowInViewPort; + VScrollTableRow firstInViewport = scrollBody.getRowByRowIndex(index); + if (firstInViewport == null) { + // this should not happen in normal situations + return; + } + setRowFocus(firstInViewport); + if (!focusOnly) { + selectFocusedRow(false, multiselectPending); + sendSelectedRows(); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setCacheRateFromUIDL(UIDL uidl) { + setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr") + : CACHE_RATE_DEFAULT); + } + + private void setCacheRate(double d) { + if (cache_rate != d) { + cache_rate = d; + cache_react_rate = 0.75 * d; + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateActionMap(UIDL mainUidl) { + UIDL actionsUidl = mainUidl.getChildByTagName("actions"); + if (actionsUidl == null) { + return; + } + + final Iterator it = actionsUidl.getChildIterator(); + while (it.hasNext()) { + final UIDL action = (UIDL) it.next(); + final String key = action.getStringAttribute("key"); + final String caption = action.getStringAttribute("caption"); + actionMap.put(key + "_c", caption); + if (action.hasAttribute("icon")) { + // TODO need some uri handling ?? + actionMap.put(key + "_i", action.getStringAttribute("icon")); + } else { + actionMap.remove(key + "_i"); + } + } + + } + + public String getActionCaption(String actionKey) { + return actionMap.get(actionKey + "_c"); + } + + public String getActionIcon(String actionKey) { + return client.translateVaadinUri(actionMap.get(actionKey + "_i")); + } + + private void updateHeader(String[] strings) { + if (strings == null) { + return; + } + + int visibleCols = strings.length; + int colIndex = 0; + if (showRowHeaders) { + tHead.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex); + visibleCols++; + visibleColOrder = new String[visibleCols]; + visibleColOrder[colIndex] = ROW_HEADER_COLUMN_KEY; + colIndex++; + } else { + visibleColOrder = new String[visibleCols]; + tHead.removeCell(ROW_HEADER_COLUMN_KEY); + } + + int i; + for (i = 0; i < strings.length; i++) { + final String cid = strings[i]; + visibleColOrder[colIndex] = cid; + tHead.enableColumn(cid, colIndex); + colIndex++; + } + + tHead.setVisible(showColHeaders); + setContainerHeight(); + + } + + /** + * Updates footers. + *

+ * Update headers whould be called before this method is called! + *

+ * + * @param strings + */ + private void updateFooter(String[] strings) { + if (strings == null) { + return; + } + + // Add dummy column if row headers are present + int colIndex = 0; + if (showRowHeaders) { + tFoot.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex); + colIndex++; + } else { + tFoot.removeCell(ROW_HEADER_COLUMN_KEY); + } + + int i; + for (i = 0; i < strings.length; i++) { + final String cid = strings[i]; + tFoot.enableColumn(cid, colIndex); + colIndex++; + } + + tFoot.setVisible(showColFooters); + } + + /** + * For internal use only. May be removed or replaced in the future. + * + * @param uidl + * which contains row data + * @param firstRow + * first row in data set + * @param reqRows + * amount of rows in data set + */ + public void updateBody(UIDL uidl, int firstRow, int reqRows) { + int oldIndent = scrollBody.getMaxIndent(); + if (uidl == null || reqRows < 1) { + // container is empty, remove possibly existing rows + if (firstRow <= 0) { + postponeSanityCheckForLastRendered = true; + while (scrollBody.getLastRendered() > scrollBody + .getFirstRendered()) { + scrollBody.unlinkRow(false); + } + postponeSanityCheckForLastRendered = false; + scrollBody.unlinkRow(false); + } + return; + } + + scrollBody.renderRows(uidl, firstRow, reqRows); + + discardRowsOutsideCacheWindow(); + scrollBody.calculateMaxIndent(); + if (oldIndent != scrollBody.getMaxIndent()) { + // indent updated, headers might need adjusting + headerChangedDuringUpdate = true; + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateRowsInBody(UIDL partialRowUpdates) { + if (partialRowUpdates == null) { + return; + } + int firstRowIx = partialRowUpdates.getIntAttribute("firsturowix"); + int count = partialRowUpdates.getIntAttribute("numurows"); + scrollBody.unlinkRows(firstRowIx, count); + scrollBody.insertRows(partialRowUpdates, firstRowIx, count); + } + + /** + * Updates the internal cache by unlinking rows that fall outside of the + * caching window. + */ + protected void discardRowsOutsideCacheWindow() { + int firstRowToKeep = (int) (firstRowInViewPort - pageLength + * cache_rate); + int lastRowToKeep = (int) (firstRowInViewPort + pageLength + pageLength + * cache_rate); + // sanity checks: + if (firstRowToKeep < 0) { + firstRowToKeep = 0; + } + if (lastRowToKeep > totalRows) { + lastRowToKeep = totalRows - 1; + } + debug("Client side calculated cache rows to keep: " + firstRowToKeep + + "-" + lastRowToKeep); + + if (serverCacheFirst != -1) { + firstRowToKeep = serverCacheFirst; + lastRowToKeep = serverCacheLast; + debug("Server cache rows that override: " + serverCacheFirst + "-" + + serverCacheLast); + if (firstRowToKeep < scrollBody.getFirstRendered() + || lastRowToKeep > scrollBody.getLastRendered()) { + debug("*** Server wants us to keep " + serverCacheFirst + "-" + + serverCacheLast + " but we only have rows " + + scrollBody.getFirstRendered() + "-" + + scrollBody.getLastRendered() + " rendered!"); + } + } + discardRowsOutsideOf(firstRowToKeep, lastRowToKeep); + + scrollBody.fixSpacers(); + + scrollBody.restoreRowVisibility(); + } + + private void discardRowsOutsideOf(int optimalFirstRow, int optimalLastRow) { + /* + * firstDiscarded and lastDiscarded are only calculated for debug + * purposes + */ + int firstDiscarded = -1, lastDiscarded = -1; + boolean cont = true; + while (cont && scrollBody.getLastRendered() > optimalFirstRow + && scrollBody.getFirstRendered() < optimalFirstRow) { + if (firstDiscarded == -1) { + firstDiscarded = scrollBody.getFirstRendered(); + } + + // removing row from start + cont = scrollBody.unlinkRow(true); + } + if (firstDiscarded != -1) { + lastDiscarded = scrollBody.getFirstRendered() - 1; + debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded); + } + firstDiscarded = lastDiscarded = -1; + + cont = true; + while (cont && scrollBody.getLastRendered() > optimalLastRow) { + if (lastDiscarded == -1) { + lastDiscarded = scrollBody.getLastRendered(); + } + + // removing row from the end + cont = scrollBody.unlinkRow(false); + } + if (lastDiscarded != -1) { + firstDiscarded = scrollBody.getLastRendered() + 1; + debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded); + } + + debug("Now in cache: " + scrollBody.getFirstRendered() + "-" + + scrollBody.getLastRendered()); + } + + /** + * Inserts rows in the table body or removes them from the table body based + * on the commands in the UIDL. + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param partialRowAdditions + * the UIDL containing row updates. + */ + public void addAndRemoveRows(UIDL partialRowAdditions) { + if (partialRowAdditions == null) { + return; + } + if (partialRowAdditions.hasAttribute("hide")) { + scrollBody.unlinkAndReindexRows( + partialRowAdditions.getIntAttribute("firstprowix"), + partialRowAdditions.getIntAttribute("numprows")); + scrollBody.ensureCacheFilled(); + } else { + if (partialRowAdditions.hasAttribute("delbelow")) { + scrollBody.insertRowsDeleteBelow(partialRowAdditions, + partialRowAdditions.getIntAttribute("firstprowix"), + partialRowAdditions.getIntAttribute("numprows")); + } else { + scrollBody.insertAndReindexRows(partialRowAdditions, + partialRowAdditions.getIntAttribute("firstprowix"), + partialRowAdditions.getIntAttribute("numprows")); + } + } + + discardRowsOutsideCacheWindow(); + } + + /** + * Gives correct column index for given column key ("cid" in UIDL). + * + * @param colKey + * @return column index of visible columns, -1 if column not visible + */ + private int getColIndexByKey(String colKey) { + // return 0 if asked for rowHeaders + if (ROW_HEADER_COLUMN_KEY.equals(colKey)) { + return 0; + } + for (int i = 0; i < visibleColOrder.length; i++) { + if (visibleColOrder[i].equals(colKey)) { + return i; + } + } + return -1; + } + + private boolean isMultiSelectModeSimple() { + return selectMode == SelectMode.MULTI + && multiselectmode == MULTISELECT_MODE_SIMPLE; + } + + private boolean isSingleSelectMode() { + return selectMode == SelectMode.SINGLE; + } + + private boolean isMultiSelectModeAny() { + return selectMode == SelectMode.MULTI; + } + + private boolean isMultiSelectModeDefault() { + return selectMode == SelectMode.MULTI + && multiselectmode == MULTISELECT_MODE_DEFAULT; + } + + private void setMultiSelectMode(int multiselectmode) { + if (BrowserInfo.get().isTouchDevice()) { + // Always use the simple mode for touch devices that do not have + // shift/ctrl keys + this.multiselectmode = MULTISELECT_MODE_SIMPLE; + } else { + this.multiselectmode = multiselectmode; + } + + } + + /** For internal use only. May be removed or replaced in the future. */ + public boolean isSelectable() { + return selectMode.getId() > SelectMode.NONE.getId(); + } + + private boolean isCollapsedColumn(String colKey) { + if (collapsedColumns == null) { + return false; + } + if (collapsedColumns.contains(colKey)) { + return true; + } + return false; + } + + private String getColKeyByIndex(int index) { + return tHead.getHeaderCell(index).getColKey(); + } + + /** + * Note: not part of the official API, extend at your own risk. May be + * removed or replaced in the future. + * + * Sets the indicated column's width for headers and scrollBody alike. + * + * @param colIndex + * index of the modified column + * @param w + * new width (may be subject to modifications if doesn't meet + * minimum requirements) + * @param isDefinedWidth + * disables expand ratio if set true + */ + protected void setColWidth(int colIndex, int w, boolean isDefinedWidth) { + final HeaderCell hcell = tHead.getHeaderCell(colIndex); + + // Make sure that the column grows to accommodate the sort indicator if + // necessary. + // get min width with no indent or padding + int minWidth = hcell.getMinWidth(false, false); + if (w < minWidth) { + w = minWidth; + } + + // Set header column width WITHOUT INDENT + hcell.setWidth(w, isDefinedWidth); + + // Set footer column width likewise + FooterCell fcell = tFoot.getFooterCell(colIndex); + fcell.setWidth(w, isDefinedWidth); + + // Ensure indicators have been taken into account + tHead.resizeCaptionContainer(hcell); + + // Make sure that the body column grows to accommodate the indent if + // necessary. + // get min width with indent, no padding + minWidth = hcell.getMinWidth(true, false); + if (w < minWidth) { + w = minWidth; + } + + // Set body column width + scrollBody.setColWidth(colIndex, w); + } + + private int getColWidth(String colKey) { + return tHead.getHeaderCell(colKey).getWidthWithIndent(); + } + + /** + * Get a rendered row by its key + * + * @param key + * The key to search with + * @return + */ + public VScrollTableRow getRenderedRowByKey(String key) { + if (scrollBody != null) { + final Iterator it = scrollBody.iterator(); + VScrollTableRow r = null; + while (it.hasNext()) { + r = (VScrollTableRow) it.next(); + if (r.getKey().equals(key)) { + return r; + } + } + } + return null; + } + + /** + * Returns the next row to the given row + * + * @param row + * The row to calculate from + * + * @return The next row or null if no row exists + */ + private VScrollTableRow getNextRow(VScrollTableRow row, int offset) { + final Iterator it = scrollBody.iterator(); + VScrollTableRow r = null; + while (it.hasNext()) { + r = (VScrollTableRow) it.next(); + if (r == row) { + r = null; + while (offset >= 0 && it.hasNext()) { + r = (VScrollTableRow) it.next(); + offset--; + } + return r; + } + } + + return null; + } + + /** + * Returns the previous row from the given row + * + * @param row + * The row to calculate from + * @return The previous row or null if no row exists + */ + private VScrollTableRow getPreviousRow(VScrollTableRow row, int offset) { + final Iterator it = scrollBody.iterator(); + final Iterator offsetIt = scrollBody.iterator(); + VScrollTableRow r = null; + VScrollTableRow prev = null; + while (it.hasNext()) { + r = (VScrollTableRow) it.next(); + if (offset < 0) { + prev = (VScrollTableRow) offsetIt.next(); + } + if (r == row) { + return prev; + } + offset--; + } + + return null; + } + + protected void reOrderColumn(String columnKey, int newIndex) { + + final int oldIndex = getColIndexByKey(columnKey); + + // Change header order + tHead.moveCell(oldIndex, newIndex); + + // Change body order + scrollBody.moveCol(oldIndex, newIndex); + + // Change footer order + tFoot.moveCell(oldIndex, newIndex); + + /* + * Build new columnOrder and update it to server Note that columnOrder + * also contains collapsed columns so we cannot directly build it from + * cells vector Loop the old columnOrder and append in order to new + * array unless on moved columnKey. On new index also put the moved key + * i == index on columnOrder, j == index on newOrder + */ + final String oldKeyOnNewIndex = visibleColOrder[newIndex]; + if (showRowHeaders) { + newIndex--; // columnOrder don't have rowHeader + } + // add back hidden rows, + for (int i = 0; i < columnOrder.length; i++) { + if (columnOrder[i].equals(oldKeyOnNewIndex)) { + break; // break loop at target + } + if (isCollapsedColumn(columnOrder[i])) { + newIndex++; + } + } + // finally we can build the new columnOrder for server + final String[] newOrder = new String[columnOrder.length]; + for (int i = 0, j = 0; j < newOrder.length; i++) { + if (j == newIndex) { + newOrder[j] = columnKey; + j++; + } + if (i == columnOrder.length) { + break; + } + if (columnOrder[i].equals(columnKey)) { + continue; + } + newOrder[j] = columnOrder[i]; + j++; + } + columnOrder = newOrder; + // also update visibleColumnOrder + int i = showRowHeaders ? 1 : 0; + for (int j = 0; j < newOrder.length; j++) { + final String cid = newOrder[j]; + if (!isCollapsedColumn(cid)) { + visibleColOrder[i++] = cid; + } + } + client.updateVariable(paintableId, "columnorder", columnOrder, false); + if (client.hasEventListeners(this, + TableConstants.COLUMN_REORDER_EVENT_ID)) { + client.sendPendingVariableChanges(); + } + } + + @Override + protected void onDetach() { + detachedScrollPosition = scrollBodyPanel.getScrollPosition(); + rowRequestHandler.cancel(); + super.onDetach(); + // ensure that scrollPosElement will be detached + if (scrollPositionElement != null) { + final Element parent = DOM.getParent(scrollPositionElement); + if (parent != null) { + DOM.removeChild(parent, scrollPositionElement); + } + } + } + + @Override + public void onAttach() { + super.onAttach(); + scrollBodyPanel.setScrollPosition(detachedScrollPosition); + } + + /** + * Run only once when component is attached and received its initial + * content. This function: + * + * * Syncs headers and bodys "natural widths and saves the values. + * + * * Sets proper width and height + * + * * Makes deferred request to get some cache rows + * + * For internal use only. May be removed or replaced in the future. + */ + public void sizeInit() { + sizeNeedsInit = false; + + scrollBody.setContainerHeight(); + + /* + * We will use browsers table rendering algorithm to find proper column + * widths. If content and header take less space than available, we will + * divide extra space relatively to each column which has not width set. + * + * Overflow pixels are added to last column. + */ + + Iterator headCells = tHead.iterator(); + Iterator footCells = tFoot.iterator(); + int i = 0; + int totalExplicitColumnsWidths = 0; + int total = 0; + float expandRatioDivider = 0; + + final int[] widths = new int[tHead.visibleCells.size()]; + + tHead.enableBrowserIntelligence(); + tFoot.enableBrowserIntelligence(); + + int hierarchyColumnIndent = scrollBody != null ? scrollBody + .getMaxIndent() : 0; + HeaderCell hierarchyHeaderWithExpandRatio = null; + + // first loop: collect natural widths + while (headCells.hasNext()) { + final HeaderCell hCell = (HeaderCell) headCells.next(); + final FooterCell fCell = (FooterCell) footCells.next(); + boolean needsIndent = hierarchyColumnIndent > 0 + && hCell.isHierarchyColumn(); + hCell.saveNaturalColumnWidthIfNotSaved(i); + fCell.saveNaturalColumnWidthIfNotSaved(i); + int w = hCell.getWidth(); + if (hCell.isDefinedWidth()) { + // server has defined column width explicitly + if (needsIndent && w < hierarchyColumnIndent) { + // hierarchy indent overrides explicitly set width + w = hierarchyColumnIndent; + } + totalExplicitColumnsWidths += w; + } else { + if (hCell.getExpandRatio() > 0) { + expandRatioDivider += hCell.getExpandRatio(); + w = 0; + if (needsIndent && w < hierarchyColumnIndent) { + hierarchyHeaderWithExpandRatio = hCell; + // don't add to widths here, because will be included in + // the expand ratio space if there's enough of it + } + } else { + // get and store greater of header width and column width, + // and store it as a minimum natural column width (these + // already contain the indent if any) + int headerWidth = hCell.getNaturalColumnWidth(i); + int footerWidth = fCell.getNaturalColumnWidth(i); + w = headerWidth > footerWidth ? headerWidth : footerWidth; + } + if (w != 0) { + hCell.setNaturalMinimumColumnWidth(w); + fCell.setNaturalMinimumColumnWidth(w); + } + } + widths[i] = w; + total += w; + i++; + } + if (hierarchyHeaderWithExpandRatio != null) { + total += hierarchyColumnIndent; + } + + tHead.disableBrowserIntelligence(); + tFoot.disableBrowserIntelligence(); + + boolean willHaveScrollbarz = willHaveScrollbars(); + + // fix "natural" width if width not set + if (isDynamicWidth()) { + int w = total; + w += scrollBody.getCellExtraWidth() * visibleColOrder.length; + if (willHaveScrollbarz) { + w += WidgetUtil.getNativeScrollbarSize(); + } + setContentWidth(w); + } + + int availW = scrollBody.getAvailableWidth(); + if (BrowserInfo.get().isIE()) { + // Hey IE, are you really sure about this? + availW = scrollBody.getAvailableWidth(); + } + availW -= scrollBody.getCellExtraWidth() * visibleColOrder.length; + + if (willHaveScrollbarz) { + availW -= WidgetUtil.getNativeScrollbarSize(); + } + + // TODO refactor this code to be the same as in resize timer + + if (availW > total) { + // natural size is smaller than available space + int extraSpace = availW - total; + if (hierarchyHeaderWithExpandRatio != null) { + /* + * add the indent's space back to ensure each column gets an + * even share according to the expand ratios (note: if the + * allocated space isn't enough for the hierarchy column it + * shall be treated like a defined width column and the indent + * space gets removed from the extra space again) + */ + extraSpace += hierarchyColumnIndent; + } + final int totalWidthR = total - totalExplicitColumnsWidths; + int checksum = 0; + + if (extraSpace == 1) { + // We cannot divide one single pixel so we give it the first + // undefined column + // no need to worry about indent here + headCells = tHead.iterator(); + i = 0; + checksum = availW; + while (headCells.hasNext()) { + HeaderCell hc = (HeaderCell) headCells.next(); + if (!hc.isDefinedWidth()) { + widths[i]++; + break; + } + i++; + } + + } else if (expandRatioDivider > 0) { + boolean setIndentToHierarchyHeader = false; + if (hierarchyHeaderWithExpandRatio != null) { + // ensure first that the hierarchyColumn gets at least the + // space allocated for indent + final int newSpace = Math + .round((extraSpace * (hierarchyHeaderWithExpandRatio + .getExpandRatio() / expandRatioDivider))); + if (newSpace < hierarchyColumnIndent) { + // not enough space for indent, remove indent from the + // extraSpace again and handle hierarchy column's header + // separately + setIndentToHierarchyHeader = true; + extraSpace -= hierarchyColumnIndent; + } + } + + // visible columns have some active expand ratios, excess + // space is divided according to them + headCells = tHead.iterator(); + i = 0; + while (headCells.hasNext()) { + HeaderCell hCell = (HeaderCell) headCells.next(); + if (hCell.getExpandRatio() > 0) { + int w = widths[i]; + if (setIndentToHierarchyHeader + && hierarchyHeaderWithExpandRatio.equals(hCell)) { + // hierarchy column's header is no longer part of + // the expansion divide and only gets indent + w += hierarchyColumnIndent; + } else { + final int newSpace = Math + .round((extraSpace * (hCell + .getExpandRatio() / expandRatioDivider))); + w += newSpace; + } + widths[i] = w; + } + checksum += widths[i]; + i++; + } + } else if (totalWidthR > 0) { + // no expand ratios defined, we will share extra space + // relatively to "natural widths" among those without + // explicit width + // no need to worry about indent here, it's already included + headCells = tHead.iterator(); + i = 0; + while (headCells.hasNext()) { + HeaderCell hCell = (HeaderCell) headCells.next(); + if (!hCell.isDefinedWidth()) { + int w = widths[i]; + final int newSpace = Math.round((float) extraSpace + * (float) w / totalWidthR); + w += newSpace; + widths[i] = w; + } + checksum += widths[i]; + i++; + } + } + + if (extraSpace > 0 && checksum != availW) { + /* + * There might be in some cases a rounding error of 1px when + * extra space is divided so if there is one then we give the + * first undefined column 1 more pixel + */ + headCells = tHead.iterator(); + i = 0; + while (headCells.hasNext()) { + HeaderCell hc = (HeaderCell) headCells.next(); + if (!hc.isDefinedWidth()) { + widths[i] += availW - checksum; + break; + } + i++; + } + } + + } else { + // body's size will be more than available and scrollbar will appear + } + + // last loop: set possibly modified values or reset if new tBody + i = 0; + headCells = tHead.iterator(); + while (headCells.hasNext()) { + final HeaderCell hCell = (HeaderCell) headCells.next(); + if (isNewBody || hCell.getWidth() == -1) { + final int w = widths[i]; + setColWidth(i, w, false); + } + i++; + } + + initializedAndAttached = true; + + updatePageLength(); + + /* + * Fix "natural" height if height is not set. This must be after width + * fixing so the components' widths have been adjusted. + */ + if (isDynamicHeight()) { + /* + * We must force an update of the row height as this point as it + * might have been (incorrectly) calculated earlier + */ + + /* + * TreeTable updates stuff in a funky order, so we must set the + * height as zero here before doing the real update to make it + * realize that there is no content, + */ + if (pageLength == totalRows && pageLength == 0) { + scrollBody.setHeight("0px"); + } + + int bodyHeight; + if (pageLength == totalRows) { + /* + * A hack to support variable height rows when paging is off. + * Generally this is not supported by scrolltable. We want to + * show all rows so the bodyHeight should be equal to the table + * height. + */ + // int bodyHeight = scrollBody.getOffsetHeight(); + bodyHeight = scrollBody.getRequiredHeight(); + } else { + bodyHeight = (int) Math.round(scrollBody.getRowHeight(true) + * pageLength); + } + boolean needsSpaceForHorizontalSrollbar = (total > availW); + if (needsSpaceForHorizontalSrollbar) { + bodyHeight += WidgetUtil.getNativeScrollbarSize(); + } + scrollBodyPanel.setHeight(bodyHeight + "px"); + WidgetUtil.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); + } + + isNewBody = false; + + if (firstvisible > 0) { + enableLazyScroller(); + } + + if (enabled) { + // Do we need cache rows + if (scrollBody.getLastRendered() + 1 < firstRowInViewPort + + pageLength + (int) cache_react_rate * pageLength) { + if (totalRows - 1 > scrollBody.getLastRendered()) { + // fetch cache rows + int firstInNewSet = scrollBody.getLastRendered() + 1; + int lastInNewSet = (int) (firstRowInViewPort + pageLength + cache_rate + * pageLength); + if (lastInNewSet > totalRows - 1) { + lastInNewSet = totalRows - 1; + } + rowRequestHandler.triggerRowFetch(firstInNewSet, + lastInNewSet - firstInNewSet + 1, 1); + } + } + } + + /* + * Ensures the column alignments are correct at initial loading.
+ * (child components widths are correct) + */ + WidgetUtil.runWebkitOverflowAutoFixDeferred(scrollBodyPanel + .getElement()); + + hadScrollBars = willHaveScrollbarz; + } + + /** + * Note: this method is not part of official API although declared as + * protected. Extend at your own risk. + * + * @return true if content area will have scrollbars visible. + */ + protected boolean willHaveScrollbars() { + if (isDynamicHeight()) { + if (pageLength < totalRows) { + return true; + } + } else { + int fakeheight = (int) Math.round(scrollBody.getRowHeight() + * totalRows); + int availableHeight = scrollBodyPanel.getElement().getPropertyInt( + "clientHeight"); + if (fakeheight > availableHeight) { + return true; + } + } + return false; + } + + private void announceScrollPosition() { + if (scrollPositionElement == null) { + scrollPositionElement = DOM.createDiv(); + scrollPositionElement.setClassName(getStylePrimaryName() + + "-scrollposition"); + scrollPositionElement.getStyle().setPosition(Position.ABSOLUTE); + scrollPositionElement.getStyle().setDisplay(Display.NONE); + getElement().appendChild(scrollPositionElement); + } + + Style style = scrollPositionElement.getStyle(); + style.setMarginLeft(getElement().getOffsetWidth() / 2 - 80, Unit.PX); + style.setMarginTop(-scrollBodyPanel.getOffsetHeight(), Unit.PX); + + // indexes go from 1-totalRows, as rowheaders in index-mode indicate + int last = (firstRowInViewPort + pageLength); + if (last > totalRows) { + last = totalRows; + } + scrollPositionElement.setInnerHTML("" + (firstRowInViewPort + 1) + + " – " + (last) + "..." + ""); + style.setDisplay(Display.BLOCK); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void hideScrollPositionAnnotation() { + if (scrollPositionElement != null) { + scrollPositionElement.getStyle().setDisplay(Display.NONE); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public boolean isScrollPositionVisible() { + return scrollPositionElement != null + && !scrollPositionElement.getStyle().getDisplay() + .equals(Display.NONE.toString()); + } + + /** For internal use only. May be removed or replaced in the future. */ + public class RowRequestHandler extends Timer { + + private int reqFirstRow = 0; + private int reqRows = 0; + private boolean isRequestHandlerRunning = false; + + public void triggerRowFetch(int first, int rows) { + setReqFirstRow(first); + setReqRows(rows); + deferRowFetch(); + } + + public void triggerRowFetch(int first, int rows, int delay) { + setReqFirstRow(first); + setReqRows(rows); + deferRowFetch(delay); + } + + public void deferRowFetch() { + deferRowFetch(250); + } + + public boolean isRequestHandlerRunning() { + return isRequestHandlerRunning; + } + + public void deferRowFetch(int msec) { + isRequestHandlerRunning = true; + if (reqRows > 0 && reqFirstRow < totalRows) { + schedule(msec); + + // tell scroll position to user if currently "visible" rows are + // not rendered + if (totalRows > pageLength + && ((firstRowInViewPort + pageLength > scrollBody + .getLastRendered()) || (firstRowInViewPort < scrollBody + .getFirstRendered()))) { + announceScrollPosition(); + } else { + hideScrollPositionAnnotation(); + } + } + } + + public int getReqFirstRow() { + return reqFirstRow; + } + + public void setReqFirstRow(int reqFirstRow) { + if (reqFirstRow < 0) { + this.reqFirstRow = 0; + } else if (reqFirstRow >= totalRows) { + this.reqFirstRow = totalRows - 1; + } else { + this.reqFirstRow = reqFirstRow; + } + } + + public void setReqRows(int reqRows) { + if (reqRows < 0) { + this.reqRows = 0; + } else if (reqFirstRow + reqRows > totalRows) { + this.reqRows = totalRows - reqFirstRow; + } else { + this.reqRows = reqRows; + } + } + + @Override + public void run() { + + if (client.getMessageSender().hasActiveRequest() || navKeyDown) { + // if client connection is busy, don't bother loading it more + VConsole.log("Postponed rowfetch"); + schedule(250); + } else if (allRenderedRowsAreNew() && !updatedReqRows) { + + /* + * If all rows are new, there might have been a server-side call + * to Table.setCurrentPageFirstItemIndex(int) In this case, + * scrolling event takes way too late, and all the rows from + * previous viewport to this one were requested. + * + * This should prevent requesting unneeded rows by updating + * reqFirstRow and reqRows before needing them. See (#14135) + */ + + setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate))); + int last = firstRowInViewPort + (int) (cache_rate * pageLength) + + pageLength - 1; + if (last >= totalRows) { + last = totalRows - 1; + } + setReqRows(last - getReqFirstRow() + 1); + updatedReqRows = true; + schedule(250); + + } else { + + int firstRendered = scrollBody.getFirstRendered(); + int lastRendered = scrollBody.getLastRendered(); + if (lastRendered > totalRows) { + lastRendered = totalRows - 1; + } + boolean rendered = firstRendered >= 0 && lastRendered >= 0; + + int firstToBeRendered = firstRendered; + + if (reqFirstRow < firstToBeRendered) { + firstToBeRendered = reqFirstRow; + } else if (firstRowInViewPort - (int) (cache_rate * pageLength) > firstToBeRendered) { + firstToBeRendered = firstRowInViewPort + - (int) (cache_rate * pageLength); + if (firstToBeRendered < 0) { + firstToBeRendered = 0; + } + } else if (rendered && firstRendered + 1 < reqFirstRow + && lastRendered + 1 < reqFirstRow) { + // requested rows must fall within the requested rendering + // area + firstToBeRendered = reqFirstRow; + } + if (firstToBeRendered + reqRows < firstRendered) { + // must increase the required row count accordingly, + // otherwise may leave a gap and the rows beyond will get + // removed + setReqRows(firstRendered - firstToBeRendered); + } + + int lastToBeRendered = lastRendered; + int lastReqRow = reqFirstRow + reqRows - 1; + + if (lastReqRow > lastToBeRendered) { + lastToBeRendered = lastReqRow; + } else if (firstRowInViewPort + pageLength + pageLength + * cache_rate < lastToBeRendered) { + lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * cache_rate)); + if (lastToBeRendered >= totalRows) { + lastToBeRendered = totalRows - 1; + } + // due Safari 3.1 bug (see #2607), verify reqrows, original + // problem unknown, but this should catch the issue + if (lastReqRow > lastToBeRendered) { + setReqRows(lastToBeRendered - reqFirstRow); + } + } else if (rendered && lastRendered - 1 > lastReqRow + && firstRendered - 1 > lastReqRow) { + // requested rows must fall within the requested rendering + // area + lastToBeRendered = lastReqRow; + } + + if (lastToBeRendered > totalRows) { + lastToBeRendered = totalRows - 1; + } + if (reqFirstRow < firstToBeRendered + || (reqFirstRow > firstToBeRendered && (reqFirstRow < firstRendered || reqFirstRow > lastRendered + 1))) { + setReqFirstRow(firstToBeRendered); + } + if (lastRendered < lastToBeRendered + && lastRendered + reqRows < lastToBeRendered) { + // must increase the required row count accordingly, + // otherwise may leave a gap and the rows after will get + // removed + setReqRows(lastToBeRendered - lastRendered); + } else if (lastToBeRendered >= firstRendered + && reqFirstRow + reqRows < firstRendered) { + setReqRows(lastToBeRendered - lastRendered); + } + + client.updateVariable(paintableId, "firstToBeRendered", + firstToBeRendered, false); + client.updateVariable(paintableId, "lastToBeRendered", + lastToBeRendered, false); + + // don't request server to update page first index in case it + // has not been changed + if (firstRowInViewPort != firstvisible) { + // remember which firstvisible we requested, in case the + // server has a differing opinion + lastRequestedFirstvisible = firstRowInViewPort; + client.updateVariable(paintableId, "firstvisible", + firstRowInViewPort, false); + } + + client.updateVariable(paintableId, "reqfirstrow", reqFirstRow, + false); + client.updateVariable(paintableId, "reqrows", reqRows, true); + + if (selectionChanged) { + unSyncedselectionsBeforeRowFetch = new HashSet( + selectedRowKeys); + } + isRequestHandlerRunning = false; + } + } + + /** + * Sends request to refresh content at this position. + */ + public void refreshContent() { + isRequestHandlerRunning = true; + int first = (int) (firstRowInViewPort - pageLength * cache_rate); + int reqRows = (int) (2 * pageLength * cache_rate + pageLength); + if (first < 0) { + reqRows = reqRows + first; + first = 0; + } + setReqFirstRow(first); + setReqRows(reqRows); + run(); + } + } + + public class HeaderCell extends Widget { + + Element td = DOM.createTD(); + + Element captionContainer = DOM.createDiv(); + + Element sortIndicator = DOM.createDiv(); + + Element colResizeWidget = DOM.createDiv(); + + Element floatingCopyOfHeaderCell; + + private boolean sortable = false; + private final String cid; + + private boolean dragging; + private Integer currentDragX = null; // is used to resolve #14796 + + private int dragStartX; + private int colIndex; + private int originalWidth; + + private boolean isResizing; + + private int headerX; + + private boolean moved; + + private int closestSlot; + + private int width = -1; + + private int naturalWidth = -1; + + private char align = ALIGN_LEFT; + + boolean definedWidth = false; + + private float expandRatio = 0; + + private boolean sorted; + + public void setSortable(boolean b) { + sortable = b; + /* + * Should in theory call updateStyleNames here, but that would just + * be a waste of time since this method is only called from + * updateCellsFromUIDL which immediately afterwards calls setAlign + * which also updates the style names. + */ + } + + /** + * Makes room for the sorting indicator in case the column that the + * header cell belongs to is sorted. This is done by resizing the width + * of the caption container element by the correct amount + */ + public void resizeCaptionContainer(int rightSpacing) { + int captionContainerWidth = width + - colResizeWidget.getOffsetWidth() - rightSpacing; + + if (td.getClassName().contains("-asc") + || td.getClassName().contains("-desc")) { + // Leave room for the sort indicator + captionContainerWidth -= sortIndicator.getOffsetWidth(); + } + + if (captionContainerWidth < 0) { + rightSpacing += captionContainerWidth; + captionContainerWidth = 0; + } + + captionContainer.getStyle().setPropertyPx("width", + captionContainerWidth); + + // Apply/Remove spacing if defined + if (rightSpacing > 0) { + colResizeWidget.getStyle().setMarginLeft(rightSpacing, Unit.PX); + } else { + colResizeWidget.getStyle().clearMarginLeft(); + } + } + + public void setNaturalMinimumColumnWidth(int w) { + naturalWidth = w; + } + + public HeaderCell(String colId, String headerText) { + cid = colId; + + setText(headerText); + + td.appendChild(colResizeWidget); + + // ensure no clipping initially (problem on column additions) + captionContainer.getStyle().setOverflow(Overflow.VISIBLE); + + td.appendChild(sortIndicator); + td.appendChild(captionContainer); + + DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK + | Event.ONCONTEXTMENU | Event.TOUCHEVENTS); + + setElement(td); + + setAlign(ALIGN_LEFT); + } + + protected void updateStyleNames(String primaryStyleName) { + colResizeWidget.setClassName(primaryStyleName + "-resizer"); + sortIndicator.setClassName(primaryStyleName + "-sort-indicator"); + captionContainer.setClassName(primaryStyleName + + "-caption-container"); + if (sorted) { + if (sortAscending) { + setStyleName(primaryStyleName + "-header-cell-asc"); + } else { + setStyleName(primaryStyleName + "-header-cell-desc"); + } + } else { + setStyleName(primaryStyleName + "-header-cell"); + } + + if (sortable) { + addStyleName(primaryStyleName + "-header-sortable"); + } + + final String ALIGN_PREFIX = primaryStyleName + + "-caption-container-align-"; + + switch (align) { + case ALIGN_CENTER: + captionContainer.addClassName(ALIGN_PREFIX + "center"); + break; + case ALIGN_RIGHT: + captionContainer.addClassName(ALIGN_PREFIX + "right"); + break; + default: + captionContainer.addClassName(ALIGN_PREFIX + "left"); + break; + } + + } + + public void disableAutoWidthCalculation() { + definedWidth = true; + expandRatio = 0; + } + + /** + * Sets width to the header cell. This width should not include any + * possible indent modifications that are present in + * {@link VScrollTableBody#getMaxIndent()}. + * + * @param w + * required width of the cell sans indentations + * @param ensureDefinedWidth + * disables expand ratio if required + */ + public void setWidth(int w, boolean ensureDefinedWidth) { + if (ensureDefinedWidth) { + definedWidth = true; + // on column resize expand ratio becomes zero + expandRatio = 0; + } + if (width == -1) { + // go to default mode, clip content if necessary + captionContainer.getStyle().clearOverflow(); + } + width = w; + if (w == -1) { + captionContainer.getStyle().clearWidth(); + setWidth(""); + } else { + tHead.resizeCaptionContainer(this); + + /* + * if we already have tBody, set the header width properly, if + * not defer it. IE will fail with complex float in table header + * unless TD width is not explicitly set. + */ + if (scrollBody != null) { + int maxIndent = scrollBody.getMaxIndent(); + if (w < maxIndent && isHierarchyColumn()) { + w = maxIndent; + } + int tdWidth = w + scrollBody.getCellExtraWidth(); + setWidth(tdWidth + "px"); + } else { + Scheduler.get().scheduleDeferred(new Command() { + + @Override + public void execute() { + int maxIndent = scrollBody.getMaxIndent(); + int tdWidth = width; + if (tdWidth < maxIndent && isHierarchyColumn()) { + tdWidth = maxIndent; + } + tdWidth += scrollBody.getCellExtraWidth(); + setWidth(tdWidth + "px"); + } + }); + } + } + } + + public void setUndefinedWidth() { + definedWidth = false; + if (!isResizing) { + setWidth(-1, false); + } + } + + private void setUndefinedWidthFlagOnly() { + definedWidth = false; + } + + /** + * Detects if width is fixed by developer on server side or resized to + * current width by user. + * + * @return true if defined, false if "natural" width + */ + public boolean isDefinedWidth() { + return definedWidth && width >= 0; + } + + /** + * This method exists for the needs of {@link VTreeTable} only. + * + * Returns the pixels width of the header cell. This includes the + * indent, if applicable. + * + * @return The width in pixels + */ + protected int getWidthWithIndent() { + if (scrollBody != null && isHierarchyColumn()) { + int maxIndent = scrollBody.getMaxIndent(); + if (maxIndent > width) { + return maxIndent; + } + } + return width; + } + + /** + * Returns the pixels width of the header cell. + * + * @return The width in pixels + */ + public int getWidth() { + return width; + } + + /** + * This method exists for the needs of {@link VTreeTable} only. + * + * @return true if this is hierarcyColumn's header cell, + * false otherwise + */ + private boolean isHierarchyColumn() { + int hierarchyColumnIndex = getHierarchyColumnIndex(); + return hierarchyColumnIndex >= 0 + && tHead.visibleCells.indexOf(this) == hierarchyColumnIndex; + } + + public void setText(String headerText) { + DOM.setInnerHTML(captionContainer, headerText); + } + + public String getColKey() { + return cid; + } + + private void setSorted(boolean sorted) { + this.sorted = sorted; + updateStyleNames(VScrollTable.this.getStylePrimaryName()); + } + + /** + * Handle column reordering. + */ + + @Override + public void onBrowserEvent(Event event) { + if (enabled && event != null) { + if (isResizing + || event.getEventTarget().cast() == colResizeWidget) { + if (dragging + && (event.getTypeInt() == Event.ONMOUSEUP || event + .getTypeInt() == Event.ONTOUCHEND)) { + // Handle releasing column header on spacer #5318 + handleCaptionEvent(event); + } else { + onResizeEvent(event); + } + } else { + /* + * Ensure focus before handling caption event. Otherwise + * variables changed from caption event may be before + * variables from other components that fire variables when + * they lose focus. + */ + if (event.getTypeInt() == Event.ONMOUSEDOWN + || event.getTypeInt() == Event.ONTOUCHSTART) { + scrollBodyPanel.setFocus(true); + } + handleCaptionEvent(event); + boolean stopPropagation = true; + if (event.getTypeInt() == Event.ONCONTEXTMENU + && !client.hasEventListeners(VScrollTable.this, + TableConstants.HEADER_CLICK_EVENT_ID)) { + // Prevent showing the browser's context menu only when + // there is a header click listener. + stopPropagation = false; + } + if (stopPropagation) { + event.stopPropagation(); + event.preventDefault(); + } + } + } + } + + private void createFloatingCopy() { + floatingCopyOfHeaderCell = DOM.createDiv(); + DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td)); + floatingCopyOfHeaderCell = DOM + .getChild(floatingCopyOfHeaderCell, 2); + // #12714 the shown "ghost element" should be inside + // v-overlay-container, and it should contain the same styles as the + // table to enable theming (except v-table & v-widget). + String stylePrimaryName = VScrollTable.this.getStylePrimaryName(); + StringBuilder sb = new StringBuilder(); + for (String s : VScrollTable.this.getStyleName().split(" ")) { + if (!s.equals(StyleConstants.UI_WIDGET)) { + sb.append(s); + if (s.equals(stylePrimaryName)) { + sb.append("-header-drag "); + } else { + sb.append(" "); + } + } + } + floatingCopyOfHeaderCell.setClassName(sb.toString().trim()); + // otherwise might wrap or be cut if narrow column + floatingCopyOfHeaderCell.getStyle().setProperty("width", "auto"); + updateFloatingCopysPosition(DOM.getAbsoluteLeft(td), + DOM.getAbsoluteTop(td)); + DOM.appendChild(VOverlay.getOverlayContainer(client), + floatingCopyOfHeaderCell); + } + + private void updateFloatingCopysPosition(int x, int y) { + x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell, + "offsetWidth") / 2; + floatingCopyOfHeaderCell.getStyle().setLeft(x, Unit.PX); + if (y > 0) { + floatingCopyOfHeaderCell.getStyle().setTop(y + 7, Unit.PX); + } + } + + private void hideFloatingCopy() { + floatingCopyOfHeaderCell.removeFromParent(); + floatingCopyOfHeaderCell = null; + } + + /** + * Fires a header click event after the user has clicked a column header + * cell + * + * @param event + * The click event + */ + private void fireHeaderClickedEvent(Event event) { + if (client.hasEventListeners(VScrollTable.this, + TableConstants.HEADER_CLICK_EVENT_ID)) { + MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(event); + client.updateVariable(paintableId, "headerClickEvent", + details.toString(), false); + client.updateVariable(paintableId, "headerClickCID", cid, true); + } + } + + protected void handleCaptionEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONTOUCHSTART: + case Event.ONMOUSEDOWN: + if (columnReordering + && WidgetUtil.isTouchEventOrLeftMouseButton(event)) { + if (event.getTypeInt() == Event.ONTOUCHSTART) { + /* + * prevent using this event in e.g. scrolling + */ + event.stopPropagation(); + } + dragging = true; + currentDragX = WidgetUtil.getTouchOrMouseClientX(event); + moved = false; + colIndex = getColIndexByKey(cid); + DOM.setCapture(getElement()); + headerX = tHead.getAbsoluteLeft(); + event.preventDefault(); // prevent selecting text && + // generated touch events + } + break; + case Event.ONMOUSEUP: + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + if (columnReordering + && WidgetUtil.isTouchEventOrLeftMouseButton(event)) { + dragging = false; + currentDragX = null; + DOM.releaseCapture(getElement()); + + if (WidgetUtil.isTouchEvent(event)) { + /* + * Prevent using in e.g. scrolling and prevent generated + * events. + */ + event.preventDefault(); + event.stopPropagation(); + } + if (moved) { + hideFloatingCopy(); + tHead.removeSlotFocus(); + if (closestSlot != colIndex + && closestSlot != (colIndex + 1)) { + if (closestSlot > colIndex) { + reOrderColumn(cid, closestSlot - 1); + } else { + reOrderColumn(cid, closestSlot); + } + } + moved = false; + break; + } + } + + if (!moved) { + // mouse event was a click to header -> sort column + if (sortable + && WidgetUtil.isTouchEventOrLeftMouseButton(event)) { + if (sortColumn.equals(cid)) { + // just toggle order + client.updateVariable(paintableId, "sortascending", + !sortAscending, false); + } else { + // set table sorted by this column + client.updateVariable(paintableId, "sortcolumn", + cid, false); + } + // get also cache columns at the same request + scrollBodyPanel.setScrollPosition(0); + firstvisible = 0; + rowRequestHandler.setReqFirstRow(0); + rowRequestHandler.setReqRows((int) (2 * pageLength + * cache_rate + pageLength)); + rowRequestHandler.deferRowFetch(); // some validation + + // defer 250ms + rowRequestHandler.cancel(); // instead of waiting + rowRequestHandler.run(); // run immediately + } + fireHeaderClickedEvent(event); + if (WidgetUtil.isTouchEvent(event)) { + /* + * Prevent using in e.g. scrolling and prevent generated + * events. + */ + event.preventDefault(); + event.stopPropagation(); + } + break; + } + break; + case Event.ONDBLCLICK: + fireHeaderClickedEvent(event); + break; + case Event.ONTOUCHMOVE: + case Event.ONMOUSEMOVE: + // only start the drag if the mouse / touch has moved a minimum + // distance in x-axis (the same idea as in #13381) + int currentX = WidgetUtil.getTouchOrMouseClientX(event); + + if (currentDragX == null + || Math.abs(currentDragX - currentX) > VDragAndDropManager.MINIMUM_DISTANCE_TO_START_DRAG) { + if (dragging + && WidgetUtil.isTouchEventOrLeftMouseButton(event)) { + if (event.getTypeInt() == Event.ONTOUCHMOVE) { + /* + * prevent using this event in e.g. scrolling + */ + event.stopPropagation(); + } + if (!moved) { + createFloatingCopy(); + moved = true; + } + + final int clientX = WidgetUtil + .getTouchOrMouseClientX(event); + final int x = clientX + + tHead.hTableWrapper.getScrollLeft(); + int slotX = headerX; + closestSlot = colIndex; + int closestDistance = -1; + int start = 0; + if (showRowHeaders) { + start++; + } + final int visibleCellCount = tHead + .getVisibleCellCount(); + for (int i = start; i <= visibleCellCount; i++) { + if (i > 0) { + final String colKey = getColKeyByIndex(i - 1); + // getColWidth only returns the internal width + // without padding, not the offset width of the + // whole td (#10890) + slotX += getColWidth(colKey) + + scrollBody.getCellExtraWidth(); + } + final int dist = Math.abs(x - slotX); + if (closestDistance == -1 || dist < closestDistance) { + closestDistance = dist; + closestSlot = i; + } + } + tHead.focusSlot(closestSlot); + + updateFloatingCopysPosition(clientX, -1); + } + } + break; + default: + break; + } + } + + private void onResizeEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONMOUSEDOWN: + if (!WidgetUtil.isTouchEventOrLeftMouseButton(event)) { + return; + } + isResizing = true; + DOM.setCapture(getElement()); + dragStartX = DOM.eventGetClientX(event); + colIndex = getColIndexByKey(cid); + originalWidth = getWidthWithIndent(); + DOM.eventPreventDefault(event); + break; + case Event.ONMOUSEUP: + if (!WidgetUtil.isTouchEventOrLeftMouseButton(event)) { + return; + } + isResizing = false; + DOM.releaseCapture(getElement()); + tHead.disableAutoColumnWidthCalculation(this); + + // Ensure last header cell is taking into account possible + // column selector + HeaderCell lastCell = tHead.getHeaderCell(tHead + .getVisibleCellCount() - 1); + tHead.resizeCaptionContainer(lastCell); + triggerLazyColumnAdjustment(true); + + fireColumnResizeEvent(cid, originalWidth, getColWidth(cid)); + break; + case Event.ONMOUSEMOVE: + if (!WidgetUtil.isTouchEventOrLeftMouseButton(event)) { + return; + } + if (isResizing) { + final int deltaX = DOM.eventGetClientX(event) - dragStartX; + if (deltaX == 0) { + return; + } + tHead.disableAutoColumnWidthCalculation(this); + + int newWidth = originalWidth + deltaX; + // get min width with indent, no padding + int minWidth = getMinWidth(true, false); + if (newWidth < minWidth) { + // already includes indent if any + newWidth = minWidth; + } + setColWidth(colIndex, newWidth, true); + triggerLazyColumnAdjustment(false); + forceRealignColumnHeaders(); + } + break; + default: + break; + } + } + + /** + * Returns the smallest possible cell width in pixels. + * + * @param includeIndent + * - width should include hierarchy column indent if + * applicable (VTreeTable only) + * @param includeCellExtraWidth + * - width should include paddings etc. + * @return + */ + private int getMinWidth(boolean includeIndent, + boolean includeCellExtraWidth) { + int minWidth = sortIndicator.getOffsetWidth(); + if (scrollBody != null) { + // check the need for indent before adding paddings etc. + if (includeIndent && isHierarchyColumn()) { + int maxIndent = scrollBody.getMaxIndent(); + if (minWidth < maxIndent) { + minWidth = maxIndent; + } + } + if (includeCellExtraWidth) { + minWidth += scrollBody.getCellExtraWidth(); + } + } + return minWidth; + } + + public int getMinWidth() { + // get min width with padding, no indent + return getMinWidth(false, true); + } + + public String getCaption() { + return DOM.getInnerText(captionContainer); + } + + public boolean isEnabled() { + return getParent() != null; + } + + public void setAlign(char c) { + align = c; + updateStyleNames(VScrollTable.this.getStylePrimaryName()); + } + + public char getAlign() { + return align; + } + + /** + * Saves natural column width if it hasn't been saved already. + * + * @param columnIndex + * @since 7.3.9 + */ + protected void saveNaturalColumnWidthIfNotSaved(int columnIndex) { + if (naturalWidth < 0) { + // This is recently revealed column. Try to detect a proper + // value (greater of header and data columns) + + int hw = captionContainer.getOffsetWidth() + getHeaderPadding(); + if (BrowserInfo.get().isGecko()) { + hw += sortIndicator.getOffsetWidth(); + } + if (columnIndex < 0) { + columnIndex = 0; + for (Iterator it = tHead.iterator(); it.hasNext(); columnIndex++) { + if (it.next() == this) { + break; + } + } + } + final int cw = scrollBody.getColWidth(columnIndex); + naturalWidth = (hw > cw ? hw : cw); + } + } + + /** + * Detects the natural minimum width for the column of this header cell. + * If column is resized by user or the width is defined by server the + * actual width is returned. Else the natural min width is returned. + * + * @param columnIndex + * column index hint, if -1 (unknown) it will be detected + * + * @return + */ + public int getNaturalColumnWidth(int columnIndex) { + final int iw = columnIndex == getHierarchyColumnIndex() ? scrollBody + .getMaxIndent() : 0; + saveNaturalColumnWidthIfNotSaved(columnIndex); + if (isDefinedWidth()) { + if (iw > width) { + return iw; + } + return width; + } else { + if (iw > naturalWidth) { + // indent is temporary value, naturalWidth shouldn't be + // updated + return iw; + } else { + return naturalWidth; + } + } + } + + public void setExpandRatio(float floatAttribute) { + if (floatAttribute != expandRatio) { + triggerLazyColumnAdjustment(false); + } + expandRatio = floatAttribute; + } + + public float getExpandRatio() { + return expandRatio; + } + + public boolean isSorted() { + return sorted; + } + } + + /** + * HeaderCell that is header cell for row headers. + * + * Reordering disabled and clicking on it resets sorting. + */ + public class RowHeadersHeaderCell extends HeaderCell { + + RowHeadersHeaderCell() { + super(ROW_HEADER_COLUMN_KEY, ""); + updateStyleNames(VScrollTable.this.getStylePrimaryName()); + } + + @Override + protected void updateStyleNames(String primaryStyleName) { + super.updateStyleNames(primaryStyleName); + setStyleName(primaryStyleName + "-header-cell-rowheader"); + } + + @Override + protected void handleCaptionEvent(Event event) { + // NOP: RowHeaders cannot be reordered + // TODO It'd be nice to reset sorting here + } + } + + public class TableHead extends Panel implements ActionOwner { + + private static final int WRAPPER_WIDTH = 900000; + + ArrayList visibleCells = new ArrayList(); + + HashMap availableCells = new HashMap(); + + Element div = DOM.createDiv(); + Element hTableWrapper = DOM.createDiv(); + Element hTableContainer = DOM.createDiv(); + Element table = DOM.createTable(); + Element headerTableBody = DOM.createTBody(); + Element tr = DOM.createTR(); + + private final Element columnSelector = DOM.createDiv(); + + private int focusedSlot = -1; + + public TableHead() { + if (BrowserInfo.get().isIE()) { + table.setPropertyInt("cellSpacing", 0); + } + + hTableWrapper.getStyle().setOverflow(Overflow.HIDDEN); + columnSelector.getStyle().setDisplay(Display.NONE); + + DOM.appendChild(table, headerTableBody); + DOM.appendChild(headerTableBody, tr); + DOM.appendChild(hTableContainer, table); + DOM.appendChild(hTableWrapper, hTableContainer); + DOM.appendChild(div, hTableWrapper); + DOM.appendChild(div, columnSelector); + setElement(div); + + DOM.sinkEvents(columnSelector, Event.ONCLICK); + + availableCells.put(ROW_HEADER_COLUMN_KEY, + new RowHeadersHeaderCell()); + } + + protected void updateStyleNames(String primaryStyleName) { + hTableWrapper.setClassName(primaryStyleName + "-header"); + columnSelector.setClassName(primaryStyleName + "-column-selector"); + setStyleName(primaryStyleName + "-header-wrap"); + for (HeaderCell c : availableCells.values()) { + c.updateStyleNames(primaryStyleName); + } + } + + public void resizeCaptionContainer(HeaderCell cell) { + HeaderCell lastcell = getHeaderCell(visibleCells.size() - 1); + int columnSelectorOffset = columnSelector.getOffsetWidth(); + + if (cell == lastcell && columnSelectorOffset > 0 + && !hasVerticalScrollbar()) { + + // Measure column widths + int columnTotalWidth = 0; + for (Widget w : visibleCells) { + int cellExtraWidth = w.getOffsetWidth(); + if (scrollBody != null + && visibleCells.indexOf(w) == getHierarchyColumnIndex() + && cellExtraWidth < scrollBody.getMaxIndent()) { + // indent must be taken into consideration even if it + // hasn't been applied yet + columnTotalWidth += scrollBody.getMaxIndent(); + } else { + columnTotalWidth += cellExtraWidth; + } + } + + int divOffset = div.getOffsetWidth(); + if (columnTotalWidth >= divOffset - columnSelectorOffset) { + /* + * Ensure column caption is visible when placed under the + * column selector widget by shifting and resizing the + * caption. + */ + int offset = 0; + int diff = divOffset - columnTotalWidth; + if (diff < columnSelectorOffset && diff > 0) { + /* + * If the difference is less than the column selectors + * width then just offset by the difference + */ + offset = columnSelectorOffset - diff; + } else { + // Else offset by the whole column selector + offset = columnSelectorOffset; + } + lastcell.resizeCaptionContainer(offset); + } else { + cell.resizeCaptionContainer(0); + } + } else { + cell.resizeCaptionContainer(0); + } + } + + @Override + public void clear() { + for (String cid : availableCells.keySet()) { + removeCell(cid); + } + availableCells.clear(); + availableCells.put(ROW_HEADER_COLUMN_KEY, + new RowHeadersHeaderCell()); + } + + public void updateCellsFromUIDL(UIDL uidl) { + Iterator it = uidl.getChildIterator(); + HashSet updated = new HashSet(); + boolean refreshContentWidths = initializedAndAttached + && hadScrollBars != willHaveScrollbars(); + while (it.hasNext()) { + final UIDL col = (UIDL) it.next(); + final String cid = col.getStringAttribute("cid"); + updated.add(cid); + + String caption = buildCaptionHtmlSnippet(col); + HeaderCell c = getHeaderCell(cid); + if (c == null) { + c = new HeaderCell(cid, caption); + availableCells.put(cid, c); + if (initializedAndAttached) { + // we will need a column width recalculation + initializedAndAttached = false; + initialContentReceived = false; + isNewBody = true; + } + } else { + c.setText(caption); + if (BrowserInfo.get().isIE10()) { + // IE10 can some times define min-height to include + // padding when setting the text... + // See https://dev.vaadin.com/ticket/15169 + WidgetUtil.forceIERedraw(c.getElement()); + } + } + + c.setSorted(false); + if (col.hasAttribute("sortable")) { + c.setSortable(true); + } else { + c.setSortable(false); + } + + // The previous call to setSortable relies on c.setAlign calling + // c.updateStyleNames + if (col.hasAttribute("align")) { + c.setAlign(col.getStringAttribute("align").charAt(0)); + } else { + c.setAlign(ALIGN_LEFT); + + } + if (col.hasAttribute("width") && !c.isResizing) { + // Make sure to accomodate for the sort indicator if + // necessary. + int width = col.getIntAttribute("width"); + int widthWithoutAddedIndent = width; + + // get min width with indent, no padding + int minWidth = c.getMinWidth(true, false); + if (width < minWidth) { + width = minWidth; + } + if (scrollBody != null && width != c.getWidthWithIndent()) { + // Do a more thorough update if a column is resized from + // the server *after* the header has been properly + // initialized + final int newWidth = width; + Scheduler.get().scheduleFinally(new ScheduledCommand() { + + @Override + public void execute() { + final int colIx = getColIndexByKey(cid); + setColWidth(colIx, newWidth, true); + } + }); + refreshContentWidths = true; + } else { + // get min width with no indent or padding + minWidth = c.getMinWidth(false, false); + if (widthWithoutAddedIndent < minWidth) { + widthWithoutAddedIndent = minWidth; + } + // save min width without indent + c.setWidth(widthWithoutAddedIndent, true); + } + } else if (col.hasAttribute("er")) { + c.setExpandRatio(col.getFloatAttribute("er")); + c.setUndefinedWidthFlagOnly(); + } else if (recalcWidths) { + c.setUndefinedWidth(); + + } else { + boolean hadExpandRatio = c.getExpandRatio() > 0; + boolean hadDefinedWidth = c.isDefinedWidth(); + if (hadExpandRatio || hadDefinedWidth) { + // Someone has removed a expand width or the defined + // width on the server side (setting it to -1), make the + // column undefined again and measure columns again. + c.setUndefinedWidth(); + c.setExpandRatio(0); + refreshContentWidths = true; + } + } + + if (col.hasAttribute("collapsed")) { + // ensure header is properly removed from parent (case when + // collapsing happens via servers side api) + if (c.isAttached()) { + c.removeFromParent(); + headerChangedDuringUpdate = true; + } + } + } + + if (refreshContentWidths) { + // Recalculate the column sizings if any column has changed + Scheduler.get().scheduleFinally(new ScheduledCommand() { + + @Override + public void execute() { + triggerLazyColumnAdjustment(true); + } + }); + } + + // check for orphaned header cells + for (Iterator cit = availableCells.keySet().iterator(); cit + .hasNext();) { + String cid = cit.next(); + if (!updated.contains(cid)) { + removeCell(cid); + cit.remove(); + // we will need a column width recalculation, since columns + // with expand ratios should expand to fill the void. + initializedAndAttached = false; + initialContentReceived = false; + isNewBody = true; + } + } + } + + public void enableColumn(String cid, int index) { + final HeaderCell c = getHeaderCell(cid); + if (!c.isEnabled() || getHeaderCell(index) != c) { + setHeaderCell(index, c); + if (initializedAndAttached) { + headerChangedDuringUpdate = true; + } + } + } + + public int getVisibleCellCount() { + return visibleCells.size(); + } + + public void setHorizontalScrollPosition(int scrollLeft) { + hTableWrapper.setScrollLeft(scrollLeft); + } + + public void setColumnCollapsingAllowed(boolean cc) { + if (cc) { + columnSelector.getStyle().setDisplay(Display.BLOCK); + } else { + columnSelector.getStyle().setDisplay(Display.NONE); + } + } + + public void disableBrowserIntelligence() { + hTableContainer.getStyle().setWidth(WRAPPER_WIDTH, Unit.PX); + } + + public void enableBrowserIntelligence() { + hTableContainer.getStyle().clearWidth(); + } + + public void setHeaderCell(int index, HeaderCell cell) { + if (cell.isEnabled()) { + // we're moving the cell + DOM.removeChild(tr, cell.getElement()); + orphan(cell); + visibleCells.remove(cell); + } + if (index < visibleCells.size()) { + // insert to right slot + DOM.insertChild(tr, cell.getElement(), index); + adopt(cell); + visibleCells.add(index, cell); + } else if (index == visibleCells.size()) { + // simply append + DOM.appendChild(tr, cell.getElement()); + adopt(cell); + visibleCells.add(cell); + } else { + throw new RuntimeException( + "Header cells must be appended in order"); + } + } + + public HeaderCell getHeaderCell(int index) { + if (index >= 0 && index < visibleCells.size()) { + return (HeaderCell) visibleCells.get(index); + } else { + return null; + } + } + + /** + * Get's HeaderCell by it's column Key. + * + * Note that this returns HeaderCell even if it is currently collapsed. + * + * @param cid + * Column key of accessed HeaderCell + * @return HeaderCell + */ + public HeaderCell getHeaderCell(String cid) { + return availableCells.get(cid); + } + + public void moveCell(int oldIndex, int newIndex) { + final HeaderCell hCell = getHeaderCell(oldIndex); + final Element cell = hCell.getElement(); + + visibleCells.remove(oldIndex); + DOM.removeChild(tr, cell); + + DOM.insertChild(tr, cell, newIndex); + visibleCells.add(newIndex, hCell); + } + + @Override + public Iterator iterator() { + return visibleCells.iterator(); + } + + @Override + public boolean remove(Widget w) { + if (visibleCells.contains(w)) { + visibleCells.remove(w); + orphan(w); + DOM.removeChild(DOM.getParent(w.getElement()), w.getElement()); + return true; + } + return false; + } + + public void removeCell(String colKey) { + final HeaderCell c = getHeaderCell(colKey); + remove(c); + } + + private void focusSlot(int index) { + removeSlotFocus(); + if (index > 0) { + Element child = tr.getChild(index - 1).getFirstChild().cast(); + child.setClassName(VScrollTable.this.getStylePrimaryName() + + "-resizer"); + child.addClassName(VScrollTable.this.getStylePrimaryName() + + "-focus-slot-right"); + } else { + Element child = tr.getChild(index).getFirstChild().cast(); + child.setClassName(VScrollTable.this.getStylePrimaryName() + + "-resizer"); + child.addClassName(VScrollTable.this.getStylePrimaryName() + + "-focus-slot-left"); + } + focusedSlot = index; + } + + private void removeSlotFocus() { + if (focusedSlot < 0) { + return; + } + if (focusedSlot == 0) { + Element child = tr.getChild(focusedSlot).getFirstChild().cast(); + child.setClassName(VScrollTable.this.getStylePrimaryName() + + "-resizer"); + } else if (focusedSlot > 0) { + Element child = tr.getChild(focusedSlot - 1).getFirstChild() + .cast(); + child.setClassName(VScrollTable.this.getStylePrimaryName() + + "-resizer"); + } + focusedSlot = -1; + } + + @Override + public void onBrowserEvent(Event event) { + if (enabled) { + if (event.getEventTarget().cast() == columnSelector) { + final int left = DOM.getAbsoluteLeft(columnSelector); + final int top = DOM.getAbsoluteTop(columnSelector) + + DOM.getElementPropertyInt(columnSelector, + "offsetHeight"); + client.getContextMenu().showAt(this, left, top); + } + } + } + + @Override + protected void onDetach() { + super.onDetach(); + if (client != null) { + client.getContextMenu().ensureHidden(this); + } + } + + class VisibleColumnAction extends Action { + + String colKey; + private boolean collapsed; + private boolean noncollapsible = false; + private VScrollTableRow currentlyFocusedRow; + + public VisibleColumnAction(String colKey) { + super(VScrollTable.TableHead.this); + this.colKey = colKey; + caption = tHead.getHeaderCell(colKey).getCaption(); + currentlyFocusedRow = focusedRow; + } + + @Override + public void execute() { + if (noncollapsible) { + return; + } + client.getContextMenu().hide(); + // toggle selected column + if (collapsedColumns.contains(colKey)) { + collapsedColumns.remove(colKey); + } else { + tHead.removeCell(colKey); + collapsedColumns.add(colKey); + triggerLazyColumnAdjustment(true); + } + + // update variable to server + client.updateVariable(paintableId, "collapsedcolumns", + collapsedColumns.toArray(new String[collapsedColumns + .size()]), false); + // let rowRequestHandler determine proper rows + rowRequestHandler.refreshContent(); + lazyRevertFocusToRow(currentlyFocusedRow); + } + + public void setCollapsed(boolean b) { + collapsed = b; + } + + public void setNoncollapsible(boolean b) { + noncollapsible = b; + } + + /** + * Override default method to distinguish on/off columns + */ + + @Override + public String getHTML() { + final StringBuffer buf = new StringBuffer(); + buf.append(""); + + buf.append(super.getHTML()); + buf.append(""); + + return buf.toString(); + } + + } + + /* + * Returns columns as Action array for column select popup + */ + + @Override + public Action[] getActions() { + Object[] cols; + if (columnReordering && columnOrder != null) { + cols = columnOrder; + } else { + // if columnReordering is disabled, we need different way to get + // all available columns + cols = visibleColOrder; + cols = new Object[visibleColOrder.length + + collapsedColumns.size()]; + int i; + for (i = 0; i < visibleColOrder.length; i++) { + cols[i] = visibleColOrder[i]; + } + for (final Iterator it = collapsedColumns.iterator(); it + .hasNext();) { + cols[i++] = it.next(); + } + } + List actions = new ArrayList(cols.length); + + for (int i = 0; i < cols.length; i++) { + final String cid = (String) cols[i]; + boolean noncollapsible = noncollapsibleColumns.contains(cid); + + if (noncollapsible + && collapsibleMenuContent == CollapseMenuContent.COLLAPSIBLE_COLUMNS) { + continue; + } + + final HeaderCell c = getHeaderCell(cid); + final VisibleColumnAction a = new VisibleColumnAction( + c.getColKey()); + a.setCaption(c.getCaption()); + if (!c.isEnabled()) { + a.setCollapsed(true); + } + if (noncollapsible) { + a.setNoncollapsible(true); + } + actions.add(a); + } + return actions.toArray(new Action[actions.size()]); + } + + @Override + public ApplicationConnection getClient() { + return client; + } + + @Override + public String getPaintableId() { + return paintableId; + } + + /** + * Returns column alignments for visible columns + */ + public char[] getColumnAlignments() { + final Iterator it = visibleCells.iterator(); + final char[] aligns = new char[visibleCells.size()]; + int colIndex = 0; + while (it.hasNext()) { + aligns[colIndex++] = ((HeaderCell) it.next()).getAlign(); + } + return aligns; + } + + /** + * Disables the automatic calculation of all column widths by forcing + * the widths to be "defined" thus turning off expand ratios and such. + */ + public void disableAutoColumnWidthCalculation(HeaderCell source) { + for (HeaderCell cell : availableCells.values()) { + cell.disableAutoWidthCalculation(); + } + // fire column resize events for all columns but the source of the + // resize action, since an event will fire separately for this. + ArrayList columns = new ArrayList( + availableCells.values()); + columns.remove(source); + sendColumnWidthUpdates(columns); + forceRealignColumnHeaders(); + } + } + + /** + * A cell in the footer + */ + public class FooterCell extends Widget { + private final Element td = DOM.createTD(); + private final Element captionContainer = DOM.createDiv(); + private char align = ALIGN_LEFT; + private int width = -1; + private float expandRatio = 0; + private final String cid; + boolean definedWidth = false; + private int naturalWidth = -1; + + public FooterCell(String colId, String headerText) { + cid = colId; + + setText(headerText); + + // ensure no clipping initially (problem on column additions) + captionContainer.getStyle().setOverflow(Overflow.VISIBLE); + + DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS); + + DOM.appendChild(td, captionContainer); + + DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK + | Event.ONCONTEXTMENU); + + setElement(td); + + updateStyleNames(VScrollTable.this.getStylePrimaryName()); + } + + protected void updateStyleNames(String primaryStyleName) { + captionContainer.setClassName(primaryStyleName + + "-footer-container"); + } + + /** + * Sets the text of the footer + * + * @param footerText + * The text in the footer + */ + public void setText(String footerText) { + if (footerText == null || footerText.equals("")) { + footerText = " "; + } + + DOM.setInnerHTML(captionContainer, footerText); + } + + /** + * Set alignment of the text in the cell + * + * @param c + * The alignment which can be ALIGN_CENTER, ALIGN_LEFT, + * ALIGN_RIGHT + */ + public void setAlign(char c) { + if (align != c) { + switch (c) { + case ALIGN_CENTER: + captionContainer.getStyle().setTextAlign(TextAlign.CENTER); + break; + case ALIGN_RIGHT: + captionContainer.getStyle().setTextAlign(TextAlign.RIGHT); + break; + default: + captionContainer.getStyle().setTextAlign(TextAlign.LEFT); + break; + } + } + align = c; + } + + /** + * Get the alignment of the text int the cell + * + * @return Returns either ALIGN_CENTER, ALIGN_LEFT or ALIGN_RIGHT + */ + public char getAlign() { + return align; + } + + /** + * Sets the width of the cell. This width should not include any + * possible indent modifications that are present in + * {@link VScrollTableBody#getMaxIndent()}. + * + * @param w + * The width of the cell + * @param ensureDefinedWidth + * Ensures that the given width is not recalculated + */ + public void setWidth(int w, boolean ensureDefinedWidth) { + + if (ensureDefinedWidth) { + definedWidth = true; + // on column resize expand ratio becomes zero + expandRatio = 0; + } + if (width == w) { + return; + } + if (width == -1) { + // go to default mode, clip content if necessary + captionContainer.getStyle().clearOverflow(); + } + width = w; + if (w == -1) { + captionContainer.getStyle().clearWidth(); + setWidth(""); + } else { + /* + * Reduce width with one pixel for the right border since the + * footers does not have any spacers between them. + */ + final int borderWidths = 1; + + // Set the container width (check for negative value) + captionContainer.getStyle().setPropertyPx("width", + Math.max(w - borderWidths, 0)); + + /* + * if we already have tBody, set the header width properly, if + * not defer it. IE will fail with complex float in table header + * unless TD width is not explicitly set. + */ + if (scrollBody != null) { + int maxIndent = scrollBody.getMaxIndent(); + if (w < maxIndent + && tFoot.visibleCells.indexOf(this) == getHierarchyColumnIndex()) { + // ensure there's room for the indent + w = maxIndent; + } + int tdWidth = w + scrollBody.getCellExtraWidth() + - borderWidths; + setWidth(Math.max(tdWidth, 0) + "px"); + } else { + Scheduler.get().scheduleDeferred(new Command() { + + @Override + public void execute() { + int tdWidth = width; + int maxIndent = scrollBody.getMaxIndent(); + if (tdWidth < maxIndent + && tFoot.visibleCells.indexOf(this) == getHierarchyColumnIndex()) { + // ensure there's room for the indent + tdWidth = maxIndent; + } + tdWidth += scrollBody.getCellExtraWidth() + - borderWidths; + setWidth(Math.max(tdWidth, 0) + "px"); + } + }); + } + } + } + + /** + * Sets the width to undefined + */ + public void setUndefinedWidth() { + definedWidth = false; + setWidth(-1, false); + } + + /** + * Detects if width is fixed by developer on server side or resized to + * current width by user. + * + * @return true if defined, false if "natural" width + */ + public boolean isDefinedWidth() { + return definedWidth && width >= 0; + } + + /** + * Returns the pixels width of the footer cell. + * + * @return The width in pixels + */ + public int getWidth() { + return width; + } + + /** + * Sets the expand ratio of the cell + * + * @param floatAttribute + * The expand ratio + */ + public void setExpandRatio(float floatAttribute) { + expandRatio = floatAttribute; + } + + /** + * Returns the expand ratio of the cell + * + * @return The expand ratio + */ + public float getExpandRatio() { + return expandRatio; + } + + /** + * Is the cell enabled? + * + * @return True if enabled else False + */ + public boolean isEnabled() { + return getParent() != null; + } + + /** + * Handle column clicking + */ + + @Override + public void onBrowserEvent(Event event) { + if (enabled && event != null) { + handleCaptionEvent(event); + + if (DOM.eventGetType(event) == Event.ONMOUSEUP) { + scrollBodyPanel.setFocus(true); + } + boolean stopPropagation = true; + if (event.getTypeInt() == Event.ONCONTEXTMENU + && !client.hasEventListeners(VScrollTable.this, + TableConstants.FOOTER_CLICK_EVENT_ID)) { + // Show browser context menu if a footer click listener is + // not present + stopPropagation = false; + } + if (stopPropagation) { + event.stopPropagation(); + event.preventDefault(); + } + } + } + + /** + * Handles a event on the captions + * + * @param event + * The event to handle + */ + protected void handleCaptionEvent(Event event) { + if (event.getTypeInt() == Event.ONMOUSEUP + || event.getTypeInt() == Event.ONDBLCLICK) { + fireFooterClickedEvent(event); + } + } + + /** + * Fires a footer click event after the user has clicked a column footer + * cell + * + * @param event + * The click event + */ + private void fireFooterClickedEvent(Event event) { + if (client.hasEventListeners(VScrollTable.this, + TableConstants.FOOTER_CLICK_EVENT_ID)) { + MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(event); + client.updateVariable(paintableId, "footerClickEvent", + details.toString(), false); + client.updateVariable(paintableId, "footerClickCID", cid, true); + } + } + + /** + * Returns the column key of the column + * + * @return The column key + */ + public String getColKey() { + return cid; + } + + /** + * Saves natural column width if it hasn't been saved already. + * + * @param columnIndex + * @since 7.3.9 + */ + protected void saveNaturalColumnWidthIfNotSaved(int columnIndex) { + if (naturalWidth < 0) { + // This is recently revealed column. Try to detect a proper + // value (greater of header and data cols) + + final int hw = ((Element) getElement().getLastChild()) + .getOffsetWidth() + getHeaderPadding(); + if (columnIndex < 0) { + columnIndex = 0; + for (Iterator it = tHead.iterator(); it.hasNext(); columnIndex++) { + if (it.next() == this) { + break; + } + } + } + final int cw = scrollBody.getColWidth(columnIndex); + naturalWidth = (hw > cw ? hw : cw); + } + } + + /** + * Detects the natural minimum width for the column of this header cell. + * If column is resized by user or the width is defined by server the + * actual width is returned. Else the natural min width is returned. + * + * @param columnIndex + * column index hint, if -1 (unknown) it will be detected + * + * @return + */ + public int getNaturalColumnWidth(int columnIndex) { + final int iw = columnIndex == getHierarchyColumnIndex() ? scrollBody + .getMaxIndent() : 0; + saveNaturalColumnWidthIfNotSaved(columnIndex); + if (isDefinedWidth()) { + if (iw > width) { + return iw; + } + return width; + } else { + if (iw > naturalWidth) { + return iw; + } else { + return naturalWidth; + } + } + } + + public void setNaturalMinimumColumnWidth(int w) { + naturalWidth = w; + } + } + + /** + * HeaderCell that is header cell for row headers. + * + * Reordering disabled and clicking on it resets sorting. + */ + public class RowHeadersFooterCell extends FooterCell { + + RowHeadersFooterCell() { + super(ROW_HEADER_COLUMN_KEY, ""); + } + + @Override + protected void handleCaptionEvent(Event event) { + // NOP: RowHeaders cannot be reordered + // TODO It'd be nice to reset sorting here + } + } + + /** + * The footer of the table which can be seen in the bottom of the Table. + */ + public class TableFooter extends Panel { + + private static final int WRAPPER_WIDTH = 900000; + + ArrayList visibleCells = new ArrayList(); + HashMap availableCells = new HashMap(); + + Element div = DOM.createDiv(); + Element hTableWrapper = DOM.createDiv(); + Element hTableContainer = DOM.createDiv(); + Element table = DOM.createTable(); + Element headerTableBody = DOM.createTBody(); + Element tr = DOM.createTR(); + + public TableFooter() { + + hTableWrapper.getStyle().setOverflow(Overflow.HIDDEN); + + DOM.appendChild(table, headerTableBody); + DOM.appendChild(headerTableBody, tr); + DOM.appendChild(hTableContainer, table); + DOM.appendChild(hTableWrapper, hTableContainer); + DOM.appendChild(div, hTableWrapper); + setElement(div); + + availableCells.put(ROW_HEADER_COLUMN_KEY, + new RowHeadersFooterCell()); + + updateStyleNames(VScrollTable.this.getStylePrimaryName()); + } + + protected void updateStyleNames(String primaryStyleName) { + hTableWrapper.setClassName(primaryStyleName + "-footer"); + setStyleName(primaryStyleName + "-footer-wrap"); + for (FooterCell c : availableCells.values()) { + c.updateStyleNames(primaryStyleName); + } + } + + @Override + public void clear() { + for (String cid : availableCells.keySet()) { + removeCell(cid); + } + availableCells.clear(); + availableCells.put(ROW_HEADER_COLUMN_KEY, + new RowHeadersFooterCell()); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client + * .ui.Widget) + */ + + @Override + public boolean remove(Widget w) { + if (visibleCells.contains(w)) { + visibleCells.remove(w); + orphan(w); + DOM.removeChild(DOM.getParent(w.getElement()), w.getElement()); + return true; + } + return false; + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.HasWidgets#iterator() + */ + + @Override + public Iterator iterator() { + return visibleCells.iterator(); + } + + /** + * Gets a footer cell which represents the given columnId + * + * @param cid + * The columnId + * + * @return The cell + */ + public FooterCell getFooterCell(String cid) { + return availableCells.get(cid); + } + + /** + * Gets a footer cell by using a column index + * + * @param index + * The index of the column + * @return The Cell + */ + public FooterCell getFooterCell(int index) { + if (index < visibleCells.size()) { + return (FooterCell) visibleCells.get(index); + } else { + return null; + } + } + + /** + * Updates the cells contents when updateUIDL request is received + * + * @param uidl + * The UIDL + */ + public void updateCellsFromUIDL(UIDL uidl) { + Iterator columnIterator = uidl.getChildIterator(); + HashSet updated = new HashSet(); + while (columnIterator.hasNext()) { + final UIDL col = (UIDL) columnIterator.next(); + final String cid = col.getStringAttribute("cid"); + updated.add(cid); + + String caption = col.hasAttribute("fcaption") ? col + .getStringAttribute("fcaption") : ""; + FooterCell c = getFooterCell(cid); + if (c == null) { + c = new FooterCell(cid, caption); + availableCells.put(cid, c); + if (initializedAndAttached) { + // we will need a column width recalculation + initializedAndAttached = false; + initialContentReceived = false; + isNewBody = true; + } + } else { + c.setText(caption); + } + + if (col.hasAttribute("align")) { + c.setAlign(col.getStringAttribute("align").charAt(0)); + } else { + c.setAlign(ALIGN_LEFT); + + } + if (col.hasAttribute("width")) { + if (scrollBody == null || isNewBody) { + // Already updated by setColWidth called from + // TableHeads.updateCellsFromUIDL in case of a server + // side resize + final int width = col.getIntAttribute("width"); + c.setWidth(width, true); + } + } else if (recalcWidths) { + c.setUndefinedWidth(); + } + if (col.hasAttribute("er")) { + c.setExpandRatio(col.getFloatAttribute("er")); + } + if (col.hasAttribute("collapsed")) { + // ensure header is properly removed from parent (case when + // collapsing happens via servers side api) + if (c.isAttached()) { + c.removeFromParent(); + headerChangedDuringUpdate = true; + } + } + } + + // check for orphaned header cells + for (Iterator cit = availableCells.keySet().iterator(); cit + .hasNext();) { + String cid = cit.next(); + if (!updated.contains(cid)) { + removeCell(cid); + cit.remove(); + } + } + } + + /** + * Set a footer cell for a specified column index + * + * @param index + * The index + * @param cell + * The footer cell + */ + public void setFooterCell(int index, FooterCell cell) { + if (cell.isEnabled()) { + // we're moving the cell + DOM.removeChild(tr, cell.getElement()); + orphan(cell); + visibleCells.remove(cell); + } + if (index < visibleCells.size()) { + // insert to right slot + DOM.insertChild(tr, cell.getElement(), index); + adopt(cell); + visibleCells.add(index, cell); + } else if (index == visibleCells.size()) { + // simply append + DOM.appendChild(tr, cell.getElement()); + adopt(cell); + visibleCells.add(cell); + } else { + throw new RuntimeException( + "Header cells must be appended in order"); + } + } + + /** + * Remove a cell by using the columnId + * + * @param colKey + * The columnId to remove + */ + public void removeCell(String colKey) { + final FooterCell c = getFooterCell(colKey); + remove(c); + } + + /** + * Enable a column (Sets the footer cell) + * + * @param cid + * The columnId + * @param index + * The index of the column + */ + public void enableColumn(String cid, int index) { + final FooterCell c = getFooterCell(cid); + if (!c.isEnabled() || getFooterCell(index) != c) { + setFooterCell(index, c); + if (initializedAndAttached) { + headerChangedDuringUpdate = true; + } + } + } + + /** + * Disable browser measurement of the table width + */ + public void disableBrowserIntelligence() { + hTableContainer.getStyle().setWidth(WRAPPER_WIDTH, Unit.PX); + } + + /** + * Enable browser measurement of the table width + */ + public void enableBrowserIntelligence() { + hTableContainer.getStyle().clearWidth(); + } + + /** + * Set the horizontal position in the cell in the footer. This is done + * when a horizontal scrollbar is present. + * + * @param scrollLeft + * The value of the leftScroll + */ + public void setHorizontalScrollPosition(int scrollLeft) { + hTableWrapper.setScrollLeft(scrollLeft); + } + + /** + * Swap cells when the column are dragged + * + * @param oldIndex + * The old index of the cell + * @param newIndex + * The new index of the cell + */ + public void moveCell(int oldIndex, int newIndex) { + final FooterCell hCell = getFooterCell(oldIndex); + final Element cell = hCell.getElement(); + + visibleCells.remove(oldIndex); + DOM.removeChild(tr, cell); + + DOM.insertChild(tr, cell, newIndex); + visibleCells.add(newIndex, hCell); + } + } + + /** + * This Panel can only contain VScrollTableRow type of widgets. This + * "simulates" very large table, keeping spacers which take room of + * unrendered rows. + * + */ + public class VScrollTableBody extends Panel { + + public static final int DEFAULT_ROW_HEIGHT = 24; + + private double rowHeight = -1; + + private final LinkedList renderedRows = new LinkedList(); + + /** + * Due some optimizations row height measuring is deferred and initial + * set of rows is rendered detached. Flag set on when table body has + * been attached in dom and rowheight has been measured. + */ + private boolean tBodyMeasurementsDone = false; + + Element preSpacer = DOM.createDiv(); + Element postSpacer = DOM.createDiv(); + + Element container = DOM.createDiv(); + + TableSectionElement tBodyElement = Document.get().createTBodyElement(); + Element table = DOM.createTable(); + + private int firstRendered; + private int lastRendered; + + private char[] aligns; + + protected VScrollTableBody() { + constructDOM(); + setElement(container); + } + + public void setLastRendered(int lastRendered) { + if (totalRows >= 0 && lastRendered > totalRows) { + VConsole.log("setLastRendered: " + this.lastRendered + " -> " + + lastRendered); + this.lastRendered = totalRows - 1; + } else { + this.lastRendered = lastRendered; + } + } + + public int getLastRendered() { + + return lastRendered; + } + + public int getFirstRendered() { + + return firstRendered; + } + + public VScrollTableRow getRowByRowIndex(int indexInTable) { + int internalIndex = indexInTable - firstRendered; + if (internalIndex >= 0 && internalIndex < renderedRows.size()) { + return (VScrollTableRow) renderedRows.get(internalIndex); + } else { + return null; + } + } + + /** + * @return the height of scrollable body, subpixels ceiled. + */ + public int getRequiredHeight() { + return preSpacer.getOffsetHeight() + postSpacer.getOffsetHeight() + + WidgetUtil.getRequiredHeight(table); + } + + private void constructDOM() { + if (BrowserInfo.get().isIE()) { + table.setPropertyInt("cellSpacing", 0); + } + + table.appendChild(tBodyElement); + DOM.appendChild(container, preSpacer); + DOM.appendChild(container, table); + DOM.appendChild(container, postSpacer); + if (BrowserInfo.get().requiresTouchScrollDelegate()) { + NodeList childNodes = container.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Element item = (Element) childNodes.getItem(i); + item.getStyle().setProperty("webkitTransform", + "translate3d(0,0,0)"); + } + } + updateStyleNames(VScrollTable.this.getStylePrimaryName()); + } + + protected void updateStyleNames(String primaryStyleName) { + table.setClassName(primaryStyleName + "-table"); + preSpacer.setClassName(primaryStyleName + "-row-spacer"); + postSpacer.setClassName(primaryStyleName + "-row-spacer"); + for (Widget w : renderedRows) { + VScrollTableRow row = (VScrollTableRow) w; + row.updateStyleNames(primaryStyleName); + } + } + + public int getAvailableWidth() { + int availW = scrollBodyPanel.getOffsetWidth() - getBorderWidth(); + return availW; + } + + public void renderInitialRows(UIDL rowData, int firstIndex, int rows) { + firstRendered = firstIndex; + setLastRendered(firstIndex + rows - 1); + final Iterator it = rowData.getChildIterator(); + aligns = tHead.getColumnAlignments(); + while (it.hasNext()) { + final VScrollTableRow row = createRow((UIDL) it.next(), aligns); + addRow(row); + } + if (isAttached()) { + fixSpacers(); + } + } + + public void renderRows(UIDL rowData, int firstIndex, int rows) { + // FIXME REVIEW + aligns = tHead.getColumnAlignments(); + final Iterator it = rowData.getChildIterator(); + if (firstIndex == lastRendered + 1) { + while (it.hasNext()) { + final VScrollTableRow row = prepareRow((UIDL) it.next()); + addRow(row); + setLastRendered(lastRendered + 1); + } + fixSpacers(); + } else if (firstIndex + rows == firstRendered) { + final VScrollTableRow[] rowArray = new VScrollTableRow[rows]; + int i = rows; + + while (it.hasNext()) { + i--; + rowArray[i] = prepareRow((UIDL) it.next()); + } + + for (i = 0; i < rows; i++) { + addRowBeforeFirstRendered(rowArray[i]); + firstRendered--; + } + } else { + // completely new set of rows + + // there can't be sanity checks for last rendered within this + // while loop regardless of what has been set previously, so + // change it temporarily to true and then return the original + // value + boolean temp = postponeSanityCheckForLastRendered; + postponeSanityCheckForLastRendered = true; + while (lastRendered + 1 > firstRendered) { + unlinkRow(false); + } + postponeSanityCheckForLastRendered = temp; + VScrollTableRow row = prepareRow((UIDL) it.next()); + firstRendered = firstIndex; + setLastRendered(firstIndex - 1); + addRow(row); + setLastRendered(lastRendered + 1); + setContainerHeight(); + fixSpacers(); + + while (it.hasNext()) { + addRow(prepareRow((UIDL) it.next())); + setLastRendered(lastRendered + 1); + } + + fixSpacers(); + } + + // this may be a new set of rows due content change, + // ensure we have proper cache rows + ensureCacheFilled(); + } + + /** + * Ensure we have the correct set of rows on client side, e.g. if the + * content on the server side has changed, or the client scroll position + * has changed since the last request. + */ + protected void ensureCacheFilled() { + + /** + * Fixes cache issue #13576 where unnecessary rows are fetched + */ + if (isLazyScrollerActive()) { + return; + } + + int reactFirstRow = (int) (firstRowInViewPort - pageLength + * cache_react_rate); + int reactLastRow = (int) (firstRowInViewPort + pageLength + pageLength + * cache_react_rate); + if (reactFirstRow < 0) { + reactFirstRow = 0; + } + if (reactLastRow >= totalRows) { + reactLastRow = totalRows - 1; + } + if (lastRendered < reactFirstRow || firstRendered > reactLastRow) { + /* + * #8040 - scroll position is completely changed since the + * latest request, so request a new set of rows. + * + * TODO: We should probably check whether the fetched rows match + * the current scroll position right when they arrive, so as to + * not waste time rendering a set of rows that will never be + * visible... + */ + rowRequestHandler.triggerRowFetch(reactFirstRow, reactLastRow + - reactFirstRow + 1, 1); + } else if (lastRendered < reactLastRow) { + // get some cache rows below visible area + rowRequestHandler.triggerRowFetch(lastRendered + 1, + reactLastRow - lastRendered, 1); + } else if (firstRendered > reactFirstRow) { + /* + * Branch for fetching cache above visible area. + * + * If cache needed for both before and after visible area, this + * will be rendered after-cache is received and rendered. So in + * some rare situations the table may make two cache visits to + * server. + */ + rowRequestHandler.triggerRowFetch(reactFirstRow, firstRendered + - reactFirstRow, 1); + } + } + + /** + * Inserts rows as provided in the rowData starting at firstIndex. + * + * @param rowData + * @param firstIndex + * @param rows + * the number of rows + * @return a list of the rows added. + */ + protected List insertRows(UIDL rowData, + int firstIndex, int rows) { + aligns = tHead.getColumnAlignments(); + final Iterator it = rowData.getChildIterator(); + List insertedRows = new ArrayList(); + + if (firstIndex == lastRendered + 1) { + while (it.hasNext()) { + final VScrollTableRow row = prepareRow((UIDL) it.next()); + addRow(row); + insertedRows.add(row); + if (postponeSanityCheckForLastRendered) { + lastRendered++; + } else { + setLastRendered(lastRendered + 1); + } + } + fixSpacers(); + } else if (firstIndex + rows == firstRendered) { + final VScrollTableRow[] rowArray = new VScrollTableRow[rows]; + int i = rows; + while (it.hasNext()) { + i--; + rowArray[i] = prepareRow((UIDL) it.next()); + } + for (i = 0; i < rows; i++) { + addRowBeforeFirstRendered(rowArray[i]); + insertedRows.add(rowArray[i]); + firstRendered--; + } + } else { + // insert in the middle + int ix = firstIndex; + while (it.hasNext()) { + VScrollTableRow row = prepareRow((UIDL) it.next()); + insertRowAt(row, ix); + insertedRows.add(row); + if (postponeSanityCheckForLastRendered) { + lastRendered++; + } else { + setLastRendered(lastRendered + 1); + } + ix++; + } + fixSpacers(); + } + return insertedRows; + } + + protected List insertAndReindexRows(UIDL rowData, + int firstIndex, int rows) { + List inserted = insertRows(rowData, firstIndex, + rows); + int actualIxOfFirstRowAfterInserted = firstIndex + rows + - firstRendered; + for (int ix = actualIxOfFirstRowAfterInserted; ix < renderedRows + .size(); ix++) { + VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix); + r.setIndex(r.getIndex() + rows); + } + setContainerHeight(); + return inserted; + } + + protected void insertRowsDeleteBelow(UIDL rowData, int firstIndex, + int rows) { + unlinkAllRowsStartingAt(firstIndex); + insertRows(rowData, firstIndex, rows); + setContainerHeight(); + } + + /** + * This method is used to instantiate new rows for this table. It + * automatically sets correct widths to rows cells and assigns correct + * client reference for child widgets. + * + * This method can be called only after table has been initialized + * + * @param uidl + */ + private VScrollTableRow prepareRow(UIDL uidl) { + final VScrollTableRow row = createRow(uidl, aligns); + row.initCellWidths(); + return row; + } + + protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) { + if (uidl.hasAttribute("gen_html")) { + // This is a generated row. + return new VScrollTableGeneratedRow(uidl, aligns2); + } + return new VScrollTableRow(uidl, aligns2); + } + + private void addRowBeforeFirstRendered(VScrollTableRow row) { + row.setIndex(firstRendered - 1); + if (row.isSelected()) { + row.addStyleName("v-selected"); + } + tBodyElement.insertBefore(row.getElement(), + tBodyElement.getFirstChild()); + adopt(row); + renderedRows.add(0, row); + } + + private void addRow(VScrollTableRow row) { + row.setIndex(firstRendered + renderedRows.size()); + if (row.isSelected()) { + row.addStyleName("v-selected"); + } + tBodyElement.appendChild(row.getElement()); + // Add to renderedRows before adopt so iterator() will return also + // this row if called in an attach handler (#9264) + renderedRows.add(row); + adopt(row); + } + + private void insertRowAt(VScrollTableRow row, int index) { + row.setIndex(index); + if (row.isSelected()) { + row.addStyleName("v-selected"); + } + if (index > 0) { + VScrollTableRow sibling = getRowByRowIndex(index - 1); + tBodyElement + .insertAfter(row.getElement(), sibling.getElement()); + } else { + VScrollTableRow sibling = getRowByRowIndex(index); + tBodyElement.insertBefore(row.getElement(), + sibling.getElement()); + } + adopt(row); + int actualIx = index - firstRendered; + renderedRows.add(actualIx, row); + } + + @Override + public Iterator iterator() { + return renderedRows.iterator(); + } + + /** + * @return false if couldn't remove row + */ + protected boolean unlinkRow(boolean fromBeginning) { + if (lastRendered - firstRendered < 0) { + return false; + } + int actualIx; + if (fromBeginning) { + actualIx = 0; + firstRendered++; + } else { + actualIx = renderedRows.size() - 1; + if (postponeSanityCheckForLastRendered) { + --lastRendered; + } else { + setLastRendered(lastRendered - 1); + } + } + if (actualIx >= 0) { + unlinkRowAtActualIndex(actualIx); + fixSpacers(); + return true; + } + return false; + } + + protected void unlinkRows(int firstIndex, int count) { + if (count < 1) { + return; + } + if (firstRendered > firstIndex + && firstRendered < firstIndex + count) { + count = count - (firstRendered - firstIndex); + firstIndex = firstRendered; + } + int lastIndex = firstIndex + count - 1; + if (lastRendered < lastIndex) { + lastIndex = lastRendered; + } + for (int ix = lastIndex; ix >= firstIndex; ix--) { + unlinkRowAtActualIndex(actualIndex(ix)); + if (postponeSanityCheckForLastRendered) { + // partialUpdate handles sanity check later + lastRendered--; + } else { + setLastRendered(lastRendered - 1); + } + } + fixSpacers(); + } + + protected void unlinkAndReindexRows(int firstIndex, int count) { + unlinkRows(firstIndex, count); + int actualFirstIx = firstIndex - firstRendered; + for (int ix = actualFirstIx; ix < renderedRows.size(); ix++) { + VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix); + r.setIndex(r.getIndex() - count); + } + setContainerHeight(); + } + + protected void unlinkAllRowsStartingAt(int index) { + if (firstRendered > index) { + index = firstRendered; + } + for (int ix = renderedRows.size() - 1; ix >= index; ix--) { + unlinkRowAtActualIndex(actualIndex(ix)); + setLastRendered(lastRendered - 1); + } + fixSpacers(); + } + + private int actualIndex(int index) { + return index - firstRendered; + } + + private void unlinkRowAtActualIndex(int index) { + final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows + .get(index); + tBodyElement.removeChild(toBeRemoved.getElement()); + orphan(toBeRemoved); + renderedRows.remove(index); + } + + @Override + public boolean remove(Widget w) { + throw new UnsupportedOperationException(); + } + + /** + * Fix container blocks height according to totalRows to avoid + * "bouncing" when scrolling + */ + private void setContainerHeight() { + fixSpacers(); + container.getStyle().setHeight(measureRowHeightOffset(totalRows), + Unit.PX); + } + + private void fixSpacers() { + int prepx = measureRowHeightOffset(firstRendered); + if (prepx < 0) { + prepx = 0; + } + preSpacer.getStyle().setPropertyPx("height", prepx); + int postpx; + if (pageLength == 0 && totalRows == pageLength) { + /* + * TreeTable depends on having lastRendered out of sync in some + * situations, which makes this method miss the special + * situation in which one row worth of post spacer to be added + * if there are no rows in the table. #9203 + */ + postpx = measureRowHeightOffset(1); + } else { + postpx = measureRowHeightOffset(totalRows - 1) + - measureRowHeightOffset(lastRendered); + } + + if (postpx < 0) { + postpx = 0; + } + postSpacer.getStyle().setPropertyPx("height", postpx); + } + + public double getRowHeight() { + return getRowHeight(false); + } + + public double getRowHeight(boolean forceUpdate) { + if (tBodyMeasurementsDone && !forceUpdate) { + return rowHeight; + } else { + if (tBodyElement.getRows().getLength() > 0) { + int tableHeight = getTableHeight(); + int rowCount = tBodyElement.getRows().getLength(); + rowHeight = tableHeight / (double) rowCount; + } else { + // Special cases if we can't just measure the current rows + if (!Double.isNaN(lastKnownRowHeight)) { + // Use previous value if available + if (BrowserInfo.get().isIE()) { + /* + * IE needs to reflow the table element at this + * point to work correctly (e.g. + * com.vaadin.tests.components.table. + * ContainerSizeChange) - the other code paths + * already trigger reflows, but here it must be done + * explicitly. + */ + getTableHeight(); + } + rowHeight = lastKnownRowHeight; + } else if (isAttached()) { + // measure row height by adding a dummy row + VScrollTableRow scrollTableRow = new VScrollTableRow(); + tBodyElement.appendChild(scrollTableRow.getElement()); + getRowHeight(forceUpdate); + tBodyElement.removeChild(scrollTableRow.getElement()); + } else { + // TODO investigate if this can never happen anymore + return DEFAULT_ROW_HEIGHT; + } + } + lastKnownRowHeight = rowHeight; + tBodyMeasurementsDone = true; + return rowHeight; + } + } + + public int getTableHeight() { + return table.getOffsetHeight(); + } + + /** + * Returns the width available for column content. + * + * @param columnIndex + * @return + */ + public int getColWidth(int columnIndex) { + if (tBodyMeasurementsDone) { + if (renderedRows.isEmpty()) { + // no rows yet rendered + return 0; + } + for (Widget row : renderedRows) { + if (!(row instanceof VScrollTableGeneratedRow)) { + TableRowElement tr = row.getElement().cast(); + // Spanned rows might cause an NPE. + if (columnIndex < tr.getChildCount()) { + Element wrapperdiv = tr.getCells() + .getItem(columnIndex) + .getFirstChildElement().cast(); + return wrapperdiv.getOffsetWidth(); + } + } + } + return 0; + } else { + return 0; + } + } + + /** + * Sets the content width of a column. + * + * Due IE limitation, we must set the width to a wrapper elements inside + * table cells (with overflow hidden, which does not work on td + * elements). + * + * To get this work properly crossplatform, we will also set the width + * of td. + * + * @param colIndex + * @param w + */ + public void setColWidth(int colIndex, int w) { + for (Widget row : renderedRows) { + ((VScrollTableRow) row).setCellWidth(colIndex, w); + } + } + + private int cellExtraWidth = -1; + + /** + * Method to return the space used for cell paddings + border. + */ + private int getCellExtraWidth() { + if (cellExtraWidth < 0) { + detectExtrawidth(); + } + return cellExtraWidth; + } + + /** + * This method exists for the needs of {@link VTreeTable} only. May be + * removed or replaced in the future.

Returns the maximum + * indent of the hierarcyColumn, if applicable. + * + * @see {@link VScrollTable#getHierarchyColumnIndex()} + * + * @return maximum indent in pixels + */ + protected int getMaxIndent() { + return 0; + } + + /** + * This method exists for the needs of {@link VTreeTable} only. May be + * removed or replaced in the future.

Calculates the maximum + * indent of the hierarcyColumn, if applicable. + */ + protected void calculateMaxIndent() { + // NOP + } + + private void detectExtrawidth() { + NodeList rows = tBodyElement.getRows(); + if (rows.getLength() == 0) { + /* need to temporary add empty row and detect */ + VScrollTableRow scrollTableRow = new VScrollTableRow(); + scrollTableRow.updateStyleNames(VScrollTable.this + .getStylePrimaryName()); + tBodyElement.appendChild(scrollTableRow.getElement()); + detectExtrawidth(); + tBodyElement.removeChild(scrollTableRow.getElement()); + } else { + boolean noCells = false; + TableRowElement item = rows.getItem(0); + TableCellElement firstTD = item.getCells().getItem(0); + if (firstTD == null) { + // content is currently empty, we need to add a fake cell + // for measuring + noCells = true; + VScrollTableRow next = (VScrollTableRow) iterator().next(); + boolean sorted = tHead.getHeaderCell(0) != null ? tHead + .getHeaderCell(0).isSorted() : false; + next.addCell(null, "", ALIGN_LEFT, "", true, sorted); + firstTD = item.getCells().getItem(0); + } + com.google.gwt.dom.client.Element wrapper = firstTD + .getFirstChildElement(); + cellExtraWidth = firstTD.getOffsetWidth() + - wrapper.getOffsetWidth(); + if (noCells) { + firstTD.getParentElement().removeChild(firstTD); + } + } + } + + public void moveCol(int oldIndex, int newIndex) { + + // loop all rows and move given index to its new place + final Iterator rows = iterator(); + while (rows.hasNext()) { + final VScrollTableRow row = (VScrollTableRow) rows.next(); + + final Element td = DOM.getChild(row.getElement(), oldIndex); + if (td != null) { + DOM.removeChild(row.getElement(), td); + + DOM.insertChild(row.getElement(), td, newIndex); + } + } + + } + + /** + * Restore row visibility which is set to "none" when the row is + * rendered (due a performance optimization). + */ + private void restoreRowVisibility() { + for (Widget row : renderedRows) { + row.getElement().getStyle().setProperty("visibility", ""); + } + } + + public int indexOf(Widget row) { + int relIx = -1; + for (int ix = 0; ix < renderedRows.size(); ix++) { + if (renderedRows.get(ix) == row) { + relIx = ix; + break; + } + } + if (relIx >= 0) { + return firstRendered + relIx; + } + return -1; + } + + public class VScrollTableRow extends Panel implements ActionOwner, + ContextMenuOwner { + + private static final int TOUCHSCROLL_TIMEOUT = 100; + private static final int DRAGMODE_MULTIROW = 2; + protected ArrayList childWidgets = new ArrayList(); + private boolean selected = false; + protected final int rowKey; + + private String[] actionKeys = null; + private final TableRowElement rowElement; + private int index; + private Event touchStart; + + private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500; + private Timer contextTouchTimeout; + private Timer dragTouchTimeout; + private int touchStartY; + private int touchStartX; + + private TouchContextProvider touchContextProvider = new TouchContextProvider( + this); + + private TooltipInfo tooltipInfo = null; + private Map cellToolTips = new HashMap(); + private boolean isDragging = false; + private String rowStyle = null; + protected boolean applyZeroWidthFix = true; + + private VScrollTableRow(int rowKey) { + this.rowKey = rowKey; + rowElement = Document.get().createTRElement(); + setElement(rowElement); + DOM.sinkEvents(getElement(), Event.MOUSEEVENTS + | Event.TOUCHEVENTS | Event.ONDBLCLICK + | Event.ONCONTEXTMENU | VTooltip.TOOLTIP_EVENTS); + } + + public VScrollTableRow(UIDL uidl, char[] aligns) { + this(uidl.getIntAttribute("key")); + + /* + * Rendering the rows as hidden improves Firefox and Safari + * performance drastically. + */ + getElement().getStyle().setProperty("visibility", "hidden"); + + rowStyle = uidl.getStringAttribute("rowstyle"); + updateStyleNames(VScrollTable.this.getStylePrimaryName()); + + String rowDescription = uidl.getStringAttribute("rowdescr"); + if (rowDescription != null && !rowDescription.equals("")) { + tooltipInfo = new TooltipInfo(rowDescription, null, this); + } else { + tooltipInfo = null; + } + + tHead.getColumnAlignments(); + int col = 0; + int visibleColumnIndex = -1; + + // row header + if (showRowHeaders) { + boolean sorted = tHead.getHeaderCell(col).isSorted(); + addCell(uidl, buildCaptionHtmlSnippet(uidl), aligns[col++], + "rowheader", true, sorted); + visibleColumnIndex++; + } + + if (uidl.hasAttribute("al")) { + actionKeys = uidl.getStringArrayAttribute("al"); + } + + addCellsFromUIDL(uidl, aligns, col, visibleColumnIndex); + + if (uidl.hasAttribute("selected") && !isSelected()) { + toggleSelection(); + } + } + + protected void updateStyleNames(String primaryStyleName) { + + if (getStylePrimaryName().contains("odd")) { + setStyleName(primaryStyleName + "-row-odd"); + } else { + setStyleName(primaryStyleName + "-row"); + } + + if (rowStyle != null) { + addStyleName(primaryStyleName + "-row-" + rowStyle); + } + + for (int i = 0; i < rowElement.getChildCount(); i++) { + TableCellElement cell = (TableCellElement) rowElement + .getChild(i); + updateCellStyleNames(cell, primaryStyleName); + } + } + + public TooltipInfo getTooltipInfo() { + return tooltipInfo; + } + + /** + * Add a dummy row, used for measurements if Table is empty. + */ + public VScrollTableRow() { + this(0); + addCell(null, "_", 'b', "", true, false); + } + + protected void initCellWidths() { + final int cells = tHead.getVisibleCellCount(); + for (int i = 0; i < cells; i++) { + int w = VScrollTable.this.getColWidth(getColKeyByIndex(i)); + if (w < 0) { + w = 0; + } + setCellWidth(i, w); + } + } + + protected void setCellWidth(int cellIx, int width) { + final Element cell = DOM.getChild(getElement(), cellIx); + Style wrapperStyle = cell.getFirstChildElement().getStyle(); + int wrapperWidth = width; + if (BrowserInfo.get().isWebkit() + || BrowserInfo.get().isOpera10()) { + /* + * Some versions of Webkit and Opera ignore the width + * definition of zero width table cells. Instead, use 1px + * and compensate with a negative margin. + */ + if (applyZeroWidthFix && width == 0) { + wrapperWidth = 1; + wrapperStyle.setMarginRight(-1, Unit.PX); + } else { + wrapperStyle.clearMarginRight(); + } + } + wrapperStyle.setPropertyPx("width", wrapperWidth); + cell.getStyle().setPropertyPx("width", width); + } + + protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, + int visibleColumnIndex) { + final Iterator cells = uidl.getChildIterator(); + while (cells.hasNext()) { + final Object cell = cells.next(); + visibleColumnIndex++; + + String columnId = visibleColOrder[visibleColumnIndex]; + + String style = ""; + if (uidl.hasAttribute("style-" + columnId)) { + style = uidl.getStringAttribute("style-" + columnId); + } + + String description = null; + if (uidl.hasAttribute("descr-" + columnId)) { + description = uidl.getStringAttribute("descr-" + + columnId); + } + + boolean sorted = tHead.getHeaderCell(col).isSorted(); + if (cell instanceof String) { + addCell(uidl, cell.toString(), aligns[col++], style, + isRenderHtmlInCells(), sorted, description); + } else { + final ComponentConnector cellContent = client + .getPaintable((UIDL) cell); + + addCell(uidl, cellContent.getWidget(), aligns[col++], + style, sorted, description); + } + } + } + + /** + * Overriding this and returning true causes all text cells to be + * rendered as HTML. + * + * @return always returns false in the default implementation + */ + protected boolean isRenderHtmlInCells() { + return false; + } + + /** + * Detects whether row is visible in tables viewport. + * + * @return + */ + public boolean isInViewPort() { + int absoluteTop = getAbsoluteTop(); + int absoluteBottom = absoluteTop + getOffsetHeight(); + int viewPortTop = scrollBodyPanel.getAbsoluteTop(); + int viewPortBottom = viewPortTop + + scrollBodyPanel.getOffsetHeight(); + return absoluteBottom > viewPortTop + && absoluteTop < viewPortBottom; + } + + /** + * Makes a check based on indexes whether the row is before the + * compared row. + * + * @param row1 + * @return true if this rows index is smaller than in the row1 + */ + public boolean isBefore(VScrollTableRow row1) { + return getIndex() < row1.getIndex(); + } + + /** + * Sets the index of the row in the whole table. Currently used just + * to set even/odd classname + * + * @param indexInWholeTable + */ + private void setIndex(int indexInWholeTable) { + index = indexInWholeTable; + boolean isOdd = indexInWholeTable % 2 == 0; + // Inverted logic to be backwards compatible with earlier 6.4. + // It is very strange because rows 1,3,5 are considered "even" + // and 2,4,6 "odd". + // + // First remove any old styles so that both styles aren't + // applied when indexes are updated. + String primaryStyleName = getStylePrimaryName(); + if (primaryStyleName != null && !primaryStyleName.equals("")) { + removeStyleName(getStylePrimaryName()); + } + if (!isOdd) { + addStyleName(VScrollTable.this.getStylePrimaryName() + + "-row-odd"); + } else { + addStyleName(VScrollTable.this.getStylePrimaryName() + + "-row"); + } + } + + public int getIndex() { + return index; + } + + @Override + protected void onDetach() { + super.onDetach(); + client.getContextMenu().ensureHidden(this); + } + + public String getKey() { + return String.valueOf(rowKey); + } + + public void addCell(UIDL rowUidl, String text, char align, + String style, boolean textIsHTML, boolean sorted) { + addCell(rowUidl, text, align, style, textIsHTML, sorted, null); + } + + public void addCell(UIDL rowUidl, String text, char align, + String style, boolean textIsHTML, boolean sorted, + String description) { + // String only content is optimized by not using Label widget + final TableCellElement td = DOM.createTD().cast(); + initCellWithText(text, align, style, textIsHTML, sorted, + description, td); + } + + protected void initCellWithText(String text, char align, + String style, boolean textIsHTML, boolean sorted, + String description, final TableCellElement td) { + final Element container = DOM.createDiv(); + container.setClassName(VScrollTable.this.getStylePrimaryName() + + "-cell-wrapper"); + + td.setClassName(VScrollTable.this.getStylePrimaryName() + + "-cell-content"); + + if (style != null && !style.equals("")) { + td.addClassName(VScrollTable.this.getStylePrimaryName() + + "-cell-content-" + style); + } + + if (sorted) { + td.addClassName(VScrollTable.this.getStylePrimaryName() + + "-cell-content-sorted"); + } + + if (textIsHTML) { + container.setInnerHTML(text); + } else { + container.setInnerText(text); + } + setAlign(align, container); + setTooltip(td, description); + + td.appendChild(container); + getElement().appendChild(td); + } + + protected void updateCellStyleNames(TableCellElement td, + String primaryStyleName) { + Element container = td.getFirstChild().cast(); + container.setClassName(primaryStyleName + "-cell-wrapper"); + + /* + * Replace old primary style name with new one + */ + String className = td.getClassName(); + String oldPrimaryName = className.split("-cell-content")[0]; + td.setClassName(className.replaceAll(oldPrimaryName, + primaryStyleName)); + } + + public void addCell(UIDL rowUidl, Widget w, char align, + String style, boolean sorted, String description) { + final TableCellElement td = DOM.createTD().cast(); + initCellWithWidget(w, align, style, sorted, td); + setTooltip(td, description); + } + + private void setTooltip(TableCellElement td, String description) { + if (description != null && !description.equals("")) { + TooltipInfo info = new TooltipInfo(description, null, this); + cellToolTips.put(td, info); + } else { + cellToolTips.remove(td); + } + + } + + private void setAlign(char align, final Element container) { + switch (align) { + case ALIGN_CENTER: + container.getStyle().setProperty("textAlign", "center"); + break; + case ALIGN_LEFT: + container.getStyle().setProperty("textAlign", "left"); + break; + case ALIGN_RIGHT: + default: + container.getStyle().setProperty("textAlign", "right"); + break; + } + } + + protected void initCellWithWidget(Widget w, char align, + String style, boolean sorted, final TableCellElement td) { + final Element container = DOM.createDiv(); + String className = VScrollTable.this.getStylePrimaryName() + + "-cell-content"; + if (style != null && !style.equals("")) { + className += " " + VScrollTable.this.getStylePrimaryName() + + "-cell-content-" + style; + } + if (sorted) { + className += " " + VScrollTable.this.getStylePrimaryName() + + "-cell-content-sorted"; + } + td.setClassName(className); + container.setClassName(VScrollTable.this.getStylePrimaryName() + + "-cell-wrapper"); + setAlign(align, container); + td.appendChild(container); + getElement().appendChild(td); + // ensure widget not attached to another element (possible tBody + // change) + w.removeFromParent(); + container.appendChild(w.getElement()); + adopt(w); + childWidgets.add(w); + } + + @Override + public Iterator iterator() { + return childWidgets.iterator(); + } + + @Override + public boolean remove(Widget w) { + if (childWidgets.contains(w)) { + orphan(w); + DOM.removeChild(DOM.getParent(w.getElement()), + w.getElement()); + childWidgets.remove(w); + return true; + } else { + return false; + } + } + + /** + * If there are registered click listeners, sends a click event and + * returns true. Otherwise, does nothing and returns false. + * + * @param event + * @param targetTdOrTr + * @param immediate + * Whether the event is sent immediately + * @return Whether a click event was sent + */ + private boolean handleClickEvent(Event event, Element targetTdOrTr, + boolean immediate) { + if (!client.hasEventListeners(VScrollTable.this, + TableConstants.ITEM_CLICK_EVENT_ID)) { + // Don't send an event if nobody is listening + return false; + } + + // This row was clicked + client.updateVariable(paintableId, "clickedKey", "" + rowKey, + false); + + if (getElement() == targetTdOrTr.getParentElement()) { + // A specific column was clicked + int childIndex = DOM.getChildIndex(getElement(), + targetTdOrTr); + String colKey = null; + colKey = tHead.getHeaderCell(childIndex).getColKey(); + client.updateVariable(paintableId, "clickedColKey", colKey, + false); + } + + MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(event); + + client.updateVariable(paintableId, "clickEvent", + details.toString(), immediate); + + return true; + } + + public TooltipInfo getTooltip( + com.google.gwt.dom.client.Element target) { + + TooltipInfo info = null; + final Element targetTdOrTr = getTdOrTr(target); + if (targetTdOrTr != null + && "td".equals(targetTdOrTr.getTagName().toLowerCase())) { + TableCellElement td = (TableCellElement) targetTdOrTr + .cast(); + info = cellToolTips.get(td); + } + + if (info == null) { + info = tooltipInfo; + } + + return info; + } + + private Element getTdOrTr(Element target) { + Element thisTrElement = getElement(); + if (target == thisTrElement) { + // This was a on the TR element + return target; + } + + // Iterate upwards until we find the TR element + Element element = target; + while (element != null + && element.getParentElement() != thisTrElement) { + element = element.getParentElement(); + } + return element; + } + + /** + * Special handler for touch devices that support native scrolling + * + * @return Whether the event was handled by this method. + */ + private boolean handleTouchEvent(final Event event) { + + boolean touchEventHandled = false; + + if (enabled && hasNativeTouchScrolling) { + touchContextProvider.handleTouchEvent(event); + + final Element targetTdOrTr = getEventTargetTdOrTr(event); + final int type = event.getTypeInt(); + + switch (type) { + case Event.ONTOUCHSTART: + touchEventHandled = true; + touchStart = event; + isDragging = false; + Touch touch = event.getChangedTouches().get(0); + // save position to fields, touches in events are same + // instance during the operation. + touchStartX = touch.getClientX(); + touchStartY = touch.getClientY(); + + if (dragmode != 0) { + if (dragTouchTimeout == null) { + dragTouchTimeout = new Timer() { + + @Override + public void run() { + if (touchStart != null) { + // Start a drag if a finger is held + // in place long enough, then moved + isDragging = true; + } + } + }; + } + dragTouchTimeout.schedule(TOUCHSCROLL_TIMEOUT); + } + + if (actionKeys != null) { + if (contextTouchTimeout == null) { + contextTouchTimeout = new Timer() { + + @Override + public void run() { + if (touchStart != null) { + // Open the context menu if finger + // is held in place long enough. + showContextMenu(touchStart); + event.preventDefault(); + touchStart = null; + } + } + }; + } + contextTouchTimeout + .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); + event.stopPropagation(); + } + break; + case Event.ONTOUCHMOVE: + touchEventHandled = true; + if (isSignificantMove(event)) { + if (contextTouchTimeout != null) { + // Moved finger before the context menu timer + // expired, so let the browser handle this as a + // scroll. + contextTouchTimeout.cancel(); + contextTouchTimeout = null; + } + if (!isDragging && dragTouchTimeout != null) { + // Moved finger before the drag timer expired, + // so let the browser handle this as a scroll. + dragTouchTimeout.cancel(); + dragTouchTimeout = null; + } + + if (dragmode != 0 && touchStart != null + && isDragging) { + event.preventDefault(); + event.stopPropagation(); + startRowDrag(touchStart, type, targetTdOrTr); + } + touchStart = null; + } + break; + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + touchEventHandled = true; + if (contextTouchTimeout != null) { + contextTouchTimeout.cancel(); + } + if (dragTouchTimeout != null) { + dragTouchTimeout.cancel(); + } + if (touchStart != null) { + if (!BrowserInfo.get().isAndroid()) { + event.preventDefault(); + WidgetUtil.simulateClickFromTouchEvent( + touchStart, this); + } + event.stopPropagation(); + touchStart = null; + } + isDragging = false; + break; + } + } + return touchEventHandled; + } + + /* + * React on click that occur on content cells only + */ + + @Override + public void onBrowserEvent(final Event event) { + + final boolean touchEventHandled = handleTouchEvent(event); + + if (enabled && !touchEventHandled) { + final int type = event.getTypeInt(); + final Element targetTdOrTr = getEventTargetTdOrTr(event); + if (type == Event.ONCONTEXTMENU) { + showContextMenu(event); + if (enabled + && (actionKeys != null || client + .hasEventListeners( + VScrollTable.this, + TableConstants.ITEM_CLICK_EVENT_ID))) { + /* + * Prevent browser context menu only if there are + * action handlers or item click listeners + * registered + */ + event.stopPropagation(); + event.preventDefault(); + } + return; + } + + boolean targetCellOrRowFound = targetTdOrTr != null; + + switch (type) { + case Event.ONDBLCLICK: + if (targetCellOrRowFound) { + handleClickEvent(event, targetTdOrTr, true); + } + break; + case Event.ONMOUSEUP: + /* + * Only fire a click if the mouseup hits the same + * element as the corresponding mousedown. This is first + * checked in the event preview but we can't fire the + * event there as the event might get canceled before it + * gets here. + */ + if (mouseUpPreviewMatched + && lastMouseDownTarget != null + && lastMouseDownTarget == getElementTdOrTr(WidgetUtil + .getElementUnderMouse(event))) { + // "Click" with left, right or middle button + + if (targetCellOrRowFound) { + /* + * Queue here, send at the same time as the + * corresponding value change event - see #7127 + */ + boolean clickEventSent = handleClickEvent( + event, targetTdOrTr, false); + + if (event.getButton() == Event.BUTTON_LEFT + && isSelectable()) { + + // Ctrl+Shift click + if ((event.getCtrlKey() || event + .getMetaKey()) + && event.getShiftKey() + && isMultiSelectModeDefault()) { + toggleShiftSelection(false); + setRowFocus(this); + + // Ctrl click + } else if ((event.getCtrlKey() || event + .getMetaKey()) + && isMultiSelectModeDefault()) { + boolean wasSelected = isSelected(); + toggleSelection(); + setRowFocus(this); + /* + * next possible range select must start + * on this row + */ + selectionRangeStart = this; + if (wasSelected) { + removeRowFromUnsentSelectionRanges(this); + } + + } else if ((event.getCtrlKey() || event + .getMetaKey()) + && isSingleSelectMode()) { + // Ctrl (or meta) click (Single + // selection) + if (!isSelected() + || (isSelected() && nullSelectionAllowed)) { + + if (!isSelected()) { + deselectAll(); + } + + toggleSelection(); + setRowFocus(this); + } + + } else if (event.getShiftKey() + && isMultiSelectModeDefault()) { + // Shift click + toggleShiftSelection(true); + + } else { + // click + boolean currentlyJustThisRowSelected = selectedRowKeys + .size() == 1 + && selectedRowKeys + .contains(getKey()); + + if (!currentlyJustThisRowSelected) { + if (isSingleSelectMode() + || isMultiSelectModeDefault()) { + /* + * For default multi select mode + * (ctrl/shift) and for single + * select mode we need to clear + * the previous selection before + * selecting a new one when the + * user clicks on a row. Only in + * multiselect/simple mode the + * old selection should remain + * after a normal click. + */ + deselectAll(); + } + toggleSelection(); + } else if ((isSingleSelectMode() || isMultiSelectModeSimple()) + && nullSelectionAllowed) { + toggleSelection(); + }/* + * else NOP to avoid excessive server + * visits (selection is removed with + * CTRL/META click) + */ + + selectionRangeStart = this; + setRowFocus(this); + } + + // Remove IE text selection hack + if (BrowserInfo.get().isIE()) { + ((Element) event.getEventTarget() + .cast()).setPropertyJSO( + "onselectstart", null); + } + // Queue value change + sendSelectedRows(false); + } + /* + * Send queued click and value change events if + * any If a click event is sent, send value + * change with it regardless of the immediate + * flag, see #7127 + */ + if (immediate || clickEventSent) { + client.sendPendingVariableChanges(); + } + } + } + mouseUpPreviewMatched = false; + lastMouseDownTarget = null; + break; + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + if (touchStart != null) { + /* + * Touch has not been handled as neither context or + * drag start, handle it as a click. + */ + WidgetUtil.simulateClickFromTouchEvent(touchStart, + this); + touchStart = null; + } + touchContextProvider.cancel(); + break; + case Event.ONTOUCHMOVE: + if (isSignificantMove(event)) { + /* + * TODO figure out scroll delegate don't eat events + * if row is selected. Null check for active + * delegate is as a workaround. + */ + if (dragmode != 0 + && touchStart != null + && (TouchScrollDelegate + .getActiveScrollDelegate() == null)) { + startRowDrag(touchStart, type, targetTdOrTr); + } + touchContextProvider.cancel(); + /* + * Avoid clicks and drags by clearing touch start + * flag. + */ + touchStart = null; + } + + break; + case Event.ONTOUCHSTART: + touchStart = event; + Touch touch = event.getChangedTouches().get(0); + // save position to fields, touches in events are same + // instance during the operation. + touchStartX = touch.getClientX(); + touchStartY = touch.getClientY(); + /* + * Prevent simulated mouse events. + */ + touchStart.preventDefault(); + if (dragmode != 0 || actionKeys != null) { + new Timer() { + + @Override + public void run() { + TouchScrollDelegate activeScrollDelegate = TouchScrollDelegate + .getActiveScrollDelegate(); + /* + * If there's a scroll delegate, check if + * we're actually scrolling and handle it. + * If no delegate, do nothing here and let + * the row handle potential drag'n'drop or + * context menu. + */ + if (activeScrollDelegate != null) { + if (activeScrollDelegate.isMoved()) { + /* + * Prevent the row from handling + * touch move/end events (the + * delegate handles those) and from + * doing drag'n'drop or opening a + * context menu. + */ + touchStart = null; + } else { + /* + * Scrolling hasn't started, so + * cancel delegate and let the row + * handle potential drag'n'drop or + * context menu. + */ + activeScrollDelegate + .stopScrolling(); + } + } + } + }.schedule(TOUCHSCROLL_TIMEOUT); + + if (contextTouchTimeout == null + && actionKeys != null) { + contextTouchTimeout = new Timer() { + + @Override + public void run() { + if (touchStart != null) { + showContextMenu(touchStart); + touchStart = null; + } + } + }; + } + if (contextTouchTimeout != null) { + contextTouchTimeout.cancel(); + contextTouchTimeout + .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); + } + } + break; + case Event.ONMOUSEDOWN: + /* + * When getting a mousedown event, we must detect where + * the corresponding mouseup event if it's on a + * different part of the page. + */ + lastMouseDownTarget = getElementTdOrTr(WidgetUtil + .getElementUnderMouse(event)); + mouseUpPreviewMatched = false; + mouseUpEventPreviewRegistration = Event + .addNativePreviewHandler(mouseUpPreviewHandler); + + if (targetCellOrRowFound) { + setRowFocus(this); + ensureFocus(); + if (dragmode != 0 + && (event.getButton() == NativeEvent.BUTTON_LEFT)) { + startRowDrag(event, type, targetTdOrTr); + + } else if (event.getCtrlKey() + || event.getShiftKey() + || event.getMetaKey() + && isMultiSelectModeDefault()) { + + // Prevent default text selection in Firefox + event.preventDefault(); + + // Prevent default text selection in IE + if (BrowserInfo.get().isIE()) { + ((Element) event.getEventTarget().cast()) + .setPropertyJSO( + "onselectstart", + getPreventTextSelectionIEHack()); + } + + event.stopPropagation(); + } + } + break; + case Event.ONMOUSEOUT: + break; + default: + break; + } + } + super.onBrowserEvent(event); + } + + private boolean isSignificantMove(Event event) { + if (touchStart == null) { + // no touch start + return false; + } + /* + * TODO calculate based on real distance instead of separate + * axis checks + */ + Touch touch = event.getChangedTouches().get(0); + if (Math.abs(touch.getClientX() - touchStartX) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) { + return true; + } + if (Math.abs(touch.getClientY() - touchStartY) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) { + return true; + } + return false; + } + + /** + * Checks if the row represented by the row key has been selected + * + * @param key + * The generated row key + */ + private boolean rowKeyIsSelected(int rowKey) { + // Check single selections + if (selectedRowKeys.contains("" + rowKey)) { + return true; + } + + // Check range selections + for (SelectionRange r : selectedRowRanges) { + if (r.inRange(getRenderedRowByKey("" + rowKey))) { + return true; + } + } + return false; + } + + protected void startRowDrag(Event event, final int type, + Element targetTdOrTr) { + VTransferable transferable = new VTransferable(); + transferable.setDragSource(ConnectorMap.get(client) + .getConnector(VScrollTable.this)); + transferable.setData("itemId", "" + rowKey); + NodeList cells = rowElement.getCells(); + for (int i = 0; i < cells.getLength(); i++) { + if (cells.getItem(i).isOrHasChild(targetTdOrTr)) { + HeaderCell headerCell = tHead.getHeaderCell(i); + transferable.setData("propertyId", headerCell.cid); + break; + } + } + + VDragEvent ev = VDragAndDropManager.get().startDrag( + transferable, event, true); + if (dragmode == DRAGMODE_MULTIROW && isMultiSelectModeAny() + && rowKeyIsSelected(rowKey)) { + + // Create a drag image of ALL rows + ev.createDragImage(scrollBody.tBodyElement, true); + + // Hide rows which are not selected + Element dragImage = ev.getDragImage(); + int i = 0; + for (Iterator iterator = scrollBody.iterator(); iterator + .hasNext();) { + VScrollTableRow next = (VScrollTableRow) iterator + .next(); + + Element child = (Element) dragImage.getChild(i++); + + if (!rowKeyIsSelected(next.rowKey)) { + child.getStyle().setVisibility(Visibility.HIDDEN); + } + } + } else { + ev.createDragImage(getElement(), true); + } + if (type == Event.ONMOUSEDOWN) { + event.preventDefault(); + } + event.stopPropagation(); + } + + /** + * Finds the TD that the event interacts with. Returns null if the + * target of the event should not be handled. If the event target is + * the row directly this method returns the TR element instead of + * the TD. + * + * @param event + * @return TD or TR element that the event targets (the actual event + * target is this element or a child of it) + */ + private Element getEventTargetTdOrTr(Event event) { + final Element eventTarget = event.getEventTarget().cast(); + return getElementTdOrTr(eventTarget); + } + + private Element getElementTdOrTr(Element element) { + + Widget widget = WidgetUtil.findWidget(element, null); + + if (widget != this) { + /* + * This is a workaround to make Labels, read only TextFields + * and Embedded in a Table clickable (see #2688). It is + * really not a fix as it does not work with a custom read + * only components (not extending VLabel/VEmbedded). + */ + while (widget != null && widget.getParent() != this) { + widget = widget.getParent(); + } + + if (!(widget instanceof VLabel) + && !(widget instanceof VEmbedded) + && !(widget instanceof VTextField && ((VTextField) widget) + .isReadOnly())) { + return null; + } + } + return getTdOrTr(element); + } + + @Override + public void showContextMenu(Event event) { + if (enabled && actionKeys != null) { + // Show context menu if there are registered action handlers + int left = WidgetUtil.getTouchOrMouseClientX(event) + + Window.getScrollLeft(); + int top = WidgetUtil.getTouchOrMouseClientY(event) + + Window.getScrollTop(); + showContextMenu(left, top); + } + } + + public void showContextMenu(int left, int top) { + VContextMenu menu = client.getContextMenu(); + contextMenu = new ContextMenuDetails(menu, getKey(), left, top); + menu.showAt(this, left, top); + } + + /** + * Has the row been selected? + * + * @return Returns true if selected, else false + */ + public boolean isSelected() { + return selected; + } + + /** + * Toggle the selection of the row + */ + public void toggleSelection() { + selected = !selected; + selectionChanged = true; + if (selected) { + selectedRowKeys.add(String.valueOf(rowKey)); + addStyleName("v-selected"); + } else { + removeStyleName("v-selected"); + selectedRowKeys.remove(String.valueOf(rowKey)); + } + } + + /** + * Is called when a user clicks an item when holding SHIFT key down. + * This will select a new range from the last focused row + * + * @param deselectPrevious + * Should the previous selected range be deselected + */ + private void toggleShiftSelection(boolean deselectPrevious) { + + /* + * Ensures that we are in multiselect mode and that we have a + * previous selection which was not a deselection + */ + if (isSingleSelectMode()) { + // No previous selection found + deselectAll(); + toggleSelection(); + return; + } + + // Set the selectable range + VScrollTableRow endRow = this; + VScrollTableRow startRow = selectionRangeStart; + if (startRow == null) { + startRow = focusedRow; + selectionRangeStart = focusedRow; + // If start row is null then we have a multipage selection + // from above + if (startRow == null) { + startRow = (VScrollTableRow) scrollBody.iterator() + .next(); + setRowFocus(endRow); + } + } else if (!startRow.isSelected()) { + // The start row is no longer selected (probably removed) + // and so we select from above + startRow = (VScrollTableRow) scrollBody.iterator().next(); + setRowFocus(endRow); + } + + // Deselect previous items if so desired + if (deselectPrevious) { + deselectAll(); + } + + // we'll ensure GUI state from top down even though selection + // was the opposite way + if (!startRow.isBefore(endRow)) { + VScrollTableRow tmp = startRow; + startRow = endRow; + endRow = tmp; + } + SelectionRange range = new SelectionRange(startRow, endRow); + + for (Widget w : scrollBody) { + VScrollTableRow row = (VScrollTableRow) w; + if (range.inRange(row)) { + if (!row.isSelected()) { + row.toggleSelection(); + } + selectedRowKeys.add(row.getKey()); + } + } + + // Add range + if (startRow != endRow) { + selectedRowRanges.add(range); + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ui.IActionOwner#getActions () + */ + + @Override + public Action[] getActions() { + if (actionKeys == null) { + return new Action[] {}; + } + final Action[] actions = new Action[actionKeys.length]; + for (int i = 0; i < actions.length; i++) { + final String actionKey = actionKeys[i]; + final TreeAction a = new TreeAction(this, + String.valueOf(rowKey), actionKey) { + + @Override + public void execute() { + super.execute(); + lazyRevertFocusToRow(VScrollTableRow.this); + } + }; + a.setCaption(getActionCaption(actionKey)); + a.setIconUrl(getActionIcon(actionKey)); + actions[i] = a; + } + return actions; + } + + @Override + public ApplicationConnection getClient() { + return client; + } + + @Override + public String getPaintableId() { + return paintableId; + } + + private int getColIndexOf(Widget child) { + com.google.gwt.dom.client.Element widgetCell = child + .getElement().getParentElement().getParentElement(); + NodeList cells = rowElement.getCells(); + for (int i = 0; i < cells.getLength(); i++) { + if (cells.getItem(i) == widgetCell) { + return i; + } + } + return -1; + } + + public Widget getWidgetForPaintable() { + return this; + } + } + + protected class VScrollTableGeneratedRow extends VScrollTableRow { + + private boolean spanColumns; + private boolean htmlContentAllowed; + + public VScrollTableGeneratedRow(UIDL uidl, char[] aligns) { + super(uidl, aligns); + addStyleName("v-table-generated-row"); + } + + public boolean isSpanColumns() { + return spanColumns; + } + + @Override + protected void initCellWidths() { + if (spanColumns) { + setSpannedColumnWidthAfterDOMFullyInited(); + } else { + super.initCellWidths(); + } + } + + private void setSpannedColumnWidthAfterDOMFullyInited() { + // Defer setting width on spanned columns to make sure that + // they are added to the DOM before trying to calculate + // widths. + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + + @Override + public void execute() { + if (showRowHeaders) { + setCellWidth(0, tHead.getHeaderCell(0) + .getWidthWithIndent()); + calcAndSetSpanWidthOnCell(1); + } else { + calcAndSetSpanWidthOnCell(0); + } + } + }); + } + + @Override + protected boolean isRenderHtmlInCells() { + return htmlContentAllowed; + } + + @Override + protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, + int visibleColumnIndex) { + htmlContentAllowed = uidl.getBooleanAttribute("gen_html"); + spanColumns = uidl.getBooleanAttribute("gen_span"); + + final Iterator cells = uidl.getChildIterator(); + if (spanColumns) { + int colCount = uidl.getChildCount(); + if (cells.hasNext()) { + final Object cell = cells.next(); + if (cell instanceof String) { + addSpannedCell(uidl, cell.toString(), aligns[0], + "", htmlContentAllowed, false, null, + colCount); + } else { + addSpannedCell(uidl, (Widget) cell, aligns[0], "", + false, colCount); + } + } + } else { + super.addCellsFromUIDL(uidl, aligns, col, + visibleColumnIndex); + } + } + + private void addSpannedCell(UIDL rowUidl, Widget w, char align, + String style, boolean sorted, int colCount) { + TableCellElement td = DOM.createTD().cast(); + td.setColSpan(colCount); + initCellWithWidget(w, align, style, sorted, td); + } + + private void addSpannedCell(UIDL rowUidl, String text, char align, + String style, boolean textIsHTML, boolean sorted, + String description, int colCount) { + // String only content is optimized by not using Label widget + final TableCellElement td = DOM.createTD().cast(); + td.setColSpan(colCount); + initCellWithText(text, align, style, textIsHTML, sorted, + description, td); + } + + @Override + protected void setCellWidth(int cellIx, int width) { + if (isSpanColumns()) { + if (showRowHeaders) { + if (cellIx == 0) { + super.setCellWidth(0, width); + } else { + // We need to recalculate the spanning TDs width for + // every cellIx in order to support column resizing. + calcAndSetSpanWidthOnCell(1); + } + } else { + // Same as above. + calcAndSetSpanWidthOnCell(0); + } + } else { + super.setCellWidth(cellIx, width); + } + } + + private void calcAndSetSpanWidthOnCell(final int cellIx) { + int spanWidth = 0; + for (int ix = (showRowHeaders ? 1 : 0); ix < tHead + .getVisibleCellCount(); ix++) { + spanWidth += tHead.getHeaderCell(ix).getOffsetWidth(); + } + WidgetUtil.setWidthExcludingPaddingAndBorder( + (Element) getElement().getChild(cellIx), spanWidth, 13, + false); + } + } + + /** + * Ensure the component has a focus. + * + * TODO the current implementation simply always calls focus for the + * component. In case the Table at some point implements focus/blur + * listeners, this method needs to be evolved to conditionally call + * focus only if not currently focused. + */ + protected void ensureFocus() { + if (!hasFocus) { + scrollBodyPanel.setFocus(true); + } + + } + + } + + /** + * Deselects all items + */ + public void deselectAll() { + for (Widget w : scrollBody) { + VScrollTableRow row = (VScrollTableRow) w; + if (row.isSelected()) { + row.toggleSelection(); + } + } + // still ensure all selects are removed from (not necessary rendered) + selectedRowKeys.clear(); + selectedRowRanges.clear(); + // also notify server that it clears all previous selections (the client + // side does not know about the invisible ones) + instructServerToForgetPreviousSelections(); + } + + /** + * Used in multiselect mode when the client side knows that all selections + * are in the next request. + */ + private void instructServerToForgetPreviousSelections() { + client.updateVariable(paintableId, "clearSelections", true, false); + } + + /** + * Determines the pagelength when the table height is fixed. + */ + public void updatePageLength() { + // Only update if visible and enabled + if (!isVisible() || !enabled) { + return; + } + + if (scrollBody == null) { + return; + } + + if (isDynamicHeight()) { + return; + } + + int rowHeight = (int) Math.round(scrollBody.getRowHeight()); + int bodyH = scrollBodyPanel.getOffsetHeight(); + int rowsAtOnce = bodyH / rowHeight; + boolean anotherPartlyVisible = ((bodyH % rowHeight) != 0); + if (anotherPartlyVisible) { + rowsAtOnce++; + } + if (pageLength != rowsAtOnce) { + pageLength = rowsAtOnce; + client.updateVariable(paintableId, "pagelength", pageLength, false); + + if (!rendering) { + int currentlyVisible = scrollBody.getLastRendered() + - scrollBody.getFirstRendered(); + if (currentlyVisible < pageLength + && currentlyVisible < totalRows) { + // shake scrollpanel to fill empty space + scrollBodyPanel.setScrollPosition(scrollTop + 1); + scrollBodyPanel.setScrollPosition(scrollTop - 1); + } + + sizeNeedsInit = true; + } + } + + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateWidth() { + if (!isVisible()) { + /* + * Do not update size when the table is hidden as all column widths + * will be set to zero and they won't be recalculated when the table + * is set visible again (until the size changes again) + */ + return; + } + + if (!isDynamicWidth()) { + int innerPixels = getOffsetWidth() - getBorderWidth(); + if (innerPixels < 0) { + innerPixels = 0; + } + setContentWidth(innerPixels); + + // readjust undefined width columns + triggerLazyColumnAdjustment(false); + + } else { + + sizeNeedsInit = true; + + // readjust undefined width columns + triggerLazyColumnAdjustment(false); + } + + /* + * setting width may affect wheter the component has scrollbars -> needs + * scrolling or not + */ + setProperTabIndex(); + } + + private static final int LAZY_COLUMN_ADJUST_TIMEOUT = 300; + + private final Timer lazyAdjustColumnWidths = new Timer() { + /** + * Check for column widths, and available width, to see if we can fix + * column widths "optimally". Doing this lazily to avoid expensive + * calculation when resizing is not yet finished. + */ + + @Override + public void run() { + if (scrollBody == null) { + // Try again later if we get here before scrollBody has been + // initalized + triggerLazyColumnAdjustment(false); + return; + } + + Iterator headCells = tHead.iterator(); + int usedMinimumWidth = 0; + int totalExplicitColumnsWidths = 0; + float expandRatioDivider = 0; + int colIndex = 0; + + int hierarchyColumnIndent = scrollBody.getMaxIndent(); + int hierarchyColumnIndex = getHierarchyColumnIndex(); + HeaderCell hierarchyHeaderInNeedOfFurtherHandling = null; + + while (headCells.hasNext()) { + final HeaderCell hCell = (HeaderCell) headCells.next(); + boolean hasIndent = hierarchyColumnIndent > 0 + && hCell.isHierarchyColumn(); + if (hCell.isDefinedWidth()) { + // get width without indent to find out whether adjustments + // are needed (requires special handling further ahead) + int w = hCell.getWidth(); + if (hasIndent && w < hierarchyColumnIndent) { + // enforce indent if necessary + w = hierarchyColumnIndent; + hierarchyHeaderInNeedOfFurtherHandling = hCell; + } + totalExplicitColumnsWidths += w; + usedMinimumWidth += w; + } else { + // natural width already includes indent if any + int naturalColumnWidth = hCell + .getNaturalColumnWidth(colIndex); + /* + * TODO If there is extra width, expand ratios are for + * additional extra widths, not for absolute column widths. + * Should be fixed in sizeInit(), too. + */ + if (hCell.getExpandRatio() > 0) { + naturalColumnWidth = 0; + } + usedMinimumWidth += naturalColumnWidth; + expandRatioDivider += hCell.getExpandRatio(); + if (hasIndent) { + hierarchyHeaderInNeedOfFurtherHandling = hCell; + } + } + colIndex++; + } + + int availW = scrollBody.getAvailableWidth(); + // Hey IE, are you really sure about this? + availW = scrollBody.getAvailableWidth(); + int visibleCellCount = tHead.getVisibleCellCount(); + int totalExtraWidth = scrollBody.getCellExtraWidth() + * visibleCellCount; + if (willHaveScrollbars()) { + totalExtraWidth += WidgetUtil.getNativeScrollbarSize(); + // if there will be vertical scrollbar, let's enable it + scrollBodyPanel.getElement().getStyle().clearOverflowY(); + } else { + // if there is no need for vertical scrollbar, let's disable it + // this is necessary since sometimes the browsers insist showing + // the scrollbar even if the content would fit perfectly + scrollBodyPanel.getElement().getStyle() + .setOverflowY(Overflow.HIDDEN); + } + + availW -= totalExtraWidth; + int forceScrollBodyWidth = -1; + + int extraSpace = availW - usedMinimumWidth; + if (extraSpace < 0) { + if (getTotalRows() == 0) { + /* + * Too wide header combined with no rows in the table. + * + * No horizontal scrollbars would be displayed because + * there's no rows that grows too wide causing the + * scrollBody container div to overflow. Must explicitely + * force a width to a scrollbar. (see #9187) + */ + forceScrollBodyWidth = usedMinimumWidth + totalExtraWidth; + } + extraSpace = 0; + // if there will be horizontal scrollbar, let's enable it + scrollBodyPanel.getElement().getStyle().clearOverflowX(); + } else { + // if there is no need for horizontal scrollbar, let's disable + // it + // this is necessary since sometimes the browsers insist showing + // the scrollbar even if the content would fit perfectly + scrollBodyPanel.getElement().getStyle() + .setOverflowX(Overflow.HIDDEN); + } + + if (forceScrollBodyWidth > 0) { + scrollBody.container.getStyle().setWidth(forceScrollBodyWidth, + Unit.PX); + } else { + // Clear width that might have been set to force horizontal + // scrolling if there are no rows + scrollBody.container.getStyle().clearWidth(); + } + + int totalUndefinedNaturalWidths = usedMinimumWidth + - totalExplicitColumnsWidths; + + if (hierarchyHeaderInNeedOfFurtherHandling != null + && !hierarchyHeaderInNeedOfFurtherHandling.isDefinedWidth()) { + // ensure the cell gets enough space for the indent + int w = hierarchyHeaderInNeedOfFurtherHandling + .getNaturalColumnWidth(hierarchyColumnIndex); + int newSpace = Math.round(w + (float) extraSpace * (float) w + / totalUndefinedNaturalWidths); + if (newSpace >= hierarchyColumnIndent) { + // no special handling required + hierarchyHeaderInNeedOfFurtherHandling = null; + } else { + // treat as a defined width column of indent's width + totalExplicitColumnsWidths += hierarchyColumnIndent; + usedMinimumWidth -= w - hierarchyColumnIndent; + totalUndefinedNaturalWidths = usedMinimumWidth + - totalExplicitColumnsWidths; + expandRatioDivider += hierarchyHeaderInNeedOfFurtherHandling + .getExpandRatio(); + extraSpace = Math.max(availW - usedMinimumWidth, 0); + } + } + + // we have some space that can be divided optimally + HeaderCell hCell; + colIndex = 0; + headCells = tHead.iterator(); + int checksum = 0; + while (headCells.hasNext()) { + hCell = (HeaderCell) headCells.next(); + if (hCell.isResizing) { + continue; + } + if (!hCell.isDefinedWidth()) { + int w = hCell.getNaturalColumnWidth(colIndex); + int newSpace; + if (expandRatioDivider > 0) { + // divide excess space by expand ratios + if (hCell.getExpandRatio() > 0) { + w = 0; + } + newSpace = Math.round((w + extraSpace + * hCell.getExpandRatio() / expandRatioDivider)); + } else { + if (hierarchyHeaderInNeedOfFurtherHandling == hCell) { + // still exists, so needs exactly the indent's width + newSpace = hierarchyColumnIndent; + } else if (totalUndefinedNaturalWidths != 0) { + // divide relatively to natural column widths + newSpace = Math.round(w + (float) extraSpace + * (float) w / totalUndefinedNaturalWidths); + } else { + newSpace = w; + } + } + checksum += newSpace; + setColWidth(colIndex, newSpace, false); + + } else { + if (hierarchyHeaderInNeedOfFurtherHandling == hCell) { + // defined with enforced into indent width + checksum += hierarchyColumnIndent; + setColWidth(colIndex, hierarchyColumnIndent, false); + } else { + int cellWidth = hCell.getWidthWithIndent(); + checksum += cellWidth; + if (hCell.isHierarchyColumn()) { + // update in case the indent has changed + // (not detectable earlier) + setColWidth(colIndex, cellWidth, true); + } + } + } + colIndex++; + } + + if (extraSpace > 0 && checksum != availW) { + /* + * There might be in some cases a rounding error of 1px when + * extra space is divided so if there is one then we give the + * first undefined column 1 more pixel + */ + headCells = tHead.iterator(); + colIndex = 0; + while (headCells.hasNext()) { + HeaderCell hc = (HeaderCell) headCells.next(); + if (!hc.isResizing && !hc.isDefinedWidth()) { + setColWidth(colIndex, hc.getWidthWithIndent() + availW + - checksum, false); + break; + } + colIndex++; + } + } + + if (isDynamicHeight() && totalRows == pageLength) { + // fix body height (may vary if lazy loading is offhorizontal + // scrollbar appears/disappears) + int bodyHeight = scrollBody.getRequiredHeight(); + boolean needsSpaceForHorizontalScrollbar = (availW < usedMinimumWidth); + if (needsSpaceForHorizontalScrollbar) { + bodyHeight += WidgetUtil.getNativeScrollbarSize(); + } + int heightBefore = getOffsetHeight(); + scrollBodyPanel.setHeight(bodyHeight + "px"); + + if (heightBefore != getOffsetHeight()) { + Util.notifyParentOfSizeChange(VScrollTable.this, rendering); + } + } + + forceRealignColumnHeaders(); + } + + }; + + private void forceRealignColumnHeaders() { + if (BrowserInfo.get().isIE()) { + /* + * IE does not fire onscroll event if scroll position is reverted to + * 0 due to the content element size growth. Ensure headers are in + * sync with content manually. Safe to use null event as we don't + * actually use the event object in listener. + */ + onScroll(null); + } + } + + /** + * helper to set pixel size of head and body part + * + * @param pixels + */ + private void setContentWidth(int pixels) { + tHead.setWidth(pixels + "px"); + scrollBodyPanel.setWidth(pixels + "px"); + tFoot.setWidth(pixels + "px"); + } + + private int borderWidth = -1; + + /** + * @return border left + border right + */ + private int getBorderWidth() { + if (borderWidth < 0) { + borderWidth = WidgetUtil.measureHorizontalPaddingAndBorder( + scrollBodyPanel.getElement(), 2); + if (borderWidth < 0) { + borderWidth = 0; + } + } + return borderWidth; + } + + /** + * Ensures scrollable area is properly sized. This method is used when fixed + * size is used. + */ + private int containerHeight; + + private void setContainerHeight() { + if (!isDynamicHeight()) { + + /* + * Android 2.3 cannot measure the height of the inline-block + * properly, and will return the wrong offset height. So for android + * 2.3 we set the element to a block element while measuring and + * then restore it which yields the correct result. #11331 + */ + if (BrowserInfo.get().isAndroid23()) { + getElement().getStyle().setDisplay(Display.BLOCK); + } + + containerHeight = getOffsetHeight(); + containerHeight -= showColHeaders ? tHead.getOffsetHeight() : 0; + containerHeight -= tFoot.getOffsetHeight(); + containerHeight -= getContentAreaBorderHeight(); + if (containerHeight < 0) { + containerHeight = 0; + } + + scrollBodyPanel.setHeight(containerHeight + "px"); + + if (BrowserInfo.get().isAndroid23()) { + getElement().getStyle().clearDisplay(); + } + } + } + + private int contentAreaBorderHeight = -1; + private int scrollLeft; + private int scrollTop; + + /** For internal use only. May be removed or replaced in the future. */ + public VScrollTableDropHandler dropHandler; + + private boolean navKeyDown; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean multiselectPending; + + /** For internal use only. May be removed or replaced in the future. */ + public CollapseMenuContent collapsibleMenuContent; + + /** + * @return border top + border bottom of the scrollable area of table + */ + private int getContentAreaBorderHeight() { + if (contentAreaBorderHeight < 0) { + + scrollBodyPanel.getElement().getStyle() + .setOverflow(Overflow.HIDDEN); + int oh = scrollBodyPanel.getOffsetHeight(); + int ch = scrollBodyPanel.getElement() + .getPropertyInt("clientHeight"); + contentAreaBorderHeight = oh - ch; + scrollBodyPanel.getElement().getStyle().setOverflow(Overflow.AUTO); + } + return contentAreaBorderHeight; + } + + @Override + public void setHeight(String height) { + if (height.length() == 0 + && getElement().getStyle().getHeight().length() != 0) { + /* + * Changing from defined to undefined size -> should do a size init + * to take page length into account again + */ + sizeNeedsInit = true; + } + super.setHeight(height); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateHeight() { + setContainerHeight(); + + if (initializedAndAttached) { + updatePageLength(); + } + + triggerLazyColumnAdjustment(false); + + /* + * setting height may affect wheter the component has scrollbars -> + * needs scrolling or not + */ + setProperTabIndex(); + + } + + /* + * Overridden due Table might not survive of visibility change (scroll pos + * lost). Example ITabPanel just set contained components invisible and back + * when changing tabs. + */ + + @Override + public void setVisible(boolean visible) { + if (isVisible() != visible) { + super.setVisible(visible); + if (initializedAndAttached) { + if (visible) { + Scheduler.get().scheduleDeferred(new Command() { + + @Override + public void execute() { + scrollBodyPanel + .setScrollPosition(measureRowHeightOffset(firstRowInViewPort)); + } + }); + } + } + } + } + + /** + * Helper function to build html snippet for column or row headers + * + * @param uidl + * possibly with values caption and icon + * @return html snippet containing possibly an icon + caption text + */ + protected String buildCaptionHtmlSnippet(UIDL uidl) { + String s = uidl.hasAttribute("caption") ? uidl + .getStringAttribute("caption") : ""; + if (uidl.hasAttribute("icon")) { + Icon icon = client.getIcon(uidl.getStringAttribute("icon")); + icon.setAlternateText("icon"); + s = icon.getElement().getString() + s; + } + return s; + } + + // Updates first visible row for the case we cannot wait + // for onScroll + private void updateFirstVisibleRow() { + scrollTop = scrollBodyPanel.getScrollPosition(); + firstRowInViewPort = calcFirstRowInViewPort(); + int maxFirstRow = totalRows - pageLength; + if (firstRowInViewPort > maxFirstRow && maxFirstRow >= 0) { + firstRowInViewPort = maxFirstRow; + } + lastRequestedFirstvisible = firstRowInViewPort; + client.updateVariable(paintableId, "firstvisible", firstRowInViewPort, + false); + } + + /** + * This method has logic which rows needs to be requested from server when + * user scrolls + */ + + @Override + public void onScroll(ScrollEvent event) { + // Do not handle scroll events while there is scroll initiated from + // server side which is not yet executed (#11454) + if (isLazyScrollerActive()) { + return; + } + + scrollLeft = scrollBodyPanel.getElement().getScrollLeft(); + scrollTop = scrollBodyPanel.getScrollPosition(); + /* + * #6970 - IE sometimes fires scroll events for a detached table. + * + * FIXME initializedAndAttached should probably be renamed - its name + * doesn't seem to reflect its semantics. onDetach() doesn't set it to + * false, and changing that might break something else, so we need to + * check isAttached() separately. + */ + if (!initializedAndAttached || !isAttached()) { + return; + } + if (!enabled) { + scrollBodyPanel + .setScrollPosition(measureRowHeightOffset(firstRowInViewPort)); + return; + } + + rowRequestHandler.cancel(); + + if (BrowserInfo.get().isSafari() && event != null && scrollTop == 0) { + // due to the webkitoverflowworkaround, top may sometimes report 0 + // for webkit, although it really is not. Expecting to have the + // correct + // value available soon. + Scheduler.get().scheduleDeferred(new Command() { + + @Override + public void execute() { + onScroll(null); + } + }); + return; + } + + // fix headers horizontal scrolling + tHead.setHorizontalScrollPosition(scrollLeft); + + // fix footers horizontal scrolling + tFoot.setHorizontalScrollPosition(scrollLeft); + + if (totalRows == 0) { + // No rows, no need to fetch new rows + return; + } + + firstRowInViewPort = calcFirstRowInViewPort(); + int maxFirstRow = totalRows - pageLength; + if (firstRowInViewPort > maxFirstRow && maxFirstRow >= 0) { + firstRowInViewPort = maxFirstRow; + } + + int postLimit = (int) (firstRowInViewPort + (pageLength - 1) + pageLength + * cache_react_rate); + if (postLimit > totalRows - 1) { + postLimit = totalRows - 1; + } + int preLimit = (int) (firstRowInViewPort - pageLength + * cache_react_rate); + if (preLimit < 0) { + preLimit = 0; + } + final int lastRendered = scrollBody.getLastRendered(); + final int firstRendered = scrollBody.getFirstRendered(); + + if (postLimit <= lastRendered && preLimit >= firstRendered) { + // we're within no-react area, no need to request more rows + // remember which firstvisible we requested, in case the server has + // a differing opinion + lastRequestedFirstvisible = firstRowInViewPort; + client.updateVariable(paintableId, "firstvisible", + firstRowInViewPort, false); + return; + } + + if (allRenderedRowsAreNew()) { + // need a totally new set of rows + rowRequestHandler + .setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate))); + int last = firstRowInViewPort + (int) (cache_rate * pageLength) + + pageLength - 1; + if (last >= totalRows) { + last = totalRows - 1; + } + rowRequestHandler.setReqRows(last + - rowRequestHandler.getReqFirstRow() + 1); + updatedReqRows = false; + rowRequestHandler.deferRowFetch(); + return; + } + if (preLimit < firstRendered) { + // need some rows to the beginning of the rendered area + + rowRequestHandler + .setReqFirstRow((int) (firstRowInViewPort - pageLength + * cache_rate)); + rowRequestHandler.setReqRows(firstRendered + - rowRequestHandler.getReqFirstRow()); + rowRequestHandler.deferRowFetch(); + + return; + } + if (postLimit > lastRendered) { + // need some rows to the end of the rendered area + int reqRows = (int) ((firstRowInViewPort + pageLength + pageLength + * cache_rate) - lastRendered); + rowRequestHandler.triggerRowFetch(lastRendered + 1, reqRows); + } + } + + private boolean allRenderedRowsAreNew() { + int firstRowInViewPort = calcFirstRowInViewPort(); + int firstRendered = scrollBody.getFirstRendered(); + int lastRendered = scrollBody.getLastRendered(); + return (firstRowInViewPort - pageLength * cache_rate > lastRendered || firstRowInViewPort + + pageLength + pageLength * cache_rate < firstRendered); + } + + protected int calcFirstRowInViewPort() { + return (int) Math.ceil(scrollTop / scrollBody.getRowHeight()); + } + + @Override + public VScrollTableDropHandler getDropHandler() { + return dropHandler; + } + + private static class TableDDDetails { + int overkey = -1; + VerticalDropLocation dropLocation; + String colkey; + + @Override + public boolean equals(Object obj) { + if (obj instanceof TableDDDetails) { + TableDDDetails other = (TableDDDetails) obj; + return dropLocation == other.dropLocation + && overkey == other.overkey + && ((colkey != null && colkey.equals(other.colkey)) || (colkey == null && other.colkey == null)); + } + return false; + } + + // + // public int hashCode() { + // return overkey; + // } + } + + public class VScrollTableDropHandler extends VAbstractDropHandler { + + private static final String ROWSTYLEBASE = "v-table-row-drag-"; + private TableDDDetails dropDetails; + private TableDDDetails lastEmphasized; + + @Override + public void dragEnter(VDragEvent drag) { + updateDropDetails(drag); + super.dragEnter(drag); + } + + private void updateDropDetails(VDragEvent drag) { + dropDetails = new TableDDDetails(); + Element elementOver = drag.getElementOver(); + + Class clazz = getRowClass(); + VScrollTableRow row = null; + if (clazz != null) { + row = WidgetUtil.findWidget(elementOver, clazz); + } + if (row != null) { + dropDetails.overkey = row.rowKey; + Element tr = row.getElement(); + Element element = elementOver; + while (element != null && element.getParentElement() != tr) { + element = element.getParentElement(); + } + int childIndex = DOM.getChildIndex(tr, element); + dropDetails.colkey = tHead.getHeaderCell(childIndex) + .getColKey(); + dropDetails.dropLocation = DDUtil.getVerticalDropLocation( + row.getElement(), drag.getCurrentGwtEvent(), 0.2); + } + + drag.getDropDetails().put("itemIdOver", dropDetails.overkey + ""); + drag.getDropDetails().put( + "detail", + dropDetails.dropLocation != null ? dropDetails.dropLocation + .toString() : null); + + } + + private Class getRowClass() { + // get the row type this way to make dd work in derived + // implementations + Iterator iterator = scrollBody.iterator(); + if (iterator.hasNext()) { + return iterator.next().getClass(); + } else { + return null; + } + } + + @Override + public void dragOver(VDragEvent drag) { + TableDDDetails oldDetails = dropDetails; + updateDropDetails(drag); + if (!oldDetails.equals(dropDetails)) { + deEmphasis(); + final TableDDDetails newDetails = dropDetails; + VAcceptCallback cb = new VAcceptCallback() { + + @Override + public void accepted(VDragEvent event) { + if (newDetails.equals(dropDetails)) { + dragAccepted(event); + } + /* + * Else new target slot already defined, ignore + */ + } + }; + validate(cb, drag); + } + } + + @Override + public void dragLeave(VDragEvent drag) { + deEmphasis(); + super.dragLeave(drag); + } + + @Override + public boolean drop(VDragEvent drag) { + deEmphasis(); + return super.drop(drag); + } + + private void deEmphasis() { + UIObject.setStyleName(getElement(), + getStylePrimaryName() + "-drag", false); + if (lastEmphasized == null) { + return; + } + for (Widget w : scrollBody.renderedRows) { + VScrollTableRow row = (VScrollTableRow) w; + if (lastEmphasized != null + && row.rowKey == lastEmphasized.overkey) { + String stylename = ROWSTYLEBASE + + lastEmphasized.dropLocation.toString() + .toLowerCase(); + VScrollTableRow.setStyleName(row.getElement(), stylename, + false); + lastEmphasized = null; + return; + } + } + } + + /** + * TODO needs different drop modes ?? (on cells, on rows), now only + * supports rows + */ + private void emphasis(TableDDDetails details) { + deEmphasis(); + UIObject.setStyleName(getElement(), + getStylePrimaryName() + "-drag", true); + // iterate old and new emphasized row + for (Widget w : scrollBody.renderedRows) { + VScrollTableRow row = (VScrollTableRow) w; + if (details != null && details.overkey == row.rowKey) { + String stylename = ROWSTYLEBASE + + details.dropLocation.toString().toLowerCase(); + VScrollTableRow.setStyleName(row.getElement(), stylename, + true); + lastEmphasized = details; + return; + } + } + } + + @Override + protected void dragAccepted(VDragEvent drag) { + emphasis(dropDetails); + } + + @Override + public ComponentConnector getConnector() { + return ConnectorMap.get(client).getConnector(VScrollTable.this); + } + + @Override + public ApplicationConnection getApplicationConnection() { + return client; + } + + } + + protected VScrollTableRow getFocusedRow() { + return focusedRow; + } + + /** + * Moves the selection head to a specific row + * + * @param row + * The row to where the selection head should move + * @return Returns true if focus was moved successfully, else false + */ + public boolean setRowFocus(VScrollTableRow row) { + + if (!isSelectable()) { + return false; + } + + // Remove previous selection + if (focusedRow != null && focusedRow != row) { + focusedRow.removeStyleName(getStylePrimaryName() + "-focus"); + } + + if (row != null) { + // Apply focus style to new selection + row.addStyleName(getStylePrimaryName() + "-focus"); + + /* + * Trying to set focus on already focused row + */ + if (row == focusedRow) { + return false; + } + + // Set new focused row + focusedRow = row; + + if (hasFocus) { + ensureRowIsVisible(row); + } + + return true; + } + + return false; + } + + /** + * Ensures that the row is visible + * + * @param row + * The row to ensure is visible + */ + private void ensureRowIsVisible(VScrollTableRow row) { + if (BrowserInfo.get().isTouchDevice()) { + // Skip due to android devices that have broken scrolltop will may + // get odd scrolling here. + return; + } + /* + * FIXME The next line doesn't always do what expected, because if the + * row is not in the DOM it won't scroll to it. + */ + WidgetUtil.scrollIntoViewVertically(row.getElement()); + } + + /** + * Handles the keyboard events handled by the table + * + * @param event + * The keyboard event received + * @return true iff the navigation event was handled + */ + protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { + if (keycode == KeyCodes.KEY_TAB || keycode == KeyCodes.KEY_SHIFT) { + // Do not handle tab key + return false; + } + + // Down navigation + if (!isSelectable() && keycode == getNavigationDownKey()) { + scrollBodyPanel.setScrollPosition(scrollBodyPanel + .getScrollPosition() + scrollingVelocity); + return true; + } else if (keycode == getNavigationDownKey()) { + if (isMultiSelectModeAny() && moveFocusDown()) { + selectFocusedRow(ctrl, shift); + + } else if (isSingleSelectMode() && !shift && moveFocusDown()) { + selectFocusedRow(ctrl, shift); + } + return true; + } + + // Up navigation + if (!isSelectable() && keycode == getNavigationUpKey()) { + scrollBodyPanel.setScrollPosition(scrollBodyPanel + .getScrollPosition() - scrollingVelocity); + return true; + } else if (keycode == getNavigationUpKey()) { + if (isMultiSelectModeAny() && moveFocusUp()) { + selectFocusedRow(ctrl, shift); + } else if (isSingleSelectMode() && !shift && moveFocusUp()) { + selectFocusedRow(ctrl, shift); + } + return true; + } + + if (keycode == getNavigationLeftKey()) { + // Left navigation + scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel + .getHorizontalScrollPosition() - scrollingVelocity); + return true; + + } else if (keycode == getNavigationRightKey()) { + // Right navigation + scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel + .getHorizontalScrollPosition() + scrollingVelocity); + return true; + } + + // Select navigation + if (isSelectable() && keycode == getNavigationSelectKey()) { + if (isSingleSelectMode()) { + boolean wasSelected = focusedRow.isSelected(); + deselectAll(); + if (!wasSelected || !nullSelectionAllowed) { + focusedRow.toggleSelection(); + } + } else { + focusedRow.toggleSelection(); + removeRowFromUnsentSelectionRanges(focusedRow); + } + + sendSelectedRows(); + return true; + } + + // Page Down navigation + if (keycode == getNavigationPageDownKey()) { + if (isSelectable()) { + /* + * If selectable we plagiate MSW behaviour: first scroll to the + * end of current view. If at the end, scroll down one page + * length and keep the selected row in the bottom part of + * visible area. + */ + if (!isFocusAtTheEndOfTable()) { + VScrollTableRow lastVisibleRowInViewPort = scrollBody + .getRowByRowIndex(firstRowInViewPort + + getFullyVisibleRowCount() - 1); + if (lastVisibleRowInViewPort != null + && lastVisibleRowInViewPort != focusedRow) { + // focused row is not at the end of the table, move + // focus and select the last visible row + setRowFocus(lastVisibleRowInViewPort); + selectFocusedRow(ctrl, shift); + updateFirstVisibleAndSendSelectedRows(); + } else { + int indexOfToBeFocused = focusedRow.getIndex() + + getFullyVisibleRowCount(); + if (indexOfToBeFocused >= totalRows) { + indexOfToBeFocused = totalRows - 1; + } + VScrollTableRow toBeFocusedRow = scrollBody + .getRowByRowIndex(indexOfToBeFocused); + + if (toBeFocusedRow != null) { + /* + * if the next focused row is rendered + */ + setRowFocus(toBeFocusedRow); + selectFocusedRow(ctrl, shift); + // TODO needs scrollintoview ? + updateFirstVisibleAndSendSelectedRows(); + } else { + // scroll down by pixels and return, to wait for + // new rows, then select the last item in the + // viewport + selectLastItemInNextRender = true; + multiselectPending = shift; + scrollByPagelength(1); + } + } + } + } else { + /* No selections, go page down by scrolling */ + scrollByPagelength(1); + } + return true; + } + + // Page Up navigation + if (keycode == getNavigationPageUpKey()) { + if (isSelectable()) { + /* + * If selectable we plagiate MSW behaviour: first scroll to the + * end of current view. If at the end, scroll down one page + * length and keep the selected row in the bottom part of + * visible area. + */ + if (!isFocusAtTheBeginningOfTable()) { + VScrollTableRow firstVisibleRowInViewPort = scrollBody + .getRowByRowIndex(firstRowInViewPort); + if (firstVisibleRowInViewPort != null + && firstVisibleRowInViewPort != focusedRow) { + // focus is not at the beginning of the table, move + // focus and select the first visible row + setRowFocus(firstVisibleRowInViewPort); + selectFocusedRow(ctrl, shift); + updateFirstVisibleAndSendSelectedRows(); + } else { + int indexOfToBeFocused = focusedRow.getIndex() + - getFullyVisibleRowCount(); + if (indexOfToBeFocused < 0) { + indexOfToBeFocused = 0; + } + VScrollTableRow toBeFocusedRow = scrollBody + .getRowByRowIndex(indexOfToBeFocused); + + if (toBeFocusedRow != null) { // if the next focused row + // is rendered + setRowFocus(toBeFocusedRow); + selectFocusedRow(ctrl, shift); + // TODO needs scrollintoview ? + updateFirstVisibleAndSendSelectedRows(); + } else { + // unless waiting for the next rowset already + // scroll down by pixels and return, to wait for + // new rows, then select the last item in the + // viewport + selectFirstItemInNextRender = true; + multiselectPending = shift; + scrollByPagelength(-1); + } + } + } + } else { + /* No selections, go page up by scrolling */ + scrollByPagelength(-1); + } + + return true; + } + + // Goto start navigation + if (keycode == getNavigationStartKey()) { + scrollBodyPanel.setScrollPosition(0); + if (isSelectable()) { + if (focusedRow != null && focusedRow.getIndex() == 0) { + return false; + } else { + VScrollTableRow rowByRowIndex = (VScrollTableRow) scrollBody + .iterator().next(); + if (rowByRowIndex.getIndex() == 0) { + setRowFocus(rowByRowIndex); + selectFocusedRow(ctrl, shift); + updateFirstVisibleAndSendSelectedRows(); + } else { + // first row of table will come in next row fetch + if (ctrl) { + focusFirstItemInNextRender = true; + } else { + selectFirstItemInNextRender = true; + multiselectPending = shift; + } + } + } + } + return true; + } + + // Goto end navigation + if (keycode == getNavigationEndKey()) { + scrollBodyPanel.setScrollPosition(scrollBody.getOffsetHeight()); + if (isSelectable()) { + final int lastRendered = scrollBody.getLastRendered(); + if (lastRendered + 1 == totalRows) { + VScrollTableRow rowByRowIndex = scrollBody + .getRowByRowIndex(lastRendered); + if (focusedRow != rowByRowIndex) { + setRowFocus(rowByRowIndex); + selectFocusedRow(ctrl, shift); + updateFirstVisibleAndSendSelectedRows(); + } + } else { + if (ctrl) { + focusLastItemInNextRender = true; + } else { + selectLastItemInNextRender = true; + multiselectPending = shift; + } + } + } + return true; + } + + return false; + } + + private boolean isFocusAtTheBeginningOfTable() { + return focusedRow.getIndex() == 0; + } + + private boolean isFocusAtTheEndOfTable() { + return focusedRow.getIndex() + 1 >= totalRows; + } + + private int getFullyVisibleRowCount() { + return (int) (scrollBodyPanel.getOffsetHeight() / scrollBody + .getRowHeight()); + } + + private void scrollByPagelength(int i) { + int pixels = i * scrollBodyPanel.getOffsetHeight(); + int newPixels = scrollBodyPanel.getScrollPosition() + pixels; + if (newPixels < 0) { + newPixels = 0; + } // else if too high, NOP (all know browsers accept illegally big + // values here) + scrollBodyPanel.setScrollPosition(newPixels); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event + * .dom.client.FocusEvent) + */ + + @Override + public void onFocus(FocusEvent event) { + if (isFocusable()) { + hasFocus = true; + + // Focus a row if no row is in focus + if (focusedRow == null) { + focusRowFromBody(); + } else { + setRowFocus(focusedRow); + } + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event + * .dom.client.BlurEvent) + */ + + @Override + public void onBlur(BlurEvent event) { + hasFocus = false; + navKeyDown = false; + + if (BrowserInfo.get().isIE()) { + /* + * IE sometimes moves focus to a clicked table cell... (#7965) + * ...and sometimes it sends blur events even though the focus + * handler is still active. (#10464) + */ + Element focusedElement = WidgetUtil.getFocusedElement(); + if (Util.getConnectorForElement(client, getParent(), focusedElement) == this + && focusedElement != null + && focusedElement != scrollBodyPanel.getFocusElement()) { + /* + * Steal focus back to the focus handler if it was moved to some + * other part of the table. Avoid stealing focus in other cases. + */ + focus(); + return; + } + } + + if (isFocusable()) { + // Unfocus any row + setRowFocus(null); + } + } + + /** + * Removes a key from a range if the key is found in a selected range + * + * @param key + * The key to remove + */ + private void removeRowFromUnsentSelectionRanges(VScrollTableRow row) { + Collection newRanges = null; + for (Iterator iterator = selectedRowRanges.iterator(); iterator + .hasNext();) { + SelectionRange range = iterator.next(); + if (range.inRange(row)) { + // Split the range if given row is in range + Collection splitranges = range.split(row); + if (newRanges == null) { + newRanges = new ArrayList(); + } + newRanges.addAll(splitranges); + iterator.remove(); + } + } + if (newRanges != null) { + selectedRowRanges.addAll(newRanges); + } + } + + /** + * Can the Table be focused? + * + * @return True if the table can be focused, else false + */ + public boolean isFocusable() { + if (scrollBody != null && enabled) { + return !(!hasHorizontalScrollbar() && !hasVerticalScrollbar() && !isSelectable()); + } + return false; + } + + private boolean hasHorizontalScrollbar() { + return scrollBody.getOffsetWidth() > scrollBodyPanel.getOffsetWidth(); + } + + private boolean hasVerticalScrollbar() { + return scrollBody.getOffsetHeight() > scrollBodyPanel.getOffsetHeight(); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.Focusable#focus() + */ + + @Override + public void focus() { + if (isFocusable()) { + scrollBodyPanel.focus(); + } + } + + /** + * Sets the proper tabIndex for scrollBodyPanel (the focusable elemen in the + * component). + *

+ * If the component has no explicit tabIndex a zero is given (default + * tabbing order based on dom hierarchy) or -1 if the component does not + * need to gain focus. The component needs no focus if it has no scrollabars + * (not scrollable) and not selectable. Note that in the future shortcut + * actions may need focus. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public void setProperTabIndex() { + int storedScrollTop = 0; + int storedScrollLeft = 0; + + if (BrowserInfo.get().getOperaVersion() >= 11) { + // Workaround for Opera scroll bug when changing tabIndex (#6222) + storedScrollTop = scrollBodyPanel.getScrollPosition(); + storedScrollLeft = scrollBodyPanel.getHorizontalScrollPosition(); + } + + if (tabIndex == 0 && !isFocusable()) { + scrollBodyPanel.setTabIndex(-1); + } else { + scrollBodyPanel.setTabIndex(tabIndex); + } + + if (BrowserInfo.get().getOperaVersion() >= 11) { + // Workaround for Opera scroll bug when changing tabIndex (#6222) + scrollBodyPanel.setScrollPosition(storedScrollTop); + scrollBodyPanel.setHorizontalScrollPosition(storedScrollLeft); + } + } + + public void startScrollingVelocityTimer() { + if (scrollingVelocityTimer == null) { + scrollingVelocityTimer = new Timer() { + + @Override + public void run() { + scrollingVelocity++; + } + }; + scrollingVelocityTimer.scheduleRepeating(100); + } + } + + public void cancelScrollingVelocityTimer() { + if (scrollingVelocityTimer != null) { + // Remove velocityTimer if it exists and the Table is disabled + scrollingVelocityTimer.cancel(); + scrollingVelocityTimer = null; + scrollingVelocity = 10; + } + } + + /** + * + * @param keyCode + * @return true if the given keyCode is used by the table for navigation + */ + private boolean isNavigationKey(int keyCode) { + return keyCode == getNavigationUpKey() + || keyCode == getNavigationLeftKey() + || keyCode == getNavigationRightKey() + || keyCode == getNavigationDownKey() + || keyCode == getNavigationPageUpKey() + || keyCode == getNavigationPageDownKey() + || keyCode == getNavigationEndKey() + || keyCode == getNavigationStartKey(); + } + + public void lazyRevertFocusToRow(final VScrollTableRow currentlyFocusedRow) { + Scheduler.get().scheduleFinally(new ScheduledCommand() { + @Override + public void execute() { + if (currentlyFocusedRow != null) { + setRowFocus(currentlyFocusedRow); + } else { + VConsole.log("no row?"); + focusRowFromBody(); + } + scrollBody.ensureFocus(); + } + }); + } + + @Override + public Action[] getActions() { + if (bodyActionKeys == null) { + return new Action[] {}; + } + final Action[] actions = new Action[bodyActionKeys.length]; + for (int i = 0; i < actions.length; i++) { + final String actionKey = bodyActionKeys[i]; + Action bodyAction = new TreeAction(this, null, actionKey); + bodyAction.setCaption(getActionCaption(actionKey)); + bodyAction.setIconUrl(getActionIcon(actionKey)); + actions[i] = bodyAction; + } + return actions; + } + + @Override + public ApplicationConnection getClient() { + return client; + } + + @Override + public String getPaintableId() { + return paintableId; + } + + /** + * Add this to the element mouse down event by using element.setPropertyJSO + * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again + * when the mouse is depressed in the mouse up event. + * + * @return Returns the JSO preventing text selection + */ + private static native JavaScriptObject getPreventTextSelectionIEHack() + /*-{ + return function(){ return false; }; + }-*/; + + public void triggerLazyColumnAdjustment(boolean now) { + lazyAdjustColumnWidths.cancel(); + if (now) { + lazyAdjustColumnWidths.run(); + } else { + lazyAdjustColumnWidths.schedule(LAZY_COLUMN_ADJUST_TIMEOUT); + } + } + + private boolean isDynamicWidth() { + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + return paintable.isUndefinedWidth(); + } + + private boolean isDynamicHeight() { + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + if (paintable == null) { + // This should be refactored. As isDynamicHeight can be called from + // a timer it is possible that the connector has been unregistered + // when this method is called, causing getConnector to return null. + return false; + } + return paintable.isUndefinedHeight(); + } + + private void debug(String msg) { + if (enableDebug) { + VConsole.error(msg); + } + } + + public Widget getWidgetForPaintable() { + return this; + } + + private static final String SUBPART_HEADER = "header"; + private static final String SUBPART_FOOTER = "footer"; + private static final String SUBPART_ROW = "row"; + private static final String SUBPART_COL = "col"; + /** + * Matches header[ix] - used for extracting the index of the targeted header + * cell + */ + private static final RegExp SUBPART_HEADER_REGEXP = RegExp + .compile(SUBPART_HEADER + "\\[(\\d+)\\]"); + /** + * Matches footer[ix] - used for extracting the index of the targeted footer + * cell + */ + private static final RegExp SUBPART_FOOTER_REGEXP = RegExp + .compile(SUBPART_FOOTER + "\\[(\\d+)\\]"); + /** Matches row[ix] - used for extracting the index of the targeted row */ + private static final RegExp SUBPART_ROW_REGEXP = RegExp.compile(SUBPART_ROW + + "\\[(\\d+)]"); + /** Matches col[ix] - used for extracting the index of the targeted column */ + private static final RegExp SUBPART_ROW_COL_REGEXP = RegExp + .compile(SUBPART_ROW + "\\[(\\d+)\\]/" + SUBPART_COL + + "\\[(\\d+)\\]"); + + @Override + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + if (SUBPART_ROW_COL_REGEXP.test(subPart)) { + MatchResult result = SUBPART_ROW_COL_REGEXP.exec(subPart); + int rowIx = Integer.valueOf(result.getGroup(1)); + int colIx = Integer.valueOf(result.getGroup(2)); + VScrollTableRow row = scrollBody.getRowByRowIndex(rowIx); + if (row != null) { + Element rowElement = row.getElement(); + if (colIx < rowElement.getChildCount()) { + return rowElement.getChild(colIx).getFirstChild().cast(); + } + } + + } else if (SUBPART_ROW_REGEXP.test(subPart)) { + MatchResult result = SUBPART_ROW_REGEXP.exec(subPart); + int rowIx = Integer.valueOf(result.getGroup(1)); + VScrollTableRow row = scrollBody.getRowByRowIndex(rowIx); + if (row != null) { + return row.getElement(); + } + + } else if (SUBPART_HEADER_REGEXP.test(subPart)) { + MatchResult result = SUBPART_HEADER_REGEXP.exec(subPart); + int headerIx = Integer.valueOf(result.getGroup(1)); + HeaderCell headerCell = tHead.getHeaderCell(headerIx); + if (headerCell != null) { + return headerCell.getElement(); + } + + } else if (SUBPART_FOOTER_REGEXP.test(subPart)) { + MatchResult result = SUBPART_FOOTER_REGEXP.exec(subPart); + int footerIx = Integer.valueOf(result.getGroup(1)); + FooterCell footerCell = tFoot.getFooterCell(footerIx); + if (footerCell != null) { + return footerCell.getElement(); + } + } + // Nothing found. + return null; + } + + @Override + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + Widget widget = WidgetUtil.findWidget(subElement, null); + if (widget instanceof HeaderCell) { + return SUBPART_HEADER + "[" + tHead.visibleCells.indexOf(widget) + + "]"; + } else if (widget instanceof FooterCell) { + return SUBPART_FOOTER + "[" + tFoot.visibleCells.indexOf(widget) + + "]"; + } else if (widget instanceof VScrollTableRow) { + // a cell in a row + VScrollTableRow row = (VScrollTableRow) widget; + int rowIx = scrollBody.indexOf(row); + if (rowIx >= 0) { + int colIx = -1; + for (int ix = 0; ix < row.getElement().getChildCount(); ix++) { + if (row.getElement().getChild(ix).isOrHasChild(subElement)) { + colIx = ix; + break; + } + } + if (colIx >= 0) { + return SUBPART_ROW + "[" + rowIx + "]/" + SUBPART_COL + "[" + + colIx + "]"; + } + return SUBPART_ROW + "[" + rowIx + "]"; + } + } + // Nothing found. + return null; + } + + /** + * @since 7.2.6 + */ + public void onUnregister() { + if (addCloseHandler != null) { + addCloseHandler.removeHandler(); + } + } + + /* + * Return true if component need to perform some work and false otherwise. + */ + @Override + public boolean isWorkPending() { + return lazyAdjustColumnWidths.isRunning(); + } + + private static Logger getLogger() { + return Logger.getLogger(VScrollTable.class.getName()); + } + + public ChildMeasurementHint getChildMeasurementHint() { + return childMeasurementHint; + } + + public void setChildMeasurementHint(ChildMeasurementHint hint) { + childMeasurementHint = hint; + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VSlider.java b/client/src/main/java/com/vaadin/client/ui/VSlider.java new file mode 100644 index 0000000000..952c387539 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VSlider.java @@ -0,0 +1,675 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +// +package com.vaadin.client.ui; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.HasValue; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.WidgetUtil; +import com.vaadin.shared.ui.slider.SliderOrientation; + +public class VSlider extends SimpleFocusablePanel implements Field, + HasValue, SubPartAware { + + public static final String CLASSNAME = "v-slider"; + + /** + * Minimum size (width or height, depending on orientation) of the slider + * base. + */ + private static final int MIN_SIZE = 50; + + protected ApplicationConnection client; + + protected String id; + + protected boolean immediate; + protected boolean disabled; + protected boolean readonly; + + private int acceleration = 1; + protected double min; + protected double max; + protected int resolution; + protected Double value; + protected SliderOrientation orientation = SliderOrientation.HORIZONTAL; + + private final HTML feedback = new HTML("", false); + private final VOverlay feedbackPopup = new VOverlay(true, false, true) { + { + setOwner(VSlider.this); + } + + @Override + public void show() { + super.show(); + updateFeedbackPosition(); + } + }; + + /* DOM element for slider's base */ + private final Element base; + private final int BASE_BORDER_WIDTH = 1; + + /* DOM element for slider's handle */ + private final Element handle; + + /* DOM element for decrement arrow */ + private final Element smaller; + + /* DOM element for increment arrow */ + private final Element bigger; + + /* Temporary dragging/animation variables */ + private boolean dragging = false; + + private VLazyExecutor delayedValueUpdater = new VLazyExecutor(100, + new ScheduledCommand() { + + @Override + public void execute() { + fireValueChanged(); + acceleration = 1; + } + }); + + public VSlider() { + super(); + + base = DOM.createDiv(); + handle = DOM.createDiv(); + smaller = DOM.createDiv(); + bigger = DOM.createDiv(); + + setStyleName(CLASSNAME); + + getElement().appendChild(bigger); + getElement().appendChild(smaller); + getElement().appendChild(base); + base.appendChild(handle); + + // Hide initially + smaller.getStyle().setDisplay(Display.NONE); + bigger.getStyle().setDisplay(Display.NONE); + + sinkEvents(Event.MOUSEEVENTS | Event.ONMOUSEWHEEL | Event.KEYEVENTS + | Event.FOCUSEVENTS | Event.TOUCHEVENTS); + + feedbackPopup.setWidget(feedback); + } + + @Override + public void setStyleName(String style) { + updateStyleNames(style, false); + } + + @Override + public void setStylePrimaryName(String style) { + updateStyleNames(style, true); + } + + protected void updateStyleNames(String styleName, boolean isPrimaryStyleName) { + + feedbackPopup.removeStyleName(getStylePrimaryName() + "-feedback"); + removeStyleName(getStylePrimaryName() + "-vertical"); + + if (isPrimaryStyleName) { + super.setStylePrimaryName(styleName); + } else { + super.setStyleName(styleName); + } + + feedbackPopup.addStyleName(getStylePrimaryName() + "-feedback"); + base.setClassName(getStylePrimaryName() + "-base"); + handle.setClassName(getStylePrimaryName() + "-handle"); + smaller.setClassName(getStylePrimaryName() + "-smaller"); + bigger.setClassName(getStylePrimaryName() + "-bigger"); + + if (isVertical()) { + addStyleName(getStylePrimaryName() + "-vertical"); + } + } + + public void setFeedbackValue(double value) { + feedback.setText(String.valueOf(value)); + } + + private void updateFeedbackPosition() { + if (isVertical()) { + feedbackPopup.setPopupPosition( + handle.getAbsoluteLeft() + handle.getOffsetWidth(), + handle.getAbsoluteTop() + handle.getOffsetHeight() / 2 + - feedbackPopup.getOffsetHeight() / 2); + } else { + feedbackPopup.setPopupPosition( + handle.getAbsoluteLeft() + handle.getOffsetWidth() / 2 + - feedbackPopup.getOffsetWidth() / 2, + handle.getAbsoluteTop() - feedbackPopup.getOffsetHeight()); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void buildBase() { + final String styleAttribute = isVertical() ? "height" : "width"; + final String oppositeStyleAttribute = isVertical() ? "width" : "height"; + final String domProperty = isVertical() ? "offsetHeight" + : "offsetWidth"; + + // clear unnecessary opposite style attribute + base.getStyle().clearProperty(oppositeStyleAttribute); + + /* + * To resolve defect #13681 we should not return from method buildBase() + * if slider has no parentElement, because such operations as + * buildHandle() and setValues(), which are needed for Slider, are + * called at the end of method buildBase(). And these methods will not + * be called if there is no parentElement. So, instead of returning from + * method buildBase() if there is no parentElement "if condition" is + * applied to call code for parentElement only in case it exists. + */ + if (getElement().hasParentElement()) { + final Element p = getElement(); + if (p.getPropertyInt(domProperty) > MIN_SIZE) { + if (isVertical()) { + setHeight(); + } else { + base.getStyle().clearProperty(styleAttribute); + } + } else { + // Set minimum size and adjust after all components have + // (supposedly) been drawn completely. + base.getStyle().setPropertyPx(styleAttribute, MIN_SIZE); + Scheduler.get().scheduleDeferred(new Command() { + + @Override + public void execute() { + final Element p = getElement(); + if (p.getPropertyInt(domProperty) > (MIN_SIZE + 5) + || propertyNotNullOrEmpty(styleAttribute, p)) { + if (isVertical()) { + setHeight(); + } else { + base.getStyle().clearProperty(styleAttribute); + } + // Ensure correct position + setValue(value, false); + } + } + + // Style has non empty property + private boolean propertyNotNullOrEmpty( + final String styleAttribute, final Element p) { + return p.getStyle().getProperty(styleAttribute) != null + && !p.getStyle().getProperty(styleAttribute) + .isEmpty(); + } + }); + } + } + + if (!isVertical()) { + // Draw handle with a delay to allow base to gain maximum width + Scheduler.get().scheduleDeferred(new Command() { + @Override + public void execute() { + buildHandle(); + setValue(value, false); + } + }); + } else { + buildHandle(); + setValue(value, false); + } + + // TODO attach listeners for focusing and arrow keys + } + + void buildHandle() { + final String handleAttribute = isVertical() ? "marginTop" + : "marginLeft"; + final String oppositeHandleAttribute = isVertical() ? "marginLeft" + : "marginTop"; + + handle.getStyle().setProperty(handleAttribute, "0"); + + // clear unnecessary opposite handle attribute + handle.getStyle().clearProperty(oppositeHandleAttribute); + } + + @Override + public void onBrowserEvent(Event event) { + if (disabled || readonly) { + return; + } + final Element targ = DOM.eventGetTarget(event); + + if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) { + processMouseWheelEvent(event); + } else if (dragging || targ == handle) { + processHandleEvent(event); + } else if (targ == smaller) { + decreaseValue(true); + } else if (targ == bigger) { + increaseValue(true); + } else if (DOM.eventGetType(event) == Event.MOUSEEVENTS) { + processBaseEvent(event); + } else if ((BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYPRESS) + || (!BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYDOWN)) { + + if (handleNavigation(event.getKeyCode(), event.getCtrlKey(), + event.getShiftKey())) { + + feedbackPopup.show(); + + delayedValueUpdater.trigger(); + + DOM.eventPreventDefault(event); + DOM.eventCancelBubble(event, true); + } + } else if (targ.equals(getElement()) + && DOM.eventGetType(event) == Event.ONFOCUS) { + feedbackPopup.show(); + } else if (targ.equals(getElement()) + && DOM.eventGetType(event) == Event.ONBLUR) { + feedbackPopup.hide(); + } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) { + feedbackPopup.show(); + } + if (WidgetUtil.isTouchEvent(event)) { + event.preventDefault(); // avoid simulated events + event.stopPropagation(); + } + } + + private void processMouseWheelEvent(final Event event) { + final int dir = DOM.eventGetMouseWheelVelocityY(event); + + if (dir < 0) { + increaseValue(false); + } else { + decreaseValue(false); + } + + delayedValueUpdater.trigger(); + + DOM.eventPreventDefault(event); + DOM.eventCancelBubble(event, true); + } + + private void processHandleEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONMOUSEDOWN: + case Event.ONTOUCHSTART: + if (!disabled && !readonly) { + focus(); + feedbackPopup.show(); + dragging = true; + handle.setClassName(getStylePrimaryName() + "-handle"); + handle.addClassName(getStylePrimaryName() + "-handle-active"); + + DOM.setCapture(getElement()); + DOM.eventPreventDefault(event); // prevent selecting text + DOM.eventCancelBubble(event, true); + event.stopPropagation(); + } + break; + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + if (dragging) { + setValueByEvent(event, false); + updateFeedbackPosition(); + event.stopPropagation(); + } + break; + case Event.ONTOUCHEND: + feedbackPopup.hide(); + case Event.ONMOUSEUP: + // feedbackPopup.hide(); + dragging = false; + handle.setClassName(getStylePrimaryName() + "-handle"); + DOM.releaseCapture(getElement()); + setValueByEvent(event, true); + event.stopPropagation(); + break; + default: + break; + } + } + + private void processBaseEvent(Event event) { + if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) { + if (!disabled && !readonly && !dragging) { + setValueByEvent(event, true); + DOM.eventCancelBubble(event, true); + } + } + } + + private void decreaseValue(boolean updateToServer) { + setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)), + updateToServer); + } + + private void increaseValue(boolean updateToServer) { + setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)), + updateToServer); + } + + private void setValueByEvent(Event event, boolean updateToServer) { + double v = min; // Fallback to min + + final int coord = getEventPosition(event); + + final int handleSize, baseSize, baseOffset; + if (isVertical()) { + handleSize = handle.getOffsetHeight(); + baseSize = base.getOffsetHeight(); + baseOffset = base.getAbsoluteTop() - Window.getScrollTop() + - handleSize / 2; + } else { + handleSize = handle.getOffsetWidth(); + baseSize = base.getOffsetWidth(); + baseOffset = base.getAbsoluteLeft() - Window.getScrollLeft() + + handleSize / 2; + } + + if (isVertical()) { + v = ((baseSize - (coord - baseOffset)) / (double) (baseSize - handleSize)) + * (max - min) + min; + } else { + v = ((coord - baseOffset) / (double) (baseSize - handleSize)) + * (max - min) + min; + } + + if (v < min) { + v = min; + } else if (v > max) { + v = max; + } + + setValue(v, updateToServer); + } + + /** + * TODO consider extracting touches support to an impl class specific for + * webkit (only browser that really supports touches). + * + * @param event + * @return + */ + protected int getEventPosition(Event event) { + if (isVertical()) { + return WidgetUtil.getTouchOrMouseClientY(event); + } else { + return WidgetUtil.getTouchOrMouseClientX(event); + } + } + + public void iLayout() { + if (isVertical()) { + setHeight(); + } + // Update handle position + setValue(value, false); + } + + private void setHeight() { + // Calculate decoration size + base.getStyle().setHeight(0, Unit.PX); + base.getStyle().setOverflow(Overflow.HIDDEN); + int h = getElement().getOffsetHeight(); + if (h < MIN_SIZE) { + h = MIN_SIZE; + } + base.getStyle().setHeight(h, Unit.PX); + base.getStyle().clearOverflow(); + } + + private void fireValueChanged() { + ValueChangeEvent.fire(VSlider.this, value); + } + + /** + * Handles the keyboard events handled by the Slider + * + * @param event + * The keyboard event received + * @return true iff the navigation event was handled + */ + public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { + + // No support for ctrl moving + if (ctrl) { + return false; + } + + if ((keycode == getNavigationUpKey() && isVertical()) + || (keycode == getNavigationRightKey() && !isVertical())) { + if (shift) { + for (int a = 0; a < acceleration; a++) { + increaseValue(false); + } + acceleration++; + } else { + increaseValue(false); + } + return true; + } else if (keycode == getNavigationDownKey() && isVertical() + || (keycode == getNavigationLeftKey() && !isVertical())) { + if (shift) { + for (int a = 0; a < acceleration; a++) { + decreaseValue(false); + } + acceleration++; + } else { + decreaseValue(false); + } + return true; + } + + return false; + } + + /** + * Get the key that increases the vertical slider. By default it is the up + * arrow key but by overriding this you can change the key to whatever you + * want. + * + * @return The keycode of the key + */ + protected int getNavigationUpKey() { + return KeyCodes.KEY_UP; + } + + /** + * Get the key that decreases the vertical slider. By default it is the down + * arrow key but by overriding this you can change the key to whatever you + * want. + * + * @return The keycode of the key + */ + protected int getNavigationDownKey() { + return KeyCodes.KEY_DOWN; + } + + /** + * Get the key that decreases the horizontal slider. By default it is the + * left arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationLeftKey() { + return KeyCodes.KEY_LEFT; + } + + /** + * Get the key that increases the horizontal slider. By default it is the + * right arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationRightKey() { + return KeyCodes.KEY_RIGHT; + } + + public void setConnection(ApplicationConnection client) { + this.client = client; + } + + public void setId(String id) { + this.id = id; + } + + public void setImmediate(boolean immediate) { + this.immediate = immediate; + } + + public void setDisabled(boolean disabled) { + this.disabled = disabled; + } + + public void setReadOnly(boolean readonly) { + this.readonly = readonly; + } + + private boolean isVertical() { + return orientation == SliderOrientation.VERTICAL; + } + + public void setOrientation(SliderOrientation orientation) { + if (this.orientation != orientation) { + this.orientation = orientation; + updateStyleNames(getStylePrimaryName(), true); + } + } + + public void setMinValue(double value) { + min = value; + } + + public void setMaxValue(double value) { + max = value; + } + + public void setResolution(int resolution) { + this.resolution = resolution; + } + + @Override + public HandlerRegistration addValueChangeHandler( + ValueChangeHandler handler) { + return addHandler(handler, ValueChangeEvent.getType()); + } + + @Override + public Double getValue() { + return value; + } + + @Override + public void setValue(Double value) { + if (value < min) { + value = min; + } else if (value > max) { + value = max; + } + + // Update handle position + final String styleAttribute = isVertical() ? "marginTop" : "marginLeft"; + final String domProperty = isVertical() ? "offsetHeight" + : "offsetWidth"; + final int handleSize = handle.getPropertyInt(domProperty); + final int baseSize = base.getPropertyInt(domProperty) + - (2 * BASE_BORDER_WIDTH); + + final int range = baseSize - handleSize; + double v = value.doubleValue(); + + // Round value to resolution + if (resolution > 0) { + v = Math.round(v * Math.pow(10, resolution)); + v = v / Math.pow(10, resolution); + } else { + v = Math.round(v); + } + final double valueRange = max - min; + double p = 0; + if (valueRange > 0) { + p = range * ((v - min) / valueRange); + } + if (p < 0) { + p = 0; + } + if (isVertical()) { + p = range - p; + } + final double pos = p; + + handle.getStyle().setPropertyPx(styleAttribute, (int) Math.round(pos)); + + // Update value + this.value = new Double(v); + setFeedbackValue(v); + } + + @Override + public void setValue(Double value, boolean fireEvents) { + if (value == null) { + return; + } + + setValue(value); + + if (fireEvents) { + fireValueChanged(); + } + } + + @Override + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + if (subPart.equals("popup")) { + feedbackPopup.show(); + return feedbackPopup.getElement(); + } + return null; + } + + @Override + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + if (feedbackPopup.getElement().isOrHasChild(subElement)) { + return "popup"; + } + return null; + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VSplitPanelHorizontal.java b/client/src/main/java/com/vaadin/client/ui/VSplitPanelHorizontal.java new file mode 100644 index 0000000000..1a3e699b20 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VSplitPanelHorizontal.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.shared.ui.Orientation; + +public class VSplitPanelHorizontal extends VAbstractSplitPanel { + + public VSplitPanelHorizontal() { + super(Orientation.HORIZONTAL); + } + + @Override + protected void startResize() { + if (getFirstWidget() != null && isWidgetFullWidth(getFirstWidget())) { + getFirstContainer().getStyle().setOverflow(Overflow.HIDDEN); + } + + if (getSecondWidget() != null && isWidgetFullWidth(getSecondWidget())) { + getSecondContainer().getStyle().setOverflow(Overflow.HIDDEN); + } + } + + @Override + protected void stopResize() { + getFirstContainer().getStyle().clearOverflow(); + getSecondContainer().getStyle().clearOverflow(); + } + + private boolean isWidgetFullWidth(Widget w) { + return w.getElement().getStyle().getWidth().equals("100%"); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VSplitPanelVertical.java b/client/src/main/java/com/vaadin/client/ui/VSplitPanelVertical.java new file mode 100644 index 0000000000..7baed03ca3 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VSplitPanelVertical.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.shared.ui.Orientation; + +public class VSplitPanelVertical extends VAbstractSplitPanel { + + public VSplitPanelVertical() { + super(Orientation.VERTICAL); + } + + @Override + protected void startResize() { + if (getFirstWidget() != null && isWidgetFullHeight(getFirstWidget())) { + getFirstContainer().getStyle().setOverflow(Overflow.HIDDEN); + } + + if (getSecondWidget() != null && isWidgetFullHeight(getSecondWidget())) { + getSecondContainer().getStyle().setOverflow(Overflow.HIDDEN); + } + } + + @Override + protected void stopResize() { + getFirstContainer().getStyle().clearOverflow(); + getSecondContainer().getStyle().clearOverflow(); + } + + private boolean isWidgetFullHeight(Widget w) { + return w.getElement().getStyle().getHeight().equals("100%"); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VTabsheet.java b/client/src/main/java/com/vaadin/client/ui/VTabsheet.java new file mode 100644 index 0000000000..e196870348 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VTabsheet.java @@ -0,0 +1,1998 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.Iterator; +import java.util.List; + +import com.google.gwt.aria.client.Id; +import com.google.gwt.aria.client.LiveValue; +import com.google.gwt.aria.client.Roles; +import com.google.gwt.aria.client.SelectedValue; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.dom.client.TableCellElement; +import com.google.gwt.dom.client.TableElement; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.HasBlurHandlers; +import com.google.gwt.event.dom.client.HasFocusHandlers; +import com.google.gwt.event.dom.client.HasKeyDownHandlers; +import com.google.gwt.event.dom.client.HasMouseDownHandlers; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.MouseDownEvent; +import com.google.gwt.event.dom.client.MouseDownHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.regexp.shared.MatchResult; +import com.google.gwt.regexp.shared.RegExp; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.user.client.ui.impl.FocusImpl; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ComputedStyle; +import com.vaadin.client.Focusable; +import com.vaadin.client.TooltipInfo; +import com.vaadin.client.VCaption; +import com.vaadin.client.VTooltip; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.aria.AriaHelper; +import com.vaadin.shared.AbstractComponentState; +import com.vaadin.shared.ComponentConstants; +import com.vaadin.shared.EventId; +import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc; +import com.vaadin.shared.ui.ComponentStateUtil; +import com.vaadin.shared.ui.tabsheet.TabState; +import com.vaadin.shared.ui.tabsheet.TabsheetServerRpc; +import com.vaadin.shared.ui.tabsheet.TabsheetState; + +public class VTabsheet extends VTabsheetBase implements Focusable, SubPartAware { + + private static final String PREV_SCROLLER_DISABLED_CLASSNAME = "Prev-disabled"; + + private static class VCloseEvent { + private Tab tab; + + VCloseEvent(Tab tab) { + this.tab = tab; + } + + public Tab getTab() { + return tab; + } + + } + + private interface VCloseHandler { + public void onClose(VCloseEvent event); + } + + /** + * Representation of a single "tab" shown in the TabBar + * + */ + public static class Tab extends SimplePanel implements HasFocusHandlers, + HasBlurHandlers, HasMouseDownHandlers, HasKeyDownHandlers { + private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell"; + private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME + + "-first"; + private static final String TD_SELECTED_CLASSNAME = TD_CLASSNAME + + "-selected"; + private static final String TD_SELECTED_FIRST_CLASSNAME = TD_SELECTED_CLASSNAME + + "-first"; + private static final String TD_FOCUS_CLASSNAME = TD_CLASSNAME + + "-focus"; + private static final String TD_FOCUS_FIRST_CLASSNAME = TD_FOCUS_CLASSNAME + + "-first"; + private static final String TD_DISABLED_CLASSNAME = TD_CLASSNAME + + "-disabled"; + + private static final String DIV_CLASSNAME = CLASSNAME + "-tabitem"; + private static final String DIV_SELECTED_CLASSNAME = DIV_CLASSNAME + + "-selected"; + private static final String DIV_FOCUS_CLASSNAME = DIV_CLASSNAME + + "-focus"; + + private TabCaption tabCaption; + Element td = getElement(); + private VCloseHandler closeHandler; + + private boolean enabledOnServer = true; + private Element div; + private TabBar tabBar; + private boolean hiddenOnServer = false; + + private String styleName; + + private String id; + + private Tab(TabBar tabBar) { + super(DOM.createTD()); + this.tabBar = tabBar; + setStyleName(td, TD_CLASSNAME); + + Roles.getTabRole().set(getElement()); + Roles.getTabRole().setAriaSelectedState(getElement(), + SelectedValue.FALSE); + + div = DOM.createDiv(); + setTabulatorIndex(-1); + setStyleName(div, DIV_CLASSNAME); + + DOM.appendChild(td, div); + + tabCaption = new TabCaption(this); + add(tabCaption); + + Roles.getTabRole().setAriaLabelledbyProperty(getElement(), + Id.of(tabCaption.getElement())); + } + + public boolean isHiddenOnServer() { + return hiddenOnServer; + } + + public void setHiddenOnServer(boolean hiddenOnServer) { + this.hiddenOnServer = hiddenOnServer; + Roles.getTabRole().setAriaHiddenState(getElement(), hiddenOnServer); + } + + @Override + protected com.google.gwt.user.client.Element getContainerElement() { + // Attach caption element to div, not td + return DOM.asOld(div); + } + + public boolean isEnabledOnServer() { + return enabledOnServer; + } + + public void setEnabledOnServer(boolean enabled) { + enabledOnServer = enabled; + Roles.getTabRole().setAriaDisabledState(getElement(), !enabled); + + setStyleName(td, TD_DISABLED_CLASSNAME, !enabled); + if (!enabled) { + focusImpl.setTabIndex(td, -1); + } + } + + public void addClickHandler(ClickHandler handler) { + tabCaption.addClickHandler(handler); + } + + public void setCloseHandler(VCloseHandler closeHandler) { + this.closeHandler = closeHandler; + } + + /** + * Toggles the style names for the Tab + * + * @param selected + * true if the Tab is selected + * @param first + * true if the Tab is the first visible Tab + */ + public void setStyleNames(boolean selected, boolean first) { + setStyleNames(selected, first, false); + } + + public void setStyleNames(boolean selected, boolean first, + boolean keyboardFocus) { + setStyleName(td, TD_FIRST_CLASSNAME, first); + setStyleName(td, TD_SELECTED_CLASSNAME, selected); + setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first); + setStyleName(div, DIV_SELECTED_CLASSNAME, selected); + setStyleName(td, TD_FOCUS_CLASSNAME, keyboardFocus); + setStyleName(td, TD_FOCUS_FIRST_CLASSNAME, keyboardFocus && first); + setStyleName(div, DIV_FOCUS_CLASSNAME, keyboardFocus); + } + + public void setTabulatorIndex(int tabIndex) { + getElement().setTabIndex(tabIndex); + } + + public boolean isClosable() { + return tabCaption.isClosable(); + } + + public void onClose() { + closeHandler.onClose(new VCloseEvent(this)); + } + + public VTabsheet getTabsheet() { + return tabBar.getTabsheet(); + } + + private void updateFromState(TabState tabState) { + tabCaption.setCaptionAsHtml(getTabsheet().isTabCaptionsAsHtml()); + tabCaption.update(tabState); + // Apply the styleName set for the tab + String newStyleName = tabState.styleName; + // Find the nth td element + if (newStyleName != null && !newStyleName.isEmpty()) { + if (!newStyleName.equals(styleName)) { + // If we have a new style name + if (styleName != null && !styleName.isEmpty()) { + // Remove old style name if present + td.removeClassName(TD_CLASSNAME + "-" + styleName); + } + // Set new style name + td.addClassName(TD_CLASSNAME + "-" + newStyleName); + styleName = newStyleName; + } + } else if (styleName != null) { + // Remove the set stylename if no stylename is present in the + // uidl + td.removeClassName(TD_CLASSNAME + "-" + styleName); + styleName = null; + } + + String newId = tabState.id; + if (newId != null && !newId.isEmpty()) { + td.setId(newId); + id = newId; + } else if (id != null) { + td.removeAttribute("id"); + id = null; + } + } + + public void recalculateCaptionWidth() { + tabCaption.setWidth(tabCaption.getRequiredWidth() + "px"); + } + + @Override + public HandlerRegistration addFocusHandler(FocusHandler handler) { + return addDomHandler(handler, FocusEvent.getType()); + } + + @Override + public HandlerRegistration addBlurHandler(BlurHandler handler) { + return addDomHandler(handler, BlurEvent.getType()); + } + + @Override + public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) { + return addDomHandler(handler, MouseDownEvent.getType()); + } + + @Override + public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) { + return addDomHandler(handler, KeyDownEvent.getType()); + } + + public void focus() { + getTabsheet().scrollIntoView(this); + focusImpl.focus(td); + } + + public void blur() { + focusImpl.blur(td); + } + + public boolean hasTooltip() { + return tabCaption.getTooltipInfo() != null; + } + + public TooltipInfo getTooltipInfo() { + return tabCaption.getTooltipInfo(); + } + + public void setAssistiveDescription(String descriptionId) { + Roles.getTablistRole().setAriaDescribedbyProperty(getElement(), + Id.of(descriptionId)); + } + + public void removeAssistiveDescription() { + Roles.getTablistRole().removeAriaDescribedbyProperty(getElement()); + } + } + + public static class TabCaption extends VCaption { + + private boolean closable = false; + private Element closeButton; + private Tab tab; + + TabCaption(Tab tab) { + super(tab.getTabsheet().connector.getConnection()); + this.tab = tab; + + AriaHelper.ensureHasId(getElement()); + } + + private boolean update(TabState tabState) { + if (tabState.description != null || tabState.componentError != null) { + setTooltipInfo(new TooltipInfo(tabState.description, + tabState.componentError, this)); + } else { + setTooltipInfo(null); + } + + // TODO need to call this instead of super because the caption does + // not have an owner + String captionString = tabState.caption.isEmpty() ? null + : tabState.caption; + boolean ret = updateCaptionWithoutOwner(captionString, + !tabState.enabled, hasAttribute(tabState.description), + hasAttribute(tabState.componentError), + tab.getTabsheet().connector + .getResourceUrl(ComponentConstants.ICON_RESOURCE + + tabState.key), tabState.iconAltText); + + setClosable(tabState.closable); + + return ret; + } + + private boolean hasAttribute(String string) { + return string != null && !string.trim().isEmpty(); + } + + private VTabsheet getTabsheet() { + return tab.getTabsheet(); + } + + @Override + public void onBrowserEvent(Event event) { + if (closable && event.getTypeInt() == Event.ONCLICK + && event.getEventTarget().cast() == closeButton) { + tab.onClose(); + event.stopPropagation(); + event.preventDefault(); + } + + super.onBrowserEvent(event); + + if (event.getTypeInt() == Event.ONLOAD) { + getTabsheet().tabSizeMightHaveChanged(getTab()); + } + } + + public Tab getTab() { + return tab; + } + + public void setClosable(boolean closable) { + this.closable = closable; + if (closable && closeButton == null) { + closeButton = DOM.createSpan(); + closeButton.setInnerHTML("×"); + closeButton + .setClassName(VTabsheet.CLASSNAME + "-caption-close"); + + Roles.getTabRole().setAriaHiddenState(closeButton, true); + Roles.getTabRole().setAriaDisabledState(closeButton, true); + + getElement().appendChild(closeButton); + } else if (!closable && closeButton != null) { + getElement().removeChild(closeButton); + closeButton = null; + } + if (closable) { + addStyleDependentName("closable"); + } else { + removeStyleDependentName("closable"); + } + } + + public boolean isClosable() { + return closable; + } + + @Override + public int getRequiredWidth() { + int width = super.getRequiredWidth(); + if (closeButton != null) { + width += WidgetUtil.getRequiredWidth(closeButton); + } + return width; + } + + public com.google.gwt.user.client.Element getCloseButton() { + return DOM.asOld(closeButton); + } + + } + + static class TabBar extends ComplexPanel implements VCloseHandler { + + private final Element tr = DOM.createTR(); + + private final Element spacerTd = DOM.createTD(); + + private Tab selected; + + private VTabsheet tabsheet; + + TabBar(VTabsheet tabsheet) { + this.tabsheet = tabsheet; + + Element el = DOM.createTable(); + Roles.getPresentationRole().set(el); + + Element tbody = DOM.createTBody(); + DOM.appendChild(el, tbody); + DOM.appendChild(tbody, tr); + setStyleName(spacerTd, CLASSNAME + "-spacertd"); + DOM.appendChild(tr, spacerTd); + DOM.appendChild(spacerTd, DOM.createDiv()); + + setElement(el); + } + + @Override + public void onClose(VCloseEvent event) { + Tab tab = event.getTab(); + if (!tab.isEnabledOnServer()) { + return; + } + int tabIndex = getWidgetIndex(tab); + getTabsheet().sendTabClosedEvent(tabIndex); + } + + protected com.google.gwt.user.client.Element getContainerElement() { + return DOM.asOld(tr); + } + + /** + * Gets the number of tabs from the tab bar. + * + * @return the number of tabs from the tab bar. + */ + public int getTabCount() { + return getWidgetCount(); + } + + /** + * Adds a tab to the tab bar. + * + * @return the added tab. + */ + public Tab addTab() { + Tab t = new Tab(this); + int tabIndex = getTabCount(); + + // Logical attach + insert(t, tr, tabIndex, true); + + if (tabIndex == 0) { + // Set the "first" style + t.setStyleNames(false, true); + } + + getTabsheet().selectionHandler.registerTab(t); + + t.setCloseHandler(this); + + return t; + } + + /** + * Gets the tab sheet instance where the tab bar is attached to. + * + * @return the tab sheet instance where the tab bar is attached to. + */ + public VTabsheet getTabsheet() { + return tabsheet; + } + + public Tab getTab(int index) { + if (index < 0 || index >= getTabCount()) { + return null; + } + return (Tab) super.getWidget(index); + } + + private int getTabIndex(String tabId) { + if (tabId == null) { + return -1; + } + for (int i = 0; i < getTabCount(); i++) { + if (tabId.equals(getTab(i).id)) { + return i; + } + } + return -1; + } + + public void selectTab(int index) { + final Tab newSelected = getTab(index); + final Tab oldSelected = selected; + + newSelected.setStyleNames(true, isFirstVisibleTab(index), true); + newSelected.setTabulatorIndex(getTabsheet().tabulatorIndex); + Roles.getTabRole().setAriaSelectedState(newSelected.getElement(), + SelectedValue.TRUE); + + if (oldSelected != null && oldSelected != newSelected) { + oldSelected.setStyleNames(false, + isFirstVisibleTab(getWidgetIndex(oldSelected))); + oldSelected.setTabulatorIndex(-1); + + Roles.getTabRole().setAriaSelectedState( + oldSelected.getElement(), SelectedValue.FALSE); + } + + // Update the field holding the currently selected tab + selected = newSelected; + + // The selected tab might need more (or less) space + newSelected.recalculateCaptionWidth(); + getTab(tabsheet.activeTabIndex).recalculateCaptionWidth(); + } + + public Tab navigateTab(int fromIndex, int toIndex) { + Tab newNavigated = getTab(toIndex); + if (newNavigated == null) { + throw new IllegalArgumentException( + "Tab at provided index toIndex was not found"); + } + + Tab oldNavigated = getTab(fromIndex); + newNavigated.setStyleNames(newNavigated.equals(selected), + isFirstVisibleTab(toIndex), true); + + if (oldNavigated != null && fromIndex != toIndex) { + oldNavigated.setStyleNames(oldNavigated.equals(selected), + isFirstVisibleTab(fromIndex), false); + } + + return newNavigated; + } + + public void removeTab(int i) { + Tab tab = getTab(i); + if (tab == null) { + return; + } + + remove(tab); + + /* + * If this widget was selected we need to unmark it as the last + * selected + */ + if (tab == selected) { + selected = null; + } + // FIXME: Shouldn't something be selected instead? + + int scrollerIndexCandidate = getTabIndex(getTabsheet().scrollerPositionTabId); + if (scrollerIndexCandidate < 0) { + // The tab with id scrollerPositionTabId has been removed + scrollerIndexCandidate = getTabsheet().scrollerIndex; + } + scrollerIndexCandidate = selectNewShownTab(scrollerIndexCandidate); + if (scrollerIndexCandidate >= 0 + && scrollerIndexCandidate < getTabCount()) { + getTabsheet().scrollIntoView(getTab(scrollerIndexCandidate)); + } + } + + private int selectNewShownTab(int oldPosition) { + // After removing a tab, find a new scroll position. In most + // cases the scroll position does not change, but if the tab + // at the scroll position was removed, need to find a nearby + // tab that is visible. + for (int i = oldPosition; i < getTabCount(); i++) { + Tab tab = getTab(i); + if (!tab.isHiddenOnServer()) { + return i; + } + } + + for (int i = oldPosition - 1; i >= 0; i--) { + Tab tab = getTab(i); + if (!tab.isHiddenOnServer()) { + return i; + } + } + + return -1; + } + + private boolean isFirstVisibleTab(int index) { + return getFirstVisibleTab() == index; + } + + /** + * Returns the index of the first visible tab on the server + */ + private int getFirstVisibleTab() { + return getNextVisibleTab(-1); + } + + /** + * Find the next visible tab. Returns -1 if none is found. + * + * @param i + * @return + */ + private int getNextVisibleTab(int i) { + int tabs = getTabCount(); + do { + i++; + } while (i < tabs && getTab(i).isHiddenOnServer()); + + if (i == tabs) { + return -1; + } else { + return i; + } + } + + /** + * Returns the index of the first visible tab in browser. + */ + private int getFirstVisibleTabClient() { + int tabs = getTabCount(); + int i = 0; + while (i < tabs && !getTab(i).isVisible()) { + i++; + } + + if (i == tabs) { + return -1; + } else { + return i; + } + } + + /** + * Find the previous visible tab. Returns -1 if none is found. + * + * @param i + * @return + */ + private int getPreviousVisibleTab(int i) { + do { + i--; + } while (i >= 0 && getTab(i).isHiddenOnServer()); + + return i; + + } + + public int scrollLeft(int currentFirstVisible) { + int prevVisible = getPreviousVisibleTab(currentFirstVisible); + if (prevVisible == -1) { + return -1; + } + + Tab newFirst = getTab(prevVisible); + newFirst.setVisible(true); + newFirst.recalculateCaptionWidth(); + + return prevVisible; + } + + public int scrollRight(int currentFirstVisible) { + int nextVisible = getNextVisibleTab(currentFirstVisible); + if (nextVisible == -1) { + return -1; + } + Tab currentFirst = getTab(currentFirstVisible); + currentFirst.setVisible(false); + currentFirst.recalculateCaptionWidth(); + return nextVisible; + } + + private void recalculateCaptionWidths() { + for (int i = 0; i < getTabCount(); ++i) { + getTab(i).recalculateCaptionWidth(); + } + } + } + + // TODO using the CLASSNAME directly makes primaryStyleName for TabSheet of + // very limited use - all use of style names should be refactored in the + // future + public static final String CLASSNAME = TabsheetState.PRIMARY_STYLE_NAME; + + public static final String TABS_CLASSNAME = CLASSNAME + "-tabcontainer"; + public static final String SCROLLER_CLASSNAME = CLASSNAME + "-scroller"; + + /** For internal use only. May be removed or replaced in the future. */ + // tabbar and 'scroller' container + public final Element tabs; + + /** + * The tabindex property (position in the browser's focus cycle.) Named like + * this to avoid confusion with activeTabIndex. + */ + int tabulatorIndex = 0; + + private static final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel(); + + // tab-scroller element + private final Element scroller; + // tab-scroller next button element + private final Element scrollerNext; + // tab-scroller prev button element + private final Element scrollerPrev; + + /** + * The index of the first visible tab (when scrolled) + */ + private int scrollerIndex = 0; + /** + * The id of the tab at position scrollerIndex. This is used for keeping the + * scroll position unchanged when a tab is removed from the server side and + * the removed tab lies to the left of the current scroll position. For + * other cases scrollerIndex alone would be sufficient. Since the tab at the + * current scroll position can be removed, scrollerIndex is required in + * addition to this variable. + */ + private String scrollerPositionTabId; + + final TabBar tb = new TabBar(this); + /** For internal use only. May be removed or replaced in the future. */ + protected final VTabsheetPanel tabPanel = new VTabsheetPanel(); + /** For internal use only. May be removed or replaced in the future. */ + public final Element contentNode; + + private final Element deco; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean waitingForResponse; + + private String currentStyle; + + /** + * @return Whether the tab could be selected or not. + */ + private boolean canSelectTab(final int tabIndex) { + Tab tab = tb.getTab(tabIndex); + if (getApplicationConnection() == null || disabled + || waitingForResponse) { + return false; + } + if (!tab.isEnabledOnServer() || tab.isHiddenOnServer()) { + return false; + } + + // Note that we return true when tabIndex == activeTabIndex; the active + // tab could be selected, it's just a no-op. + return true; + } + + /** + * Load the content of a tab of the provided index. + * + * @param index + * of the tab to load + * + * @return true if the specified sheet gets loaded, otherwise false. + */ + public boolean loadTabSheet(int tabIndex) { + if (activeTabIndex != tabIndex && canSelectTab(tabIndex)) { + tb.selectTab(tabIndex); + + activeTabIndex = tabIndex; + + addStyleDependentName("loading"); + // Hide the current contents so a loading indicator can be shown + // instead + getCurrentlyDisplayedWidget().getElement().getParentElement() + .getStyle().setVisibility(Visibility.HIDDEN); + + getRpcProxy().setSelected(tabKeys.get(tabIndex).toString()); + + waitingForResponse = true; + + tb.getTab(tabIndex).focus(); // move keyboard focus to active tab + + return true; + } + + return false; + } + + /** + * Returns the currently displayed widget in the tab panel. + * + * @since 7.2 + * @return currently displayed content widget + */ + public Widget getCurrentlyDisplayedWidget() { + return tabPanel.getWidget(tabPanel.getVisibleWidget()); + } + + /** + * Returns the client to server RPC proxy for the tabsheet. + * + * @since 7.2 + * @return RPC proxy + */ + protected TabsheetServerRpc getRpcProxy() { + return connector.getRpcProxy(TabsheetServerRpc.class); + } + + /** + * For internal use only. + * + * Avoid using this method directly and use appropriate superclass methods + * where applicable. + * + * @deprecated since 7.2 - use more specific methods instead (getRpcProxy(), + * getConnectorForWidget(Widget) etc.) + * @return ApplicationConnection + */ + @Deprecated + public ApplicationConnection getApplicationConnection() { + return client; + } + + private VTooltip getVTooltip() { + return getApplicationConnection().getVTooltip(); + } + + public void tabSizeMightHaveChanged(Tab tab) { + // icon onloads may change total width of tabsheet + if (isDynamicWidth()) { + updateDynamicWidth(); + } + updateTabScroller(); + + } + + void sendTabClosedEvent(int tabIndex) { + getRpcProxy().closeTab(tabKeys.get(tabIndex)); + } + + public VTabsheet() { + super(CLASSNAME); + + // Tab scrolling + getElement().getStyle().setOverflow(Overflow.HIDDEN); + tabs = DOM.createDiv(); + DOM.setElementProperty(tabs, "className", TABS_CLASSNAME); + Roles.getTablistRole().set(tabs); + Roles.getTablistRole().setAriaLiveProperty(tabs, LiveValue.OFF); + scroller = DOM.createDiv(); + Roles.getTablistRole().setAriaHiddenState(scroller, true); + + DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME); + + scrollerPrev = DOM.createButton(); + scrollerPrev.setTabIndex(-1); + DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME + + "Prev"); + Roles.getTablistRole().setAriaHiddenState(scrollerPrev, true); + DOM.sinkEvents(scrollerPrev, Event.ONCLICK | Event.ONMOUSEDOWN); + + scrollerNext = DOM.createButton(); + scrollerNext.setTabIndex(-1); + DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME + + "Next"); + Roles.getTablistRole().setAriaHiddenState(scrollerNext, true); + DOM.sinkEvents(scrollerNext, Event.ONCLICK | Event.ONMOUSEDOWN); + + DOM.appendChild(getElement(), tabs); + + // Tabs + tabPanel.setStyleName(CLASSNAME + "-tabsheetpanel"); + contentNode = DOM.createDiv(); + Roles.getTabpanelRole().set(contentNode); + + deco = DOM.createDiv(); + + tb.setStyleName(CLASSNAME + "-tabs"); + DOM.setElementProperty(contentNode, "className", CLASSNAME + "-content"); + DOM.setElementProperty(deco, "className", CLASSNAME + "-deco"); + + add(tb, tabs); + DOM.appendChild(scroller, scrollerPrev); + DOM.appendChild(scroller, scrollerNext); + + DOM.appendChild(getElement(), contentNode); + add(tabPanel, contentNode); + DOM.appendChild(getElement(), deco); + + DOM.appendChild(tabs, scroller); + + // TODO Use for Safari only. Fix annoying 1px first cell in TabBar. + // DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM + // .getFirstChild(tb.getElement()))), "display", "none"); + + } + + @Override + public void onBrowserEvent(Event event) { + com.google.gwt.dom.client.Element eventTarget = DOM + .eventGetTarget(event); + + if (event.getTypeInt() == Event.ONCLICK) { + + // Tab scrolling + if (eventTarget == scrollerPrev || eventTarget == scrollerNext) { + scrollAccordingToScrollTarget(eventTarget); + + event.stopPropagation(); + } + + } else if (event.getTypeInt() == Event.ONMOUSEDOWN) { + + if (eventTarget == scrollerPrev || eventTarget == scrollerNext) { + // In case the focus was previously on a Tab, we need to cancel + // the upcoming blur on the Tab which will follow this mouse + // down event. + focusBlurManager.cancelNextBlurSchedule(); + + return; + } + } + + super.onBrowserEvent(event); + } + + /* + * Scroll the tab bar according to the last scrollTarget (the scroll button + * pressed). + */ + private void scrollAccordingToScrollTarget( + com.google.gwt.dom.client.Element scrollTarget) { + if (scrollTarget == null) { + return; + } + + int newFirstIndex = -1; + + // Scroll left. + if (isScrolledTabs() && scrollTarget == scrollerPrev) { + newFirstIndex = tb.scrollLeft(scrollerIndex); + + // Scroll right. + } else if (isClippedTabs() && scrollTarget == scrollerNext) { + newFirstIndex = tb.scrollRight(scrollerIndex); + } + + if (newFirstIndex != -1) { + scrollerIndex = newFirstIndex; + scrollerPositionTabId = tb.getTab(scrollerIndex).id; + updateTabScroller(); + } + + // For this to work well, make sure the method gets called only from + // user events. + selectionHandler.focusTabAtIndex(scrollerIndex); + } + + /** + * Checks if the tab with the selected index has been scrolled out of the + * view (on the left side). + * + * @param index + * @return + */ + private boolean scrolledOutOfView(int index) { + return scrollerIndex > index; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void handleStyleNames(AbstractComponentState state) { + // Add proper stylenames for all elements (easier to prevent unwanted + // style inheritance) + if (ComponentStateUtil.hasStyles(state)) { + final List styles = state.styles; + if (currentStyle == null || !currentStyle.equals(styles.toString())) { + currentStyle = styles.toString(); + final String tabsBaseClass = TABS_CLASSNAME; + String tabsClass = tabsBaseClass; + final String contentBaseClass = CLASSNAME + "-content"; + String contentClass = contentBaseClass; + final String decoBaseClass = CLASSNAME + "-deco"; + String decoClass = decoBaseClass; + for (String style : styles) { + tb.addStyleDependentName(style); + tabsClass += " " + tabsBaseClass + "-" + style; + contentClass += " " + contentBaseClass + "-" + style; + decoClass += " " + decoBaseClass + "-" + style; + } + DOM.setElementProperty(tabs, "className", tabsClass); + DOM.setElementProperty(contentNode, "className", contentClass); + DOM.setElementProperty(deco, "className", decoClass); + } + } else { + tb.setStyleName(CLASSNAME + "-tabs"); + DOM.setElementProperty(tabs, "className", TABS_CLASSNAME); + DOM.setElementProperty(contentNode, "className", CLASSNAME + + "-content"); + DOM.setElementProperty(deco, "className", CLASSNAME + "-deco"); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateDynamicWidth() { + // Find width consumed by tabs + TableCellElement spacerCell = ((TableElement) tb.getElement().cast()) + .getRows().getItem(0).getCells().getItem(tb.getTabCount()); + + int spacerWidth = spacerCell.getOffsetWidth(); + DivElement div = (DivElement) spacerCell.getFirstChildElement(); + + int spacerMinWidth = spacerCell.getOffsetWidth() - div.getOffsetWidth(); + + int tabsWidth = tb.getOffsetWidth() - spacerWidth + spacerMinWidth; + + // Find content width + Style style = tabPanel.getElement().getStyle(); + String overflow = style.getProperty("overflow"); + style.setProperty("overflow", "hidden"); + style.setPropertyPx("width", tabsWidth); + + boolean hasTabs = tabPanel.getWidgetCount() > 0; + + Style wrapperstyle = null; + if (hasTabs) { + wrapperstyle = getCurrentlyDisplayedWidget().getElement() + .getParentElement().getStyle(); + wrapperstyle.setPropertyPx("width", tabsWidth); + } + // Get content width from actual widget + + int contentWidth = 0; + if (hasTabs) { + contentWidth = getCurrentlyDisplayedWidget().getOffsetWidth(); + } + style.setProperty("overflow", overflow); + + // Set widths to max(tabs,content) + if (tabsWidth < contentWidth) { + tabsWidth = contentWidth; + } + + int outerWidth = tabsWidth + getContentAreaBorderWidth(); + + tabs.getStyle().setPropertyPx("width", outerWidth); + style.setPropertyPx("width", tabsWidth); + if (hasTabs) { + wrapperstyle.setPropertyPx("width", tabsWidth); + } + + contentNode.getStyle().setPropertyPx("width", tabsWidth); + super.setWidth(outerWidth + "px"); + updateOpenTabSize(); + } + + private boolean isAllTabsBeforeIndexInvisible() { + boolean invisible = true; + for (int i = 0; i < scrollerIndex; i++) { + invisible = invisible & !tb.getTab(i).isVisible(); + } + return invisible; + } + + private boolean isScrollerPrevDisabled() { + return scrollerPrev.getClassName().contains( + PREV_SCROLLER_DISABLED_CLASSNAME); + } + + private boolean isScrollerHidden() { + return scroller.getStyle().getDisplay() + .equals(Display.NONE.getCssName()); + } + + private boolean isIndexSkippingHiddenTabs() { + return isAllTabsBeforeIndexInvisible() + && (isScrollerPrevDisabled() || isScrollerHidden()); + } + + @Override + public void renderTab(final TabState tabState, int index) { + Tab tab = tb.getTab(index); + if (tab == null) { + tab = tb.addTab(); + } + + tab.updateFromState(tabState); + tab.setEnabledOnServer((!disabledTabKeys.contains(tabKeys.get(index)))); + tab.setHiddenOnServer(!tabState.visible); + + if (scrolledOutOfView(index) && !isIndexSkippingHiddenTabs()) { + // Should not set tabs visible if they are scrolled out of view + tab.setVisible(false); + } else { + // When the tab was hidden and then turned visible again + // and there is space for it, it should be in view (#17096) (#17333) + if (isTabSetVisibleBeforeScroller(tabState, index, tab)) { + scrollerIndex = index; + tab.setVisible(true); + tab.setStyleNames(false, true); + + // scroll to the currently selected tab if it got clipped + // after making another tab visible + if (isClippedTabs()) { + scrollIntoView(getActiveTab()); + } + } else { + tab.setVisible(tabState.visible); + } + } + + /* + * Force the width of the caption container so the content will not wrap + * and tabs won't be too narrow in certain browsers + */ + tab.recalculateCaptionWidth(); + } + + /** + * Checks whether the tab has been set to visible and the scroller is at the + * first visible tab. That means that the scroller has to be adjusted so + * that the tab is visible again. + */ + private boolean isTabSetVisibleBeforeScroller(TabState tabState, int index, + Tab tab) { + return isIndexSkippingHiddenTabs() && isScrollerAtFirstVisibleTab() + && hasTabChangedVisibility(tabState, tab) + && scrolledOutOfView(index); + } + + /** + * Checks whether the tab is visible on server but is not visible on client + * yet. + */ + private boolean hasTabChangedVisibility(TabState tabState, Tab tab) { + return !tab.isVisible() && tabState.visible; + } + + private boolean isScrollerAtFirstVisibleTab() { + return tb.getFirstVisibleTabClient() == scrollerIndex; + } + + /** + * @deprecated as of 7.1, VTabsheet only keeps the active tab in the DOM + * without any place holders. + */ + @Deprecated + public class PlaceHolder extends VLabel { + public PlaceHolder() { + super(""); + } + } + + /** + * Renders the widget content for a tab sheet. + * + * @param newWidget + */ + public void renderContent(Widget newWidget) { + assert tabPanel.getWidgetCount() <= 1; + + if (null == newWidget) { + newWidget = new SimplePanel(); + } + + if (tabPanel.getWidgetCount() == 0) { + tabPanel.add(newWidget); + } else if (tabPanel.getWidget(0) != newWidget) { + tabPanel.remove(0); + tabPanel.add(newWidget); + } + + assert tabPanel.getWidgetCount() <= 1; + + // There's never any other index than 0, but maintaining API for now + tabPanel.showWidget(0); + + VTabsheet.this.iLayout(); + updateOpenTabSize(); + VTabsheet.this.removeStyleDependentName("loading"); + } + + /** + * Recalculates the sizes of tab captions, causing the tabs to be rendered + * the correct size. + */ + private void updateTabCaptionSizes() { + for (int tabIx = 0; tabIx < tb.getTabCount(); tabIx++) { + tb.getTab(tabIx).recalculateCaptionWidth(); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateContentNodeHeight() { + if (!isDynamicHeight()) { + int contentHeight = getOffsetHeight(); + contentHeight -= deco.getOffsetHeight(); + contentHeight -= tb.getOffsetHeight(); + + ComputedStyle cs = new ComputedStyle(contentNode); + contentHeight -= Math.ceil(cs.getPaddingHeight()); + contentHeight -= Math.ceil(cs.getBorderHeight()); + + if (contentHeight < 0) { + contentHeight = 0; + } + + // Set proper values for content element + contentNode.getStyle().setHeight(contentHeight, Unit.PX); + } else { + contentNode.getStyle().clearHeight(); + } + } + + /** + * Run internal layouting. + */ + public void iLayout() { + updateTabScroller(); + updateTabCaptionSizes(); + } + + /** + * Sets the size of the visible tab (component). As the tab is set to + * position: absolute (to work around a firefox flickering bug) we must keep + * this up-to-date by hand. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public void updateOpenTabSize() { + /* + * The overflow=auto element must have a height specified, otherwise it + * will be just as high as the contents and no scrollbars will appear + */ + int height = -1; + int width = -1; + int minWidth = 0; + + if (!isDynamicHeight()) { + height = contentNode.getOffsetHeight(); + } + if (!isDynamicWidth()) { + width = contentNode.getOffsetWidth() - getContentAreaBorderWidth(); + } else { + /* + * If the tabbar is wider than the content we need to use the tabbar + * width as minimum width so scrollbars get placed correctly (at the + * right edge). + */ + minWidth = tb.getOffsetWidth() - getContentAreaBorderWidth(); + } + tabPanel.fixVisibleTabSize(width, height, minWidth); + + } + + /** + * Layouts the tab-scroller elements, and applies styles. + */ + private void updateTabScroller() { + if (!isDynamicWidth()) { + tabs.getStyle().setWidth(100, Unit.PCT); + } + + // Make sure scrollerIndex is valid + if (scrollerIndex < 0 || scrollerIndex > tb.getTabCount()) { + scrollerIndex = tb.getFirstVisibleTab(); + } else if (tb.getTabCount() > 0 + && tb.getTab(scrollerIndex).isHiddenOnServer()) { + scrollerIndex = tb.getNextVisibleTab(scrollerIndex); + } + + boolean scrolled = isScrolledTabs(); + boolean clipped = isClippedTabs(); + if (tb.getTabCount() > 0 && tb.isVisible() && (scrolled || clipped)) { + scroller.getStyle().clearDisplay(); + DOM.setElementProperty(scrollerPrev, "className", + SCROLLER_CLASSNAME + + (scrolled ? "Prev" + : PREV_SCROLLER_DISABLED_CLASSNAME)); + DOM.setElementProperty(scrollerNext, "className", + SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled")); + + // the active tab should be focusable if and only if it is visible + boolean isActiveTabVisible = scrollerIndex <= activeTabIndex + && !isClipped(tb.selected); + tb.selected.setTabulatorIndex(isActiveTabVisible ? tabulatorIndex + : -1); + + } else { + scroller.getStyle().setDisplay(Display.NONE); + } + + if (BrowserInfo.get().isSafari()) { + /* + * another hack for webkits. tabscroller sometimes drops without + * "shaking it" reproducable in + * com.vaadin.tests.components.tabsheet.TabSheetIcons + */ + final Style style = scroller.getStyle(); + style.setProperty("whiteSpace", "normal"); + Scheduler.get().scheduleDeferred(new Command() { + + @Override + public void execute() { + style.setProperty("whiteSpace", ""); + } + }); + } + + } + + /** For internal use only. May be removed or replaced in the future. */ + public void showAllTabs() { + scrollerIndex = tb.getFirstVisibleTab(); + scrollerPositionTabId = scrollerIndex < 0 ? null : tb + .getTab(scrollerIndex).id; + for (int i = 0; i < tb.getTabCount(); i++) { + Tab t = tb.getTab(i); + if (!t.isHiddenOnServer()) { + t.setVisible(true); + } + } + } + + private boolean isScrolledTabs() { + return scrollerIndex > tb.getFirstVisibleTab(); + } + + private boolean isClippedTabs() { + return (tb.getOffsetWidth() - DOM.getElementPropertyInt((Element) tb + .getContainerElement().getLastChild().cast(), "offsetWidth")) > getOffsetWidth() + - (isScrolledTabs() ? scroller.getOffsetWidth() : 0); + } + + private boolean isClipped(Tab tab) { + return tab.getAbsoluteLeft() + tab.getOffsetWidth() > getAbsoluteLeft() + + getOffsetWidth() - scroller.getOffsetWidth(); + } + + @Override + protected void clearPaintables() { + + int i = tb.getTabCount(); + while (i > 0) { + tb.removeTab(--i); + } + tabPanel.clear(); + + } + + @Override + public Iterator getWidgetIterator() { + return tabPanel.iterator(); + } + + /** For internal use only. May be removed or replaced in the future. */ + public int getContentAreaBorderWidth() { + return WidgetUtil.measureHorizontalBorder(contentNode); + } + + @Override + public int getTabCount() { + return tb.getTabCount(); + } + + @Override + public ComponentConnector getTab(int index) { + if (tabPanel.getWidgetCount() > index) { + Widget widget = tabPanel.getWidget(index); + return getConnectorForWidget(widget); + } + return null; + } + + @Override + public void removeTab(int index) { + tb.removeTab(index); + + // Removing content from tp is handled by the connector + } + + @Override + public void selectTab(int index) { + tb.selectTab(index); + } + + @Override + public void focus() { + getActiveTab().focus(); + } + + public void blur() { + getActiveTab().blur(); + } + + /* + * Gets the active tab. + */ + private Tab getActiveTab() { + return tb.getTab(activeTabIndex); + } + + @Override + public void setConnector(AbstractComponentConnector connector) { + super.setConnector(connector); + + focusBlurManager.connector = connector; + } + + /* + * The focus and blur manager instance. + */ + private FocusBlurManager focusBlurManager = new FocusBlurManager(); + + /* + * Generate the correct focus/blur events for the main TabSheet component + * (#14304). + * + * The TabSheet must fire one focus event when the user clicks on the tab + * bar (i.e. inner TabBar class) containing the Tabs or when the focus is + * provided to the TabSheet by any means. Also one blur event should be + * fired only when the user leaves the tab bar. After the user focus on the + * tab bar and before leaving it, no matter how many times he's pressing the + * Tabs or the scroll buttons, the TabSheet component should not fire any of + * those blur/focus events. + * + * The only focusable elements contained in the tab bar are the Tabs (see + * inner class Tab). The reason is the accessibility support. + * + * Having this in mind, the chosen solution path for our problem is to match + * a sequence of focus/blur events on the tabs, choose only the first focus + * and last blur events and pass only those further to the main component. + * Any consecutive blur/focus events on 2 Tabs must be ignored. + * + * Because in a blur event we don't know whether or not a focus will follow, + * we just defer a command initiated on the blur event to wait and see if + * any focus will appear. The command will be executed after the next focus, + * so if no focus was triggered in the mean while it'll submit the blur + * event to the main component, otherwise it'll do nothing, so the main + * component will not generate the blur.. + */ + private static class FocusBlurManager { + + // The real tab with focus on it. If the focus goes to another element + // in the page this will be null. + private Tab focusedTab; + + /* + * Gets the focused tab. + */ + private Tab getFocusedTab() { + return focusedTab; + } + + /* + * Sets the local field tracking the focused tab. + */ + private void setFocusedTab(Tab focusedTab) { + this.focusedTab = focusedTab; + } + + /* + * The ultimate focus/blur event dispatcher. + */ + private AbstractComponentConnector connector; + + /** + * Delegate method for the onFocus event occurring on Tab. + * + * @since 7.2.6 + * @param newFocusTab + * the new focused tab. + * @see #onBlur(Tab) + */ + public void onFocus(Tab newFocusTab) { + + if (connector.hasEventListener(EventId.FOCUS)) { + + // Send the focus event only first time when we focus on any + // tab. The focused tab will be reseted on the last blur. + if (focusedTab == null) { + connector.getRpcProxy(FocusAndBlurServerRpc.class).focus(); + } + } + + cancelLastBlurSchedule(); + + setFocusedTab(newFocusTab); + } + + /** + * Delegate method for the onBlur event occurring on Tab. + * + * @param blurSource + * the source of the blur. + * + * @see #onFocus(Tab) + */ + public void onBlur(Tab blurSource) { + if (focusedTab != null && focusedTab == blurSource) { + + if (connector.hasEventListener(EventId.BLUR)) { + scheduleBlur(focusedTab); + } + } + } + + /* + * The last blur command to be executed. + */ + private BlurCommand blurCommand; + + /* + * Execute the final blur command. + */ + private class BlurCommand implements Command { + + /* + * The blur source. + */ + private Tab blurSource; + + /** + * Create the blur command using the blur source. + * + * @param blurSource + * the source. + * @param focusedTabProvider + * provides the current focused tab. + */ + public BlurCommand(Tab blurSource) { + this.blurSource = blurSource; + } + + /** + * Stop the command from being executed. + * + * @since 7.4 + */ + public void stopSchedule() { + blurSource = null; + } + + /** + * Schedule the command for a deferred execution. + * + * @since 7.4 + */ + public void scheduleDeferred() { + Scheduler.get().scheduleDeferred(this); + } + + @Override + public void execute() { + + Tab focusedTab = getFocusedTab(); + + if (blurSource == null) { + return; + } + + // The focus didn't change since this blur triggered, so + // the new focused element is not a tab. + if (focusedTab == blurSource) { + + // We're certain there's no focus anymore. + focusedTab.removeAssistiveDescription(); + setFocusedTab(null); + + connector.getRpcProxy(FocusAndBlurServerRpc.class).blur(); + } + + // Call this to set it to null and be consistent. + cancelLastBlurSchedule(); + } + } + + /* + * Schedule a new blur event for a deferred execution. + */ + private void scheduleBlur(Tab blurSource) { + + if (nextBlurScheduleCancelled) { + + // This will set the stopNextBlurCommand back to false as well. + cancelLastBlurSchedule(); + + // Reset the status. + nextBlurScheduleCancelled = false; + return; + } + + cancelLastBlurSchedule(); + + blurCommand = new BlurCommand(blurSource); + blurCommand.scheduleDeferred(); + } + + /** + * Remove the last blur deferred command from execution. + */ + public void cancelLastBlurSchedule() { + if (blurCommand != null) { + blurCommand.stopSchedule(); + blurCommand = null; + } + + // We really want to make sure this flag gets reseted at any time + // when something interact with the blur manager and ther's no blur + // command scheduled (as we just canceled it). + nextBlurScheduleCancelled = false; + } + + /** + * Cancel the next scheduled execution. This method must be called only + * from an event occurring before the onBlur event. It's the case of IE + * which doesn't trigger the focus event, so we're using this approach + * to cancel the next blur event prior it's execution, calling the + * method from mouse down event. + */ + public void cancelNextBlurSchedule() { + + // Make sure there's still no other command to be executed. + cancelLastBlurSchedule(); + + nextBlurScheduleCancelled = true; + } + + /* + * Flag that the next deferred command won't get executed. This is + * useful in case of IE where the user focus event don't fire and we're + * using the mouse down event to track the focus. But the mouse down + * event triggers before the blur, so we need to cancel the deferred + * execution in advance. + */ + private boolean nextBlurScheduleCancelled = false; + + } + + /* + * The tabs selection handler instance. + */ + private final TabSelectionHandler selectionHandler = new TabSelectionHandler(); + + /* + * Handle the events for selecting the tabs. + */ + private class TabSelectionHandler implements FocusHandler, BlurHandler, + KeyDownHandler, ClickHandler, MouseDownHandler { + + /** For internal use only. May be removed or replaced in the future. */ + // The current visible focused index. + private int focusedTabIndex = 0; + + /** + * Register the tab to the selection handler. + * + * @param tab + * the tab to register. + */ + public void registerTab(Tab tab) { + + tab.addBlurHandler(this); + tab.addFocusHandler(this); + tab.addKeyDownHandler(this); + tab.addClickHandler(this); + tab.addMouseDownHandler(this); + } + + @Override + public void onBlur(final BlurEvent event) { + + getVTooltip().hideTooltip(); + + Object blurSource = event.getSource(); + + if (blurSource instanceof Tab) { + focusBlurManager.onBlur((Tab) blurSource); + } + } + + @Override + public void onFocus(FocusEvent event) { + + if (event.getSource() instanceof Tab) { + Tab focusSource = (Tab) event.getSource(); + focusBlurManager.onFocus(focusSource); + + if (focusSource.hasTooltip()) { + focusSource.setAssistiveDescription(getVTooltip() + .getUniqueId()); + getVTooltip().showAssistive(focusSource.getTooltipInfo()); + } + + } + } + + @Override + public void onClick(ClickEvent event) { + + // IE doesn't trigger focus when click, so we need to make sure + // the previous blur deferred command will get killed. + focusBlurManager.cancelLastBlurSchedule(); + + TabCaption caption = (TabCaption) event.getSource(); + Element targetElement = event.getNativeEvent().getEventTarget() + .cast(); + // the tab should not be focused if the close button was clicked + if (targetElement == caption.getCloseButton()) { + return; + } + + int index = tb.getWidgetIndex(caption.getParent()); + + tb.navigateTab(focusedTabIndex, index); + + focusedTabIndex = index; + + if (!loadTabSheet(index)) { + + // This needs to be called at the end, as the activeTabIndex + // is set in the loadTabSheet. + focus(); + } + } + + @Override + public void onMouseDown(MouseDownEvent event) { + + if (event.getSource() instanceof Tab) { + + // IE doesn't trigger focus when click, so we need to make sure + // the + // next blur deferred command will get killed. + focusBlurManager.cancelNextBlurSchedule(); + } + } + + @Override + public void onKeyDown(KeyDownEvent event) { + if (event.getSource() instanceof Tab) { + int keycode = event.getNativeEvent().getKeyCode(); + + if (!event.isAnyModifierKeyDown()) { + if (keycode == getPreviousTabKey()) { + selectPreviousTab(); + event.stopPropagation(); + + } else if (keycode == getNextTabKey()) { + selectNextTab(); + event.stopPropagation(); + + } else if (keycode == getCloseTabKey()) { + Tab tab = tb.getTab(activeTabIndex); + if (tab.isClosable()) { + tab.onClose(); + } + + } else if (keycode == getSelectTabKey()) { + loadTabSheet(focusedTabIndex); + + // Prevent the page from scrolling when hitting space + // (select key) to select the current tab. + event.preventDefault(); + } + } + } + } + + /* + * Left arrow key selection. + */ + private void selectPreviousTab() { + int newTabIndex = focusedTabIndex; + // Find the previous visible and enabled tab if any. + do { + newTabIndex--; + } while (newTabIndex >= 0 && !canSelectTab(newTabIndex)); + + if (newTabIndex >= 0) { + keySelectTab(newTabIndex); + } + } + + /* + * Right arrow key selection. + */ + private void selectNextTab() { + int newTabIndex = focusedTabIndex; + // Find the next visible and enabled tab if any. + do { + newTabIndex++; + } while (newTabIndex < getTabCount() && !canSelectTab(newTabIndex)); + + if (newTabIndex < getTabCount()) { + keySelectTab(newTabIndex); + } + } + + /* + * Select the specified tab using left/right key. + */ + private void keySelectTab(int newTabIndex) { + Tab tab = tb.getTab(newTabIndex); + if (tab == null) { + return; + } + + // Focus the tab, otherwise the selected one will loose focus and + // TabSheet will get blurred. + focusTabAtIndex(newTabIndex); + + tb.navigateTab(focusedTabIndex, newTabIndex); + + focusedTabIndex = newTabIndex; + } + + /** + * Focus the specified tab. Make sure to call this only from user + * events, otherwise will break things. + * + * @param tabIndex + * the index of the tab to set. + */ + void focusTabAtIndex(int tabIndex) { + Tab tabToFocus = tb.getTab(tabIndex); + if (tabToFocus != null) { + tabToFocus.focus(); + } + } + + } + + /** + * @return The key code of the keyboard shortcut that selects the previous + * tab in a focused tabsheet. + */ + protected int getPreviousTabKey() { + return KeyCodes.KEY_LEFT; + } + + /** + * Gets the key to activate the selected tab when navigating using + * previous/next (left/right) keys. + * + * @return the key to activate the selected tab. + * + * @see #getNextTabKey() + * @see #getPreviousTabKey() + */ + protected int getSelectTabKey() { + return KeyCodes.KEY_SPACE; + } + + /** + * @return The key code of the keyboard shortcut that selects the next tab + * in a focused tabsheet. + */ + protected int getNextTabKey() { + return KeyCodes.KEY_RIGHT; + } + + /** + * @return The key code of the keyboard shortcut that closes the currently + * selected tab in a focused tabsheet. + */ + protected int getCloseTabKey() { + return KeyCodes.KEY_DELETE; + } + + private void scrollIntoView(Tab tab) { + + if (!tab.isHiddenOnServer()) { + + // Check for visibility first as clipped tabs to the right are + // always visible. + // On IE8 a tab with false visibility would have the bounds of the + // full TabBar. + if (!tab.isVisible()) { + while (!tab.isVisible()) { + scrollerIndex = tb.scrollLeft(scrollerIndex); + } + updateTabScroller(); + + } else if (isClipped(tab)) { + while (isClipped(tab) && scrollerIndex != -1) { + scrollerIndex = tb.scrollRight(scrollerIndex); + } + updateTabScroller(); + } + if (scrollerIndex >= 0 && scrollerIndex < tb.getTabCount()) { + scrollerPositionTabId = tb.getTab(scrollerIndex).id; + } else { + scrollerPositionTabId = null; + } + } + } + + /** + * Makes tab bar visible. + * + * @since 7.2 + */ + public void showTabs() { + tb.setVisible(true); + removeStyleName(CLASSNAME + "-hidetabs"); + tb.recalculateCaptionWidths(); + } + + /** + * Makes tab bar invisible. + * + * @since 7.2 + */ + public void hideTabs() { + tb.setVisible(false); + addStyleName(CLASSNAME + "-hidetabs"); + } + + /** Matches tab[ix] - used for extracting the index of the targeted tab */ + private static final RegExp SUBPART_TAB_REGEXP = RegExp + .compile("tab\\[(\\d+)](.*)"); + + @Override + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + if ("tabpanel".equals(subPart)) { + return DOM.asOld(tabPanel.getElement().getFirstChildElement()); + } else if (SUBPART_TAB_REGEXP.test(subPart)) { + MatchResult result = SUBPART_TAB_REGEXP.exec(subPart); + int tabIx = Integer.valueOf(result.getGroup(1)); + Tab tab = tb.getTab(tabIx); + if (tab != null) { + if ("/close".equals(result.getGroup(2))) { + if (tab.isClosable()) { + return tab.tabCaption.getCloseButton(); + } + } else { + return tab.tabCaption.getElement(); + } + } + } + return null; + } + + @Override + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + if (tabPanel.getElement().equals(subElement.getParentElement()) + || tabPanel.getElement().equals(subElement)) { + return "tabpanel"; + } else { + for (int i = 0; i < tb.getTabCount(); ++i) { + Tab tab = tb.getTab(i); + if (tab.isClosable() + && tab.tabCaption.getCloseButton().isOrHasChild( + subElement)) { + return "tab[" + i + "]/close"; + } else if (tab.getElement().isOrHasChild(subElement)) { + return "tab[" + i + "]"; + } + } + } + return null; + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VTabsheetBase.java b/client/src/main/java/com/vaadin/client/ui/VTabsheetBase.java new file mode 100644 index 0000000000..e96aa035ed --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VTabsheetBase.java @@ -0,0 +1,201 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.HasEnabled; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorMap; +import com.vaadin.shared.ui.tabsheet.TabState; + +public abstract class VTabsheetBase extends ComplexPanel implements HasEnabled { + + /** For internal use only. May be removed or replaced in the future. */ + protected ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + protected final ArrayList tabKeys = new ArrayList(); + /** For internal use only. May be removed or replaced in the future. */ + protected Set disabledTabKeys = new HashSet(); + + /** For internal use only. May be removed or replaced in the future. */ + protected int activeTabIndex = 0; + /** For internal use only. May be removed or replaced in the future. */ + protected boolean disabled; + /** For internal use only. May be removed or replaced in the future. */ + protected boolean readonly; + + /** For internal use only. May be removed or replaced in the future. */ + protected AbstractComponentConnector connector; + + private boolean tabCaptionsAsHtml = false; + + public VTabsheetBase(String classname) { + setElement(DOM.createDiv()); + setStyleName(classname); + } + + /** + * @return a list of currently shown Widgets + */ + public abstract Iterator getWidgetIterator(); + + /** + * Clears current tabs and contents + */ + protected abstract void clearPaintables(); + + /** + * Implement in extending classes. This method should render needed elements + * and set the visibility of the tab according to the 'selected' parameter. + */ + public abstract void renderTab(TabState tabState, int index); + + /** + * Implement in extending classes. This method should return the number of + * tabs currently rendered. + */ + public abstract int getTabCount(); + + /** + * Implement in extending classes. This method should return the Paintable + * corresponding to the given index. + */ + public abstract ComponentConnector getTab(int index); + + /** + * Implement in extending classes. This method should remove the rendered + * tab with the specified index. + */ + public abstract void removeTab(int index); + + /** + * Returns true if the width of the widget is undefined, false otherwise. + * + * @since 7.2 + * @return true if width of the widget is determined by its content + */ + protected boolean isDynamicWidth() { + return getConnectorForWidget(this).isUndefinedWidth(); + } + + /** + * Returns true if the height of the widget is undefined, false otherwise. + * + * @since 7.2 + * @return true if width of the height is determined by its content + */ + protected boolean isDynamicHeight() { + return getConnectorForWidget(this).isUndefinedHeight(); + } + + /** + * Sets the connector that should be notified of events etc. + * + * For internal use only. This method may be removed or replaced in the + * future. + * + * @since 7.2 + * @param connector + */ + public void setConnector(AbstractComponentConnector connector) { + this.connector = connector; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void clearTabKeys() { + tabKeys.clear(); + disabledTabKeys.clear(); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void addTabKey(String key, boolean disabled) { + tabKeys.add(key); + if (disabled) { + disabledTabKeys.add(key); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setClient(ApplicationConnection client) { + this.client = client; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setActiveTabIndex(int activeTabIndex) { + this.activeTabIndex = activeTabIndex; + } + + /** For internal use only. May be removed or replaced in the future. */ + @Override + public void setEnabled(boolean enabled) { + disabled = !enabled; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setReadonly(boolean readonly) { + this.readonly = readonly; + } + + /** For internal use only. May be removed or replaced in the future. */ + protected ComponentConnector getConnectorForWidget(Widget widget) { + return ConnectorMap.get(client).getConnector(widget); + } + + /** For internal use only. May be removed or replaced in the future. */ + public abstract void selectTab(int index); + + @Override + public boolean isEnabled() { + return !disabled; + } + + /** + * Sets whether the caption is rendered as HTML. + *

+ * The default is false, i.e. render tab captions as plain text + * + * @since 7.4 + * @param captionAsHtml + * true if the captions are rendered as HTML, false if rendered + * as plain text + */ + public void setTabCaptionsAsHtml(boolean tabCaptionsAsHtml) { + this.tabCaptionsAsHtml = tabCaptionsAsHtml; + } + + /** + * Checks whether captions are rendered as HTML + * + * The default is false, i.e. render tab captions as plain text + * + * @since 7.4 + * @return true if the captions are rendered as HTML, false if rendered as + * plain text + */ + public boolean isTabCaptionsAsHtml() { + return tabCaptionsAsHtml; + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VTabsheetPanel.java b/client/src/main/java/com/vaadin/client/ui/VTabsheetPanel.java new file mode 100644 index 0000000000..240f493907 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VTabsheetPanel.java @@ -0,0 +1,202 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler; + +/** + * A panel that displays all of its child widgets in a 'deck', where only one + * can be visible at a time. It is used by + * {@link com.vaadin.client.ui.VTabsheet}. + * + * This class has the same basic functionality as the GWT DeckPanel + * {@link com.google.gwt.user.client.ui.DeckPanel}, with the exception that it + * doesn't manipulate the child widgets' width and height attributes. + */ +public class VTabsheetPanel extends ComplexPanel { + + private Widget visibleWidget; + + private final TouchScrollHandler touchScrollHandler; + + /** + * Creates an empty tabsheet panel. + */ + public VTabsheetPanel() { + setElement(DOM.createDiv()); + touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); + } + + /** + * Adds the specified widget to the deck. + * + * @param w + * the widget to be added + */ + @Override + public void add(Widget w) { + Element el = createContainerElement(); + DOM.appendChild(getElement(), el); + super.add(w, el); + } + + private Element createContainerElement() { + Element el = DOM.createDiv(); + el.getStyle().setPosition(Position.ABSOLUTE); + hide(el); + touchScrollHandler.addElement(el); + return el; + } + + /** + * Gets the index of the currently-visible widget. + * + * @return the visible widget's index + */ + public int getVisibleWidget() { + return getWidgetIndex(visibleWidget); + } + + /** + * Inserts a widget before the specified index. + * + * @param w + * the widget to be inserted + * @param beforeIndex + * the index before which it will be inserted + * @throws IndexOutOfBoundsException + * if beforeIndex is out of range + */ + public void insert(Widget w, int beforeIndex) { + Element el = createContainerElement(); + DOM.insertChild(getElement(), el, beforeIndex); + super.insert(w, el, beforeIndex, false); + } + + @Override + public boolean remove(Widget w) { + Element child = w.getElement(); + Element parent = null; + if (child != null) { + parent = DOM.getParent(child); + } + final boolean removed = super.remove(w); + if (removed) { + if (visibleWidget == w) { + visibleWidget = null; + } + if (parent != null) { + DOM.removeChild(getElement(), parent); + } + touchScrollHandler.removeElement(parent); + } + return removed; + } + + /** + * Shows the widget at the specified index. This causes the currently- + * visible widget to be hidden. + * + * @param index + * the index of the widget to be shown + */ + public void showWidget(int index) { + checkIndexBoundsForAccess(index); + Widget newVisible = getWidget(index); + if (visibleWidget != newVisible) { + if (visibleWidget != null) { + hide(DOM.getParent(visibleWidget.getElement())); + } + visibleWidget = newVisible; + touchScrollHandler.setElements(visibleWidget.getElement() + .getParentElement()); + } + // Always ensure the selected tab is visible. If server prevents a tab + // change we might end up here with visibleWidget == newVisible but its + // parent is still hidden. + unHide(DOM.getParent(visibleWidget.getElement())); + } + + private void hide(Element e) { + e.getStyle().setVisibility(Visibility.HIDDEN); + e.getStyle().setTop(-100000, Unit.PX); + e.getStyle().setLeft(-100000, Unit.PX); + } + + private void unHide(Element e) { + e.getStyle().setTop(0, Unit.PX); + e.getStyle().setLeft(0, Unit.PX); + e.getStyle().clearVisibility(); + } + + public void fixVisibleTabSize(int width, int height, int minWidth) { + if (visibleWidget == null) { + return; + } + + boolean dynamicHeight = false; + + if (height < 0) { + height = visibleWidget.getOffsetHeight(); + dynamicHeight = true; + } + if (width < 0) { + width = visibleWidget.getOffsetWidth(); + } + if (width < minWidth) { + width = minWidth; + } + + Element wrapperDiv = visibleWidget.getElement().getParentElement(); + + // width first + getElement().getStyle().setPropertyPx("width", width); + wrapperDiv.getStyle().setPropertyPx("width", width); + + if (dynamicHeight) { + // height of widget might have changed due wrapping + height = visibleWidget.getOffsetHeight(); + } + // v-tabsheet-tabsheetpanel height + getElement().getStyle().setPropertyPx("height", height); + + // widget wrapper height + if (dynamicHeight) { + wrapperDiv.getStyle().clearHeight(); + } else { + // widget wrapper height + wrapperDiv.getStyle().setPropertyPx("height", height); + } + } + + public void replaceComponent(Widget oldComponent, Widget newComponent) { + boolean isVisible = (visibleWidget == oldComponent); + int widgetIndex = getWidgetIndex(oldComponent); + remove(oldComponent); + insert(newComponent, widgetIndex); + if (isVisible) { + showWidget(widgetIndex); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VTextArea.java b/client/src/main/java/com/vaadin/client/ui/VTextArea.java new file mode 100644 index 0000000000..bb3d3a476b --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VTextArea.java @@ -0,0 +1,336 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.dom.client.Style.WhiteSpace; +import com.google.gwt.dom.client.TextAreaElement; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyUpEvent; +import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.dd.DragImageModifier; + +/** + * This class represents a multiline textfield (textarea). + * + * TODO consider replacing this with a RichTextArea based implementation. IE + * does not support CSS height for textareas in Strict mode :-( + * + * @author Vaadin Ltd. + * + */ +public class VTextArea extends VTextField implements DragImageModifier { + + public static final String CLASSNAME = "v-textarea"; + private boolean wordwrap = true; + private MaxLengthHandler maxLengthHandler = new MaxLengthHandler(); + private boolean browserSupportsMaxLengthAttribute = browserSupportsMaxLengthAttribute(); + private EnterDownHandler enterDownHandler = new EnterDownHandler(); + + public VTextArea() { + super(DOM.createTextArea()); + setStyleName(CLASSNAME); + + // KeyDownHandler is needed for correct text input on all + // browsers, not just those that don't support a max length attribute + addKeyDownHandler(enterDownHandler); + + if (!browserSupportsMaxLengthAttribute) { + addKeyUpHandler(maxLengthHandler); + addChangeHandler(maxLengthHandler); + sinkEvents(Event.ONPASTE); + } + } + + public TextAreaElement getTextAreaElement() { + return super.getElement().cast(); + } + + public void setRows(int rows) { + getTextAreaElement().setRows(rows); + } + + @Override + public void setSelectionRange(int pos, int length) { + super.setSelectionRange(pos, length); + final String value = getValue(); + /* + * Align position to index inside string value + */ + int index = pos; + if (index < 0) { + index = 0; + } + if (index > value.length()) { + index = value.length(); + } + // Get pixels count required to scroll textarea vertically + int scrollTop = getScrollTop(value, index); + int scrollLeft = -1; + /* + * Check if textarea has wrap attribute set to "off". In the latter case + * horizontal scroll is also required. + */ + if (!isWordwrap()) { + // Get pixels count required to scroll textarea horizontally + scrollLeft = getScrollLeft(value, index); + } + // Set back original text if previous methods calls changed it + if (!isWordwrap() || index < value.length()) { + setValue(value, false); + } + /* + * Call original method to set cursor position. In most browsers it + * doesn't lead to scrolling. + */ + super.setSelectionRange(pos, length); + /* + * Align vertical scroll to middle of textarea view (height) if + * scrolling is reqiured at all. + */ + if (scrollTop > 0) { + scrollTop += getElement().getClientHeight() / 2; + } + /* + * Align horizontal scroll to middle of textarea view (widht) if + * scrolling is reqiured at all. + */ + if (scrollLeft > 0) { + scrollLeft += getElement().getClientWidth() / 2; + } + /* + * Scroll if computed scrollTop is greater than scroll after cursor + * setting + */ + if (getElement().getScrollTop() < scrollTop) { + getElement().setScrollTop(scrollTop); + } + /* + * Scroll if computed scrollLeft is greater than scroll after cursor + * setting + */ + if (getElement().getScrollLeft() < scrollLeft) { + getElement().setScrollLeft(scrollLeft); + } + } + + /* + * Get horizontal scroll value required to get position visible. Method is + * called only when text wrapping is off. There is need to scroll + * horizontally in case words are wrapped. + */ + private int getScrollLeft(String value, int index) { + String beginning = value.substring(0, index); + // Compute beginning of the current line + int begin = beginning.lastIndexOf('\n'); + String line = value.substring(begin + 1); + index = index - begin - 1; + if (index < line.length()) { + index++; + } + line = line.substring(0, index); + /* + * Now line contains current line up to index position + */ + setValue(line.trim(), false); // Set this line to the textarea. + /* + * Scroll textarea up to the end of the line (maximum possible + * horizontal scrolling value). Now the end line becomes visible. + */ + getElement().setScrollLeft(getElement().getScrollWidth()); + // Return resulting horizontal scrolling value. + return getElement().getScrollLeft(); + } + + /* + * Get vertical scroll value required to get position visible + */ + private int getScrollTop(String value, int index) { + /* + * Trim text after position and set this trimmed text if index is not + * very end. + */ + if (index < value.length()) { + String beginning = value.substring(0, index); + setValue(beginning, false); + } + /* + * Now textarea contains trimmed text and could be scrolled up to the + * top. Scroll it to maximum possible value to get end of the text + * visible. + */ + getElement().setScrollTop(getElement().getScrollHeight()); + // Return resulting vertical scrolling value. + return getElement().getScrollTop(); + } + + private class MaxLengthHandler implements KeyUpHandler, ChangeHandler { + + @Override + public void onKeyUp(KeyUpEvent event) { + enforceMaxLength(); + } + + public void onPaste(Event event) { + enforceMaxLength(); + } + + @Override + public void onChange(ChangeEvent event) { + // Opera does not support paste events so this enforces max length + // for Opera. + enforceMaxLength(); + } + + } + + protected void enforceMaxLength() { + if (getMaxLength() >= 0) { + Scheduler.get().scheduleDeferred(new Command() { + @Override + public void execute() { + if (getText().length() > getMaxLength()) { + setText(getText().substring(0, getMaxLength())); + } + } + }); + } + } + + protected boolean browserSupportsMaxLengthAttribute() { + BrowserInfo info = BrowserInfo.get(); + if (info.isFirefox()) { + return true; + } + if (info.isSafari()) { + return true; + } + if (info.isIE10() || info.isIE11() || info.isEdge()) { + return true; + } + if (info.isAndroid()) { + return true; + } + return false; + } + + @Override + protected void updateMaxLength(int maxLength) { + if (browserSupportsMaxLengthAttribute) { + super.updateMaxLength(maxLength); + } else { + // Events handled by MaxLengthHandler. This call enforces max length + // when the max length value has changed + enforceMaxLength(); + } + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (event.getTypeInt() == Event.ONPASTE) { + maxLengthHandler.onPaste(event); + } + } + + private class EnterDownHandler implements KeyDownHandler { + + @Override + public void onKeyDown(KeyDownEvent event) { + // Fix for #12424/13811 - if the key being pressed is enter, we stop + // propagation of the KeyDownEvents if there were no modifier keys + // also pressed. This prevents shortcuts that are bound to only the + // enter key from being processed but allows usage of e.g. + // shift-enter or ctrl-enter. + if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER + && !event.isAnyModifierKeyDown()) { + event.stopPropagation(); + } + } + + } + + @Override + public int getCursorPos() { + // This is needed so that TextBoxImplIE6 is used to return the correct + // position for old Internet Explorer versions where it has to be + // detected in a different way. + return getImpl().getTextAreaCursorPos(getElement()); + } + + @Override + protected void setMaxLengthToElement(int newMaxLength) { + // There is no maxlength property for textarea. The maximum length is + // enforced by the KEYUP handler + + } + + public void setWordwrap(boolean wordwrap) { + if (wordwrap == this.wordwrap) { + return; // No change + } + + if (wordwrap) { + getElement().removeAttribute("wrap"); + getElement().getStyle().clearOverflow(); + getElement().getStyle().clearWhiteSpace(); + } else { + getElement().setAttribute("wrap", "off"); + getElement().getStyle().setOverflow(Overflow.AUTO); + getElement().getStyle().setWhiteSpace(WhiteSpace.PRE); + } + if (BrowserInfo.get().isOpera() + || (BrowserInfo.get().isWebkit() && wordwrap)) { + // Opera fails to dynamically update the wrap attribute so we detach + // and reattach the whole TextArea. + // Webkit fails to properly reflow the text when enabling wrapping, + // same workaround + WidgetUtil.detachAttach(getElement()); + } + this.wordwrap = wordwrap; + } + + @Override + public void onKeyDown(KeyDownEvent event) { + // Overridden to avoid submitting TextArea value on enter in IE. This is + // another reason why widgets should inherit a common abstract + // class instead of directly each other. + // This method is overridden only for IE and Firefox. + } + + @Override + public void modifyDragImage(Element element) { + // Fix for #13557 - drag image doesn't show original text area text. + // It happens because "value" property is not copied into the cloned + // element + String value = getElement().getPropertyString("value"); + if (value != null) { + element.setPropertyString("value", value); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VTextField.java b/client/src/main/java/com/vaadin/client/ui/VTextField.java new file mode 100644 index 0000000000..1554bd1a22 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VTextField.java @@ -0,0 +1,531 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.TextBoxBase; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.WidgetUtil; +import com.vaadin.shared.EventId; +import com.vaadin.shared.ui.textfield.TextFieldConstants; + +/** + * This class represents a basic text input field with one row. + * + * @author Vaadin Ltd. + * + */ +public class VTextField extends TextBoxBase implements Field, ChangeHandler, + FocusHandler, BlurHandler, KeyDownHandler { + + /** + * The input node CSS classname. + */ + public static final String CLASSNAME = "v-textfield"; + /** + * This CSS classname is added to the input node on hover. + */ + public static final String CLASSNAME_FOCUS = "focus"; + + /** For internal use only. May be removed or replaced in the future. */ + public String paintableId; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public String valueBeforeEdit = null; + + /** + * Set to false if a text change event has been sent since the last value + * change event. This means that {@link #valueBeforeEdit} should not be + * trusted when determining whether a text change even should be sent. + */ + private boolean valueBeforeEditIsSynced = true; + + private boolean immediate = false; + private int maxLength = -1; + + private static final String CLASSNAME_PROMPT = "prompt"; + private static final String TEXTCHANGE_MODE_TIMEOUT = "TIMEOUT"; + + private String inputPrompt = null; + private boolean prompting = false; + private int lastCursorPos = -1; + + // used while checking if FF has set input prompt as value + private boolean possibleInputError = false; + + public VTextField() { + this(DOM.createInputText()); + } + + protected VTextField(Element node) { + super(node); + setStyleName(CLASSNAME); + addChangeHandler(this); + if (BrowserInfo.get().isIE() || BrowserInfo.get().isFirefox()) { + addKeyDownHandler(this); + } + addFocusHandler(this); + addBlurHandler(this); + } + + /** + * For internal use only. May be removed or replaced in the future. + *

+ * TODO When GWT adds ONCUT, add it there and remove workaround. See + * http://code.google.com/p/google-web-toolkit/issues/detail?id=4030 + *

+ * Also note that the cut/paste are not totally crossbrowsers compatible. + * E.g. in Opera mac works via context menu, but on via File->Paste/Cut. + * Opera might need the polling method for 100% working textchanceevents. + * Eager polling for a change is bit dum and heavy operation, so I guess we + * should first try to survive without. + */ + public static final int TEXTCHANGE_EVENTS = Event.ONPASTE | Event.KEYEVENTS + | Event.ONMOUSEUP; + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + + if (listenTextChangeEvents + && (event.getTypeInt() & TEXTCHANGE_EVENTS) == event + .getTypeInt()) { + deferTextChangeEvent(); + } + + } + + /* + * TODO optimize this so that only changes are sent + make the value change + * event just a flag that moves the current text to value + */ + private String lastTextChangeString = null; + + private String getLastCommunicatedString() { + return lastTextChangeString; + } + + private void communicateTextValueToServer() { + String text = getText(); + if (prompting) { + // Input prompt visible, text is actually "" + text = ""; + } + if (!text.equals(getLastCommunicatedString())) { + if (valueBeforeEditIsSynced && text.equals(valueBeforeEdit)) { + /* + * Value change for the current text has been enqueued since the + * last text change event was sent, but we can't know that it + * has been sent to the server. Ensure that all pending changes + * are sent now. Sending a value change without a text change + * will simulate a TextChangeEvent on the server. + */ + client.sendPendingVariableChanges(); + } else { + // Default case - just send an immediate text change message + client.updateVariable(paintableId, + TextFieldConstants.VAR_CUR_TEXT, text, true); + + // Shouldn't investigate valueBeforeEdit to avoid duplicate text + // change events as the states are not in sync any more + valueBeforeEditIsSynced = false; + } + lastTextChangeString = text; + } + } + + private Timer textChangeEventTrigger = new Timer() { + + @Override + public void run() { + if (isAttached()) { + updateCursorPosition(); + communicateTextValueToServer(); + scheduled = false; + } + } + }; + + private boolean scheduled = false; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean listenTextChangeEvents; + /** For internal use only. May be removed or replaced in the future. */ + public String textChangeEventMode; + public int textChangeEventTimeout; + + private void deferTextChangeEvent() { + if (textChangeEventMode.equals(TEXTCHANGE_MODE_TIMEOUT) && scheduled) { + return; + } else { + textChangeEventTrigger.cancel(); + } + textChangeEventTrigger.schedule(getTextChangeEventTimeout()); + scheduled = true; + } + + private int getTextChangeEventTimeout() { + return textChangeEventTimeout; + } + + @Override + public void setReadOnly(boolean readOnly) { + boolean wasReadOnly = isReadOnly(); + + if (readOnly) { + setTabIndex(-1); + } else if (wasReadOnly && !readOnly && getTabIndex() == -1) { + /* + * Need to manually set tab index to 0 since server will not send + * the tab index if it is 0. + */ + setTabIndex(0); + } + + super.setReadOnly(readOnly); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateFieldContent(final String text) { + setPrompting(inputPrompt != null && focusedTextField != this + && (text.equals(""))); + + String fieldValue; + if (prompting) { + fieldValue = isReadOnly() ? "" : inputPrompt; + addStyleDependentName(CLASSNAME_PROMPT); + } else { + fieldValue = text; + removeStyleDependentName(CLASSNAME_PROMPT); + } + setText(fieldValue); + + lastTextChangeString = valueBeforeEdit = text; + valueBeforeEditIsSynced = true; + } + + protected void onCut() { + if (listenTextChangeEvents) { + deferTextChangeEvent(); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public native void attachCutEventListener(Element el) + /*-{ + var me = this; + el.oncut = $entry(function() { + me.@com.vaadin.client.ui.VTextField::onCut()(); + }); + }-*/; + + protected native void detachCutEventListener(Element el) + /*-{ + el.oncut = null; + }-*/; + + private void onDrop() { + if (focusedTextField == this) { + return; + } + updateText(false); + } + + private void updateText(boolean blurred) { + String text = getText(); + setPrompting(inputPrompt != null && (text == null || text.isEmpty())); + if (prompting) { + setText(isReadOnly() ? "" : inputPrompt); + if (blurred) { + addStyleDependentName(CLASSNAME_PROMPT); + } + } + + valueChange(blurred); + } + + private void scheduleOnDropEvent() { + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + onDrop(); + } + }); + } + + private native void attachDropEventListener(Element el) + /*-{ + var me = this; + el.ondrop = $entry(function() { + me.@com.vaadin.client.ui.VTextField::scheduleOnDropEvent()(); + }); + }-*/; + + private native void detachDropEventListener(Element el) + /*-{ + el.ondrop = null; + }-*/; + + @Override + protected void onDetach() { + super.onDetach(); + detachCutEventListener(getElement()); + if (focusedTextField == this) { + focusedTextField = null; + } + if (BrowserInfo.get().isFirefox()) { + removeOnInputListener(getElement()); + detachDropEventListener(getElement()); + } + } + + @Override + protected void onAttach() { + super.onAttach(); + if (listenTextChangeEvents) { + detachCutEventListener(getElement()); + } + if (BrowserInfo.get().isFirefox()) { + // Workaround for FF setting input prompt as the value if esc is + // pressed while the field is focused and empty (#8051). + addOnInputListener(getElement()); + // Workaround for FF updating component's internal value after + // having drag-and-dropped text from another element (#14056) + attachDropEventListener(getElement()); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setMaxLength(int newMaxLength) { + if (newMaxLength == maxLength) { + return; + } + maxLength = newMaxLength; + updateMaxLength(maxLength); + } + + /** + * This method is responsible for updating the DOM or otherwise ensuring + * that the given max length is enforced. Called when the max length for the + * field has changed. + * + * @param maxLength + * The new max length + */ + protected void updateMaxLength(int maxLength) { + if (maxLength >= 0) { + getElement().setPropertyInt("maxLength", maxLength); + } else { + getElement().removeAttribute("maxLength"); + + } + setMaxLengthToElement(maxLength); + } + + protected void setMaxLengthToElement(int newMaxLength) { + if (newMaxLength >= 0) { + getElement().setPropertyInt("maxLength", newMaxLength); + } else { + getElement().removeAttribute("maxLength"); + } + } + + public int getMaxLength() { + return maxLength; + } + + @Override + public void onChange(ChangeEvent event) { + valueChange(false); + } + + /** + * Called when the field value might have changed and/or the field was + * blurred. These are combined so the blur event is sent in the same batch + * as a possible value change event (these are often connected). + * + * @param blurred + * true if the field was blurred + */ + public void valueChange(boolean blurred) { + if (client != null && paintableId != null) { + boolean sendBlurEvent = false; + boolean sendValueChange = false; + + if (blurred && client.hasEventListeners(this, EventId.BLUR)) { + sendBlurEvent = true; + client.updateVariable(paintableId, EventId.BLUR, "", false); + } + + String newText = prompting ? "" : getText(); + if (newText != null && !newText.equals(valueBeforeEdit)) { + sendValueChange = immediate; + client.updateVariable(paintableId, "text", newText, false); + valueBeforeEdit = newText; + valueBeforeEditIsSynced = true; + } + + /* + * also send cursor position, no public api yet but for easier + * extension + */ + updateCursorPosition(); + + if (sendBlurEvent || sendValueChange) { + /* + * Avoid sending text change event as we will simulate it on the + * server side before value change events. + */ + textChangeEventTrigger.cancel(); + scheduled = false; + client.sendPendingVariableChanges(); + } + } + } + + /** + * Updates the cursor position variable if it has changed since the last + * update. + * + * @return true iff the value was updated + */ + protected boolean updateCursorPosition() { + if (WidgetUtil.isAttachedAndDisplayed(this)) { + int cursorPos = getCursorPos(); + if (lastCursorPos != cursorPos) { + client.updateVariable(paintableId, + TextFieldConstants.VAR_CURSOR, cursorPos, false); + lastCursorPos = cursorPos; + return true; + } + } + return false; + } + + private static VTextField focusedTextField; + + public static void flushChangesFromFocusedTextField() { + if (focusedTextField != null) { + focusedTextField.onChange(null); + } + } + + @Override + public void onFocus(FocusEvent event) { + addStyleDependentName(CLASSNAME_FOCUS); + if (prompting) { + setText(""); + removeStyleDependentName(CLASSNAME_PROMPT); + setPrompting(false); + } + focusedTextField = this; + if (client != null && client.hasEventListeners(this, EventId.FOCUS)) { + client.updateVariable(paintableId, EventId.FOCUS, "", true); + } + } + + @Override + public void onBlur(BlurEvent event) { + // this is called twice on Chrome when e.g. changing tab while prompting + // field focused - do not change settings on the second time + if (focusedTextField != this) { + return; + } + removeStyleDependentName(CLASSNAME_FOCUS); + focusedTextField = null; + updateText(true); + } + + private void setPrompting(boolean prompting) { + this.prompting = prompting; + } + + public void setColumns(int columns) { + if (columns <= 0) { + return; + } + + setWidth(columns + "em"); + } + + @Override + public void onKeyDown(KeyDownEvent event) { + if (BrowserInfo.get().isIE() + && event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { + // IE does not send change events when pressing enter in a text + // input so we handle it using a key listener instead + valueChange(false); + } else if (BrowserInfo.get().isFirefox() + && event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE + && getText().equals("")) { + // check after onInput event if inputPrompt has appeared as the + // value of the field + possibleInputError = true; + } + } + + public void setImmediate(boolean immediate) { + this.immediate = immediate; + } + + public void setInputPrompt(String inputPrompt) { + this.inputPrompt = inputPrompt; + } + + protected boolean isWordwrap() { + String wrap = getElement().getAttribute("wrap"); + return !"off".equals(wrap); + } + + private native void addOnInputListener(Element el) + /*-{ + var self = this; + el.oninput = $entry(function() { + self.@com.vaadin.client.ui.VTextField::checkForInputError()(); + }); + }-*/; + + private native void removeOnInputListener(Element el) + /*-{ + el.oninput = null; + }-*/; + + private void checkForInputError() { + if (possibleInputError && getText().equals(inputPrompt)) { + setText(""); + } + possibleInputError = false; + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VTextualDate.java b/client/src/main/java/com/vaadin/client/ui/VTextualDate.java new file mode 100644 index 0000000000..4d80b30f4f --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VTextualDate.java @@ -0,0 +1,410 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.Date; + +import com.google.gwt.aria.client.Roles; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.user.client.ui.TextBox; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.Focusable; +import com.vaadin.client.LocaleNotLoadedException; +import com.vaadin.client.LocaleService; +import com.vaadin.client.VConsole; +import com.vaadin.client.ui.aria.AriaHelper; +import com.vaadin.client.ui.aria.HandlesAriaCaption; +import com.vaadin.client.ui.aria.HandlesAriaInvalid; +import com.vaadin.client.ui.aria.HandlesAriaRequired; +import com.vaadin.shared.EventId; +import com.vaadin.shared.ui.datefield.Resolution; + +public class VTextualDate extends VDateField implements Field, ChangeHandler, + Focusable, SubPartAware, HandlesAriaCaption, HandlesAriaInvalid, + HandlesAriaRequired, KeyDownHandler { + + private static final String PARSE_ERROR_CLASSNAME = "-parseerror"; + + /** For internal use only. May be removed or replaced in the future. */ + public final TextBox text; + + /** For internal use only. May be removed or replaced in the future. */ + public String formatStr; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean lenient; + + private static final String CLASSNAME_PROMPT = "prompt"; + + /** For internal use only. May be removed or replaced in the future. */ + public static final String ATTR_INPUTPROMPT = "prompt"; + + /** For internal use only. May be removed or replaced in the future. */ + public String inputPrompt = ""; + + private boolean prompting = false; + + public VTextualDate() { + super(); + text = new TextBox(); + text.addChangeHandler(this); + text.addFocusHandler(new FocusHandler() { + @Override + public void onFocus(FocusEvent event) { + text.addStyleName(VTextField.CLASSNAME + "-" + + VTextField.CLASSNAME_FOCUS); + if (prompting) { + text.setText(""); + setPrompting(false); + } + if (getClient() != null + && getClient().hasEventListeners(VTextualDate.this, + EventId.FOCUS)) { + getClient() + .updateVariable(getId(), EventId.FOCUS, "", true); + } + + // Needed for tooltip event handling + VTextualDate.this.fireEvent(event); + } + }); + text.addBlurHandler(new BlurHandler() { + @Override + public void onBlur(BlurEvent event) { + text.removeStyleName(VTextField.CLASSNAME + "-" + + VTextField.CLASSNAME_FOCUS); + String value = getText(); + setPrompting(inputPrompt != null + && (value == null || "".equals(value))); + if (prompting) { + text.setText(readonly ? "" : inputPrompt); + } + if (getClient() != null + && getClient().hasEventListeners(VTextualDate.this, + EventId.BLUR)) { + getClient().updateVariable(getId(), EventId.BLUR, "", true); + } + + // Needed for tooltip event handling + VTextualDate.this.fireEvent(event); + } + }); + if (BrowserInfo.get().isIE()) { + addDomHandler(this, KeyDownEvent.getType()); + } + add(text); + } + + protected void updateStyleNames() { + if (text != null) { + text.setStyleName(VTextField.CLASSNAME); + text.addStyleName(getStylePrimaryName() + "-textfield"); + } + } + + protected String getFormatString() { + if (formatStr == null) { + if (currentResolution == Resolution.YEAR) { + formatStr = "yyyy"; // force full year + } else { + + try { + String frmString = LocaleService + .getDateFormat(currentLocale); + frmString = cleanFormat(frmString); + // String delim = LocaleService + // .getClockDelimiter(currentLocale); + if (currentResolution.getCalendarField() >= Resolution.HOUR + .getCalendarField()) { + if (dts.isTwelveHourClock()) { + frmString += " hh"; + } else { + frmString += " HH"; + } + if (currentResolution.getCalendarField() >= Resolution.MINUTE + .getCalendarField()) { + frmString += ":mm"; + if (currentResolution.getCalendarField() >= Resolution.SECOND + .getCalendarField()) { + frmString += ":ss"; + } + } + if (dts.isTwelveHourClock()) { + frmString += " aaa"; + } + + } + + formatStr = frmString; + } catch (LocaleNotLoadedException e) { + // TODO should die instead? Can the component survive + // without format string? + VConsole.error(e); + } + } + } + return formatStr; + } + + @Override + public void bindAriaCaption( + com.google.gwt.user.client.Element captionElement) { + AriaHelper.bindCaption(text, captionElement); + } + + @Override + public void setAriaRequired(boolean required) { + AriaHelper.handleInputRequired(text, required); + } + + @Override + public void setAriaInvalid(boolean invalid) { + AriaHelper.handleInputInvalid(text, invalid); + } + + /** + * Updates the text field according to the current date (provided by + * {@link #getDate()}). Takes care of updating text, enabling and disabling + * the field, setting/removing readonly status and updating readonly styles. + *

+ * For internal use only. May be removed or replaced in the future. + *

+ * TODO: Split part of this into a method that only updates the text as this + * is what usually is needed except for updateFromUIDL. + */ + public void buildDate() { + removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME); + // Create the initial text for the textfield + String dateText; + Date currentDate = getDate(); + if (currentDate != null) { + dateText = getDateTimeService().formatDate(currentDate, + getFormatString()); + } else { + dateText = ""; + } + + setText(dateText); + text.setEnabled(enabled); + text.setReadOnly(readonly); + + if (readonly) { + text.addStyleName("v-readonly"); + Roles.getTextboxRole().setAriaReadonlyProperty(text.getElement(), + true); + } else { + text.removeStyleName("v-readonly"); + Roles.getTextboxRole() + .removeAriaReadonlyProperty(text.getElement()); + } + + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + text.setEnabled(enabled); + } + + protected void setPrompting(boolean prompting) { + this.prompting = prompting; + if (prompting) { + addStyleDependentName(CLASSNAME_PROMPT); + } else { + removeStyleDependentName(CLASSNAME_PROMPT); + } + } + + @Override + @SuppressWarnings("deprecation") + public void onChange(ChangeEvent event) { + if (!text.getText().equals("")) { + try { + String enteredDate = text.getText(); + + setDate(getDateTimeService().parseDate(enteredDate, + getFormatString(), lenient)); + + if (lenient) { + // If date value was leniently parsed, normalize text + // presentation. + // FIXME: Add a description/example here of when this is + // needed + text.setValue( + getDateTimeService().formatDate(getDate(), + getFormatString()), false); + } + + // remove possibly added invalid value indication + removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME); + } catch (final Exception e) { + VConsole.log(e); + + addStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME); + // this is a hack that may eventually be removed + getClient().updateVariable(getId(), "lastInvalidDateString", + text.getText(), false); + setDate(null); + } + } else { + setDate(null); + // remove possibly added invalid value indication + removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME); + } + // always send the date string + getClient() + .updateVariable(getId(), "dateString", text.getText(), false); + + // Update variables + // (only the smallest defining resolution needs to be + // immediate) + Date currentDate = getDate(); + getClient().updateVariable(getId(), "year", + currentDate != null ? currentDate.getYear() + 1900 : -1, + currentResolution == Resolution.YEAR && immediate); + if (currentResolution.getCalendarField() >= Resolution.MONTH + .getCalendarField()) { + getClient().updateVariable(getId(), "month", + currentDate != null ? currentDate.getMonth() + 1 : -1, + currentResolution == Resolution.MONTH && immediate); + } + if (currentResolution.getCalendarField() >= Resolution.DAY + .getCalendarField()) { + getClient().updateVariable(getId(), "day", + currentDate != null ? currentDate.getDate() : -1, + currentResolution == Resolution.DAY && immediate); + } + if (currentResolution.getCalendarField() >= Resolution.HOUR + .getCalendarField()) { + getClient().updateVariable(getId(), "hour", + currentDate != null ? currentDate.getHours() : -1, + currentResolution == Resolution.HOUR && immediate); + } + if (currentResolution.getCalendarField() >= Resolution.MINUTE + .getCalendarField()) { + getClient().updateVariable(getId(), "min", + currentDate != null ? currentDate.getMinutes() : -1, + currentResolution == Resolution.MINUTE && immediate); + } + if (currentResolution.getCalendarField() >= Resolution.SECOND + .getCalendarField()) { + getClient().updateVariable(getId(), "sec", + currentDate != null ? currentDate.getSeconds() : -1, + currentResolution == Resolution.SECOND && immediate); + } + + } + + private String cleanFormat(String format) { + // Remove unnecessary d & M if resolution is too low + if (currentResolution.getCalendarField() < Resolution.DAY + .getCalendarField()) { + format = format.replaceAll("d", ""); + } + if (currentResolution.getCalendarField() < Resolution.MONTH + .getCalendarField()) { + format = format.replaceAll("M", ""); + } + + // Remove unsupported patterns + // TODO support for 'G', era designator (used at least in Japan) + format = format.replaceAll("[GzZwWkK]", ""); + + // Remove extra delimiters ('/' and '.') + while (format.startsWith("/") || format.startsWith(".") + || format.startsWith("-")) { + format = format.substring(1); + } + while (format.endsWith("/") || format.endsWith(".") + || format.endsWith("-")) { + format = format.substring(0, format.length() - 1); + } + + // Remove duplicate delimiters + format = format.replaceAll("//", "/"); + format = format.replaceAll("\\.\\.", "."); + format = format.replaceAll("--", "-"); + + return format.trim(); + } + + @Override + public void focus() { + text.setFocus(true); + } + + protected String getText() { + if (prompting) { + return ""; + } + return text.getText(); + } + + protected void setText(String text) { + if (inputPrompt != null + && (text == null || "".equals(text)) + && !this.text.getStyleName() + .contains( + VTextField.CLASSNAME + "-" + + VTextField.CLASSNAME_FOCUS)) { + text = readonly ? "" : inputPrompt; + setPrompting(true); + } else { + setPrompting(false); + } + + this.text.setText(text); + } + + private final String TEXTFIELD_ID = "field"; + + @Override + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + if (subPart.equals(TEXTFIELD_ID)) { + return text.getElement(); + } + + return null; + } + + @Override + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + if (text.getElement().isOrHasChild(subElement)) { + return TEXTFIELD_ID; + } + + return null; + } + + @Override + public void onKeyDown(KeyDownEvent event) { + if (BrowserInfo.get().isIE() + && event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { + // IE does not send change events when pressing enter in a text + // input so we handle it using a key listener instead + onChange(null); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VTree.java b/client/src/main/java/com/vaadin/client/ui/VTree.java new file mode 100644 index 0000000000..efbafd0cb7 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VTree.java @@ -0,0 +1,2262 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import com.google.gwt.aria.client.ExpandedValue; +import com.google.gwt.aria.client.Id; +import com.google.gwt.aria.client.Roles; +import com.google.gwt.aria.client.SelectedValue; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Node; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.ContextMenuEvent; +import com.google.gwt.event.dom.client.ContextMenuHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyPressEvent; +import com.google.gwt.event.dom.client.KeyPressHandler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.UIObject; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorMap; +import com.vaadin.client.MouseEventDetailsBuilder; +import com.vaadin.client.UIDL; +import com.vaadin.client.Util; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.aria.AriaHelper; +import com.vaadin.client.ui.aria.HandlesAriaCaption; +import com.vaadin.client.ui.dd.DDUtil; +import com.vaadin.client.ui.dd.VAbstractDropHandler; +import com.vaadin.client.ui.dd.VAcceptCallback; +import com.vaadin.client.ui.dd.VDragAndDropManager; +import com.vaadin.client.ui.dd.VDragEvent; +import com.vaadin.client.ui.dd.VDropHandler; +import com.vaadin.client.ui.dd.VHasDropHandler; +import com.vaadin.client.ui.dd.VTransferable; +import com.vaadin.client.ui.tree.TreeConnector; +import com.vaadin.shared.MouseEventDetails; +import com.vaadin.shared.MouseEventDetails.MouseButton; +import com.vaadin.shared.ui.MultiSelectMode; +import com.vaadin.shared.ui.dd.VerticalDropLocation; +import com.vaadin.shared.ui.tree.TreeConstants; + +/** + * + */ +public class VTree extends FocusElementPanel implements VHasDropHandler, + FocusHandler, BlurHandler, KeyPressHandler, KeyDownHandler, + SubPartAware, ActionOwner, HandlesAriaCaption { + private String lastNodeKey = ""; + + public static final String CLASSNAME = "v-tree"; + + /** + * @deprecated As of 7.0, use {@link MultiSelectMode#DEFAULT} instead. + */ + @Deprecated + public static final MultiSelectMode MULTISELECT_MODE_DEFAULT = MultiSelectMode.DEFAULT; + + /** + * @deprecated As of 7.0, use {@link MultiSelectMode#SIMPLE} instead. + */ + @Deprecated + public static final MultiSelectMode MULTISELECT_MODE_SIMPLE = MultiSelectMode.SIMPLE; + + private static final int CHARCODE_SPACE = 32; + + /** For internal use only. May be removed or replaced in the future. */ + public final FlowPanel body = new FlowPanel(); + + /** For internal use only. May be removed or replaced in the future. */ + public Set selectedIds = new HashSet(); + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public String paintableId; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean selectable; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean isMultiselect; + + private String currentMouseOverKey; + + /** For internal use only. May be removed or replaced in the future. */ + public TreeNode lastSelection; + + /** For internal use only. May be removed or replaced in the future. */ + public TreeNode focusedNode; + + /** For internal use only. May be removed or replaced in the future. */ + public MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT; + + private final HashMap keyToNode = new HashMap(); + + /** + * This map contains captions and icon urls for actions like: * "33_c" -> + * "Edit" * "33_i" -> "http://dom.com/edit.png" + */ + private final HashMap actionMap = new HashMap(); + + /** For internal use only. May be removed or replaced in the future. */ + public boolean immediate; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean isNullSelectionAllowed = true; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean isHtmlContentAllowed = false; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean disabled = false; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean readonly; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean rendering; + + private VAbstractDropHandler dropHandler; + + /** For internal use only. May be removed or replaced in the future. */ + public int dragMode; + + private boolean selectionHasChanged = false; + + /* + * to fix #14388. The cause of defect #14388: event 'clickEvent' is sent to + * server before updating of "selected" variable, but should be sent after + * it + */ + private boolean clickEventPending = false; + + /** For internal use only. May be removed or replaced in the future. */ + public String[] bodyActionKeys; + + /** For internal use only. May be removed or replaced in the future. */ + public TreeConnector connector; + + public VLazyExecutor iconLoaded = new VLazyExecutor(50, + new ScheduledCommand() { + + @Override + public void execute() { + doLayout(); + } + + }); + + public VTree() { + super(); + setStyleName(CLASSNAME); + + Roles.getTreeRole().set(body.getElement()); + add(body); + + addFocusHandler(this); + addBlurHandler(this); + + /* + * Listen to context menu events on the empty space in the tree + */ + sinkEvents(Event.ONCONTEXTMENU); + addDomHandler(new ContextMenuHandler() { + @Override + public void onContextMenu(ContextMenuEvent event) { + handleBodyContextMenu(event); + } + }, ContextMenuEvent.getType()); + + /* + * Firefox auto-repeat works correctly only if we use a key press + * handler, other browsers handle it correctly when using a key down + * handler + */ + if (BrowserInfo.get().isGecko()) { + addKeyPressHandler(this); + } else { + addKeyDownHandler(this); + } + + /* + * We need to use the sinkEvents method to catch the keyUp events so we + * can cache a single shift. KeyUpHandler cannot do this. At the same + * time we catch the mouse down and up events so we can apply the text + * selection patch in IE + */ + sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONKEYUP); + + /* + * Re-set the tab index to make sure that the FocusElementPanel's + * (super) focus element gets the tab index and not the element + * containing the tree. + */ + setTabIndex(0); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user + * .client.Event) + */ + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (event.getTypeInt() == Event.ONMOUSEDOWN) { + // Prevent default text selection in IE + if (BrowserInfo.get().isIE()) { + ((Element) event.getEventTarget().cast()).setPropertyJSO( + "onselectstart", applyDisableTextSelectionIEHack()); + } + } else if (event.getTypeInt() == Event.ONMOUSEUP) { + // Remove IE text selection hack + if (BrowserInfo.get().isIE()) { + ((Element) event.getEventTarget().cast()).setPropertyJSO( + "onselectstart", null); + } + } else if (event.getTypeInt() == Event.ONKEYUP) { + if (selectionHasChanged) { + if (event.getKeyCode() == getNavigationDownKey() + && !event.getShiftKey()) { + sendSelectionToServer(); + event.preventDefault(); + } else if (event.getKeyCode() == getNavigationUpKey() + && !event.getShiftKey()) { + sendSelectionToServer(); + event.preventDefault(); + } else if (event.getKeyCode() == KeyCodes.KEY_SHIFT) { + sendSelectionToServer(); + event.preventDefault(); + } else if (event.getKeyCode() == getNavigationSelectKey()) { + sendSelectionToServer(); + event.preventDefault(); + } + } + } + } + + public String getActionCaption(String actionKey) { + return actionMap.get(actionKey + "_c"); + } + + public String getActionIcon(String actionKey) { + return actionMap.get(actionKey + "_i"); + } + + /** + * Returns the first root node of the tree or null if there are no root + * nodes. + * + * @return The first root {@link TreeNode} + */ + protected TreeNode getFirstRootNode() { + if (body.getWidgetCount() == 0) { + return null; + } + return (TreeNode) body.getWidget(0); + } + + /** + * Returns the last root node of the tree or null if there are no root + * nodes. + * + * @return The last root {@link TreeNode} + */ + protected TreeNode getLastRootNode() { + if (body.getWidgetCount() == 0) { + return null; + } + return (TreeNode) body.getWidget(body.getWidgetCount() - 1); + } + + /** + * Returns a list of all root nodes in the Tree in the order they appear in + * the tree. + * + * @return A list of all root {@link TreeNode}s. + */ + protected List getRootNodes() { + ArrayList rootNodes = new ArrayList(); + for (int i = 0; i < body.getWidgetCount(); i++) { + rootNodes.add((TreeNode) body.getWidget(i)); + } + return rootNodes; + } + + private void updateTreeRelatedDragData(VDragEvent drag) { + + currentMouseOverKey = findCurrentMouseOverKey(drag.getElementOver()); + + drag.getDropDetails().put("itemIdOver", currentMouseOverKey); + if (currentMouseOverKey != null) { + TreeNode treeNode = getNodeByKey(currentMouseOverKey); + VerticalDropLocation detail = treeNode.getDropDetail(drag + .getCurrentGwtEvent()); + Boolean overTreeNode = null; + if (treeNode != null && !treeNode.isLeaf() + && detail == VerticalDropLocation.MIDDLE) { + overTreeNode = true; + } + drag.getDropDetails().put("itemIdOverIsNode", overTreeNode); + drag.getDropDetails().put("detail", detail); + } else { + drag.getDropDetails().put("itemIdOverIsNode", null); + drag.getDropDetails().put("detail", null); + } + + } + + private String findCurrentMouseOverKey(Element elementOver) { + TreeNode treeNode = WidgetUtil.findWidget(elementOver, TreeNode.class); + return treeNode == null ? null : treeNode.key; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateDropHandler(UIDL childUidl) { + if (dropHandler == null) { + dropHandler = new VAbstractDropHandler() { + + @Override + public void dragEnter(VDragEvent drag) { + } + + @Override + protected void dragAccepted(final VDragEvent drag) { + + } + + @Override + public void dragOver(final VDragEvent currentDrag) { + final Object oldIdOver = currentDrag.getDropDetails().get( + "itemIdOver"); + final VerticalDropLocation oldDetail = (VerticalDropLocation) currentDrag + .getDropDetails().get("detail"); + + updateTreeRelatedDragData(currentDrag); + final VerticalDropLocation detail = (VerticalDropLocation) currentDrag + .getDropDetails().get("detail"); + boolean nodeHasChanged = (currentMouseOverKey != null && currentMouseOverKey != oldIdOver) + || (currentMouseOverKey == null && oldIdOver != null); + boolean detailHasChanded = (detail != null && detail != oldDetail) + || (detail == null && oldDetail != null); + + if (nodeHasChanged || detailHasChanded) { + final String newKey = currentMouseOverKey; + TreeNode treeNode = keyToNode.get(oldIdOver); + if (treeNode != null) { + // clear old styles + treeNode.emphasis(null); + } + if (newKey != null) { + validate(new VAcceptCallback() { + @Override + public void accepted(VDragEvent event) { + VerticalDropLocation curDetail = (VerticalDropLocation) event + .getDropDetails().get("detail"); + if (curDetail == detail + && newKey.equals(currentMouseOverKey)) { + getNodeByKey(newKey).emphasis(detail); + } + /* + * Else drag is already on a different + * node-detail pair, new criteria check is + * going on + */ + } + }, currentDrag); + + } + } + + } + + @Override + public void dragLeave(VDragEvent drag) { + cleanUp(); + } + + private void cleanUp() { + if (currentMouseOverKey != null) { + getNodeByKey(currentMouseOverKey).emphasis(null); + currentMouseOverKey = null; + } + } + + @Override + public boolean drop(VDragEvent drag) { + cleanUp(); + return super.drop(drag); + } + + @Override + public ComponentConnector getConnector() { + return ConnectorMap.get(client).getConnector(VTree.this); + } + + @Override + public ApplicationConnection getApplicationConnection() { + return client; + } + + }; + } + dropHandler.updateAcceptRules(childUidl); + } + + public void setSelected(TreeNode treeNode, boolean selected) { + if (selected) { + if (!isMultiselect) { + while (selectedIds.size() > 0) { + final String id = selectedIds.iterator().next(); + final TreeNode oldSelection = getNodeByKey(id); + if (oldSelection != null) { + // can be null if the node is not visible (parent + // collapsed) + oldSelection.setSelected(false); + } + selectedIds.remove(id); + } + } + treeNode.setSelected(true); + selectedIds.add(treeNode.key); + } else { + if (!isNullSelectionAllowed) { + if (!isMultiselect || selectedIds.size() == 1) { + return; + } + } + selectedIds.remove(treeNode.key); + treeNode.setSelected(false); + } + + sendSelectionToServer(); + } + + /** + * Sends the selection to the server + */ + private void sendSelectionToServer() { + Command command = new Command() { + @Override + public void execute() { + /* + * we should send selection to server immediately in 2 cases: 1) + * 'immediate' property of Tree is true 2) clickEventPending is + * true + */ + client.updateVariable(paintableId, "selected", + selectedIds.toArray(new String[selectedIds.size()]), + clickEventPending || immediate); + clickEventPending = false; + selectionHasChanged = false; + } + }; + + /* + * Delaying the sending of the selection in webkit to ensure the + * selection is always sent when the tree has focus and after click + * events have been processed. This is due to the focusing + * implementation in FocusImplSafari which uses timeouts when focusing + * and blurring. + */ + if (BrowserInfo.get().isWebkit()) { + Scheduler.get().scheduleDeferred(command); + } else { + command.execute(); + } + } + + /** + * Is a node selected in the tree + * + * @param treeNode + * The node to check + * @return + */ + public boolean isSelected(TreeNode treeNode) { + return selectedIds.contains(treeNode.key); + } + + public class TreeNode extends SimplePanel implements ActionOwner { + + public static final String CLASSNAME = "v-tree-node"; + public static final String CLASSNAME_FOCUSED = CLASSNAME + "-focused"; + + public String key; + + /** For internal use only. May be removed or replaced in the future. */ + public String[] actionKeys = null; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean childrenLoaded; + + Element nodeCaptionDiv; + + protected Element nodeCaptionSpan; + + /** For internal use only. May be removed or replaced in the future. */ + public FlowPanel childNodeContainer; + + private boolean open; + + private Icon icon; + + private Event mouseDownEvent; + + private int cachedHeight = -1; + + private boolean focused = false; + + public TreeNode() { + constructDom(); + sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS + | Event.TOUCHEVENTS | Event.ONCONTEXTMENU); + } + + public VerticalDropLocation getDropDetail(NativeEvent currentGwtEvent) { + if (cachedHeight < 0) { + /* + * Height is cached to avoid flickering (drop hints may change + * the reported offsetheight -> would change the drop detail) + */ + cachedHeight = nodeCaptionDiv.getOffsetHeight(); + } + VerticalDropLocation verticalDropLocation = DDUtil + .getVerticalDropLocation(nodeCaptionDiv, cachedHeight, + currentGwtEvent, 0.15); + return verticalDropLocation; + } + + protected void emphasis(VerticalDropLocation detail) { + String base = "v-tree-node-drag-"; + UIObject.setStyleName(getElement(), base + "top", + VerticalDropLocation.TOP == detail); + UIObject.setStyleName(getElement(), base + "bottom", + VerticalDropLocation.BOTTOM == detail); + UIObject.setStyleName(getElement(), base + "center", + VerticalDropLocation.MIDDLE == detail); + base = "v-tree-node-caption-drag-"; + UIObject.setStyleName(nodeCaptionDiv, base + "top", + VerticalDropLocation.TOP == detail); + UIObject.setStyleName(nodeCaptionDiv, base + "bottom", + VerticalDropLocation.BOTTOM == detail); + UIObject.setStyleName(nodeCaptionDiv, base + "center", + VerticalDropLocation.MIDDLE == detail); + + // also add classname to "folder node" into which the drag is + // targeted + + TreeNode folder = null; + /* Possible parent of this TreeNode will be stored here */ + TreeNode parentFolder = getParentNode(); + + // TODO fix my bugs + if (isLeaf()) { + folder = parentFolder; + // note, parent folder may be null if this is root node => no + // folder target exists + } else { + if (detail == VerticalDropLocation.TOP) { + folder = parentFolder; + } else { + folder = this; + } + // ensure we remove the dragfolder classname from the previous + // folder node + setDragFolderStyleName(this, false); + setDragFolderStyleName(parentFolder, false); + } + if (folder != null) { + setDragFolderStyleName(folder, detail != null); + } + + } + + private TreeNode getParentNode() { + Widget parent2 = getParent().getParent(); + if (parent2 instanceof TreeNode) { + return (TreeNode) parent2; + } + return null; + } + + private void setDragFolderStyleName(TreeNode folder, boolean add) { + if (folder != null) { + UIObject.setStyleName(folder.getElement(), + "v-tree-node-dragfolder", add); + UIObject.setStyleName(folder.nodeCaptionDiv, + "v-tree-node-caption-dragfolder", add); + } + } + + /** + * Handles mouse selection + * + * @param ctrl + * Was the ctrl-key pressed + * @param shift + * Was the shift-key pressed + * @return Returns true if event was handled, else false + */ + private boolean handleClickSelection(final boolean ctrl, + final boolean shift) { + + // always when clicking an item, focus it + setFocusedNode(this, false); + + if (!BrowserInfo.get().isOpera()) { + /* + * Ensure that the tree's focus element also gains focus + * (TreeNodes focus is faked using FocusElementPanel in browsers + * other than Opera). + */ + focus(); + } + + executeEventCommand(new ScheduledCommand() { + + @Override + public void execute() { + + if (multiSelectMode == MultiSelectMode.SIMPLE + || !isMultiselect) { + toggleSelection(); + lastSelection = TreeNode.this; + } else if (multiSelectMode == MultiSelectMode.DEFAULT) { + // Handle ctrl+click + if (isMultiselect && ctrl && !shift) { + toggleSelection(); + lastSelection = TreeNode.this; + + // Handle shift+click + } else if (isMultiselect && !ctrl && shift) { + deselectAll(); + selectNodeRange(lastSelection.key, key); + sendSelectionToServer(); + + // Handle ctrl+shift click + } else if (isMultiselect && ctrl && shift) { + selectNodeRange(lastSelection.key, key); + + // Handle click + } else { + // TODO should happen only if this alone not yet + // selected, + // now sending excess server calls + deselectAll(); + toggleSelection(); + lastSelection = TreeNode.this; + } + } + } + }); + + return true; + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt + * .user.client.Event) + */ + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + final int type = DOM.eventGetType(event); + final Element target = DOM.eventGetTarget(event); + + if (type == Event.ONLOAD && icon != null && target == icon.getElement()) { + iconLoaded.trigger(); + } + + if (disabled) { + return; + } + + final boolean inCaption = isCaptionElement(target); + if (inCaption + && client.hasEventListeners(VTree.this, + TreeConstants.ITEM_CLICK_EVENT_ID) + + && (type == Event.ONDBLCLICK || type == Event.ONMOUSEUP)) { + fireClick(event); + } + if (type == Event.ONCLICK) { + if (getElement() == target) { + // state change + toggleState(); + } else if (!readonly && inCaption) { + if (selectable) { + // caption click = selection change && possible click + // event + if (handleClickSelection( + event.getCtrlKey() || event.getMetaKey(), + event.getShiftKey())) { + event.preventDefault(); + } + } else { + // Not selectable, only focus the node. + setFocusedNode(this); + } + } + event.stopPropagation(); + } else if (type == Event.ONCONTEXTMENU) { + showContextMenu(event); + } + + if (dragMode != 0 || dropHandler != null) { + if (type == Event.ONMOUSEDOWN || type == Event.ONTOUCHSTART) { + if (nodeCaptionDiv.isOrHasChild((Node) event + .getEventTarget().cast())) { + if (dragMode > 0 + && (type == Event.ONTOUCHSTART || event + .getButton() == NativeEvent.BUTTON_LEFT)) { + mouseDownEvent = event; // save event for possible + // dd operation + if (type == Event.ONMOUSEDOWN) { + event.preventDefault(); // prevent text + // selection + } else { + /* + * FIXME We prevent touch start event to be used + * as a scroll start event. Note that we cannot + * easily distinguish whether the user wants to + * drag or scroll. The same issue is in table + * that has scrollable area and has drag and + * drop enable. Some kind of timer might be used + * to resolve the issue. + */ + event.stopPropagation(); + } + } + } + } else if (type == Event.ONMOUSEMOVE + || type == Event.ONMOUSEOUT + || type == Event.ONTOUCHMOVE) { + + if (mouseDownEvent != null) { + // start actual drag on slight move when mouse is down + VTransferable t = new VTransferable(); + t.setDragSource(ConnectorMap.get(client).getConnector( + VTree.this)); + t.setData("itemId", key); + VDragEvent drag = VDragAndDropManager.get().startDrag( + t, mouseDownEvent, true); + + drag.createDragImage(nodeCaptionDiv, true); + event.stopPropagation(); + + mouseDownEvent = null; + } + } else if (type == Event.ONMOUSEUP) { + mouseDownEvent = null; + } + if (type == Event.ONMOUSEOVER) { + mouseDownEvent = null; + currentMouseOverKey = key; + event.stopPropagation(); + } + + } else if (type == Event.ONMOUSEDOWN + && event.getButton() == NativeEvent.BUTTON_LEFT) { + event.preventDefault(); // text selection + } + } + + /** + * Checks if the given element is the caption or the icon. + * + * @param target + * The element to check + * @return true if the element is the caption or the icon + */ + public boolean isCaptionElement(com.google.gwt.dom.client.Element target) { + return (target == nodeCaptionSpan || (icon != null && target == icon + .getElement())); + } + + private void fireClick(final Event evt) { + /* + * Ensure we have focus in tree before sending variables. Otherwise + * previously modified field may contain dirty variables. + */ + if (!treeHasFocus) { + if (BrowserInfo.get().isOpera()) { + if (focusedNode == null) { + getNodeByKey(key).setFocused(true); + } else { + focusedNode.setFocused(true); + } + } else { + focus(); + } + } + + final MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(evt); + + executeEventCommand(new ScheduledCommand() { + + @Override + public void execute() { + // Determine if we should send the event immediately to the + // server. We do not want to send the event if there is a + // selection event happening after this. In all other cases + // we want to send it immediately. + clickEventPending = false; + if ((details.getButton() == MouseButton.LEFT || details + .getButton() == MouseButton.MIDDLE) + && !details.isDoubleClick() && selectable) { + // Probably a selection that will cause a value change + // event to be sent + clickEventPending = true; + + // The exception is that user clicked on the + // currently selected row and null selection is not + // allowed == no selection event + if (isSelected() && selectedIds.size() == 1 + && !isNullSelectionAllowed) { + clickEventPending = false; + } + } + client.updateVariable(paintableId, "clickedKey", key, false); + client.updateVariable(paintableId, "clickEvent", + details.toString(), !clickEventPending); + } + }); + } + + /* + * Must wait for Safari to focus before sending click and value change + * events (see #6373, #6374) + */ + private void executeEventCommand(ScheduledCommand command) { + if (BrowserInfo.get().isWebkit() && !treeHasFocus) { + Scheduler.get().scheduleDeferred(command); + } else { + command.execute(); + } + } + + private void toggleSelection() { + if (selectable) { + VTree.this.setSelected(this, !isSelected()); + } + } + + private void toggleState() { + setState(!getState(), true); + } + + protected void constructDom() { + String labelId = DOM.createUniqueId(); + + addStyleName(CLASSNAME); + String treeItemId = DOM.createUniqueId(); + getElement().setId(treeItemId); + Roles.getTreeitemRole().set(getElement()); + Roles.getTreeitemRole().setAriaSelectedState(getElement(), + SelectedValue.FALSE); + Roles.getTreeitemRole().setAriaLabelledbyProperty(getElement(), + Id.of(labelId)); + + nodeCaptionDiv = DOM.createDiv(); + DOM.setElementProperty(nodeCaptionDiv, "className", CLASSNAME + + "-caption"); + Element wrapper = DOM.createDiv(); + wrapper.setId(labelId); + wrapper.setAttribute("for", treeItemId); + + nodeCaptionSpan = DOM.createSpan(); + DOM.appendChild(getElement(), nodeCaptionDiv); + DOM.appendChild(nodeCaptionDiv, wrapper); + DOM.appendChild(wrapper, nodeCaptionSpan); + + if (BrowserInfo.get().isOpera()) { + /* + * Focus the caption div of the node to get keyboard navigation + * to work without scrolling up or down when focusing a node. + */ + nodeCaptionDiv.setTabIndex(-1); + } + + childNodeContainer = new FlowPanel(); + childNodeContainer.setStyleName(CLASSNAME + "-children"); + Roles.getGroupRole().set(childNodeContainer.getElement()); + setWidget(childNodeContainer); + } + + public boolean isLeaf() { + String[] styleNames = getStyleName().split(" "); + for (String styleName : styleNames) { + if (styleName.equals(CLASSNAME + "-leaf")) { + return true; + } + } + return false; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setState(boolean state, boolean notifyServer) { + if (open == state) { + return; + } + if (state) { + if (!childrenLoaded && notifyServer) { + client.updateVariable(paintableId, "requestChildTree", + true, false); + } + if (notifyServer) { + client.updateVariable(paintableId, "expand", + new String[] { key }, true); + } + addStyleName(CLASSNAME + "-expanded"); + Roles.getTreeitemRole().setAriaExpandedState(getElement(), + ExpandedValue.TRUE); + childNodeContainer.setVisible(true); + } else { + removeStyleName(CLASSNAME + "-expanded"); + Roles.getTreeitemRole().setAriaExpandedState(getElement(), + ExpandedValue.FALSE); + childNodeContainer.setVisible(false); + if (notifyServer) { + client.updateVariable(paintableId, "collapse", + new String[] { key }, true); + } + } + open = state; + + if (!rendering) { + doLayout(); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public boolean getState() { + return open; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setText(String text) { + DOM.setInnerText(nodeCaptionSpan, text); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setHtml(String html) { + nodeCaptionSpan.setInnerHTML(html); + } + + public boolean isChildrenLoaded() { + return childrenLoaded; + } + + /** + * Returns the children of the node + * + * @return A set of tree nodes + */ + public List getChildren() { + List nodes = new LinkedList(); + + if (!isLeaf() && isChildrenLoaded()) { + Iterator iter = childNodeContainer.iterator(); + while (iter.hasNext()) { + TreeNode node = (TreeNode) iter.next(); + nodes.add(node); + } + } + return nodes; + } + + @Override + public Action[] getActions() { + if (actionKeys == null) { + return new Action[] {}; + } + final Action[] actions = new Action[actionKeys.length]; + for (int i = 0; i < actions.length; i++) { + final String actionKey = actionKeys[i]; + final TreeAction a = new TreeAction(this, String.valueOf(key), + actionKey); + a.setCaption(getActionCaption(actionKey)); + a.setIconUrl(getActionIcon(actionKey)); + actions[i] = a; + } + return actions; + } + + @Override + public ApplicationConnection getClient() { + return client; + } + + @Override + public String getPaintableId() { + return paintableId; + } + + /** + * Adds/removes Vaadin specific style name. + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param selected + */ + public void setSelected(boolean selected) { + // add style name to caption dom structure only, not to subtree + setStyleName(nodeCaptionDiv, "v-tree-node-selected", selected); + } + + protected boolean isSelected() { + return VTree.this.isSelected(this); + } + + /** + * Travels up the hierarchy looking for this node + * + * @param child + * The child which grandparent this is or is not + * @return True if this is a grandparent of the child node + */ + public boolean isGrandParentOf(TreeNode child) { + TreeNode currentNode = child; + boolean isGrandParent = false; + while (currentNode != null) { + currentNode = currentNode.getParentNode(); + if (currentNode == this) { + isGrandParent = true; + break; + } + } + return isGrandParent; + } + + public boolean isSibling(TreeNode node) { + return node.getParentNode() == getParentNode(); + } + + public void showContextMenu(Event event) { + if (!readonly && !disabled) { + if (actionKeys != null) { + int left = event.getClientX(); + int top = event.getClientY(); + top += Window.getScrollTop(); + left += Window.getScrollLeft(); + client.getContextMenu().showAt(this, left, top); + + event.stopPropagation(); + event.preventDefault(); + } + } + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.Widget#onDetach() + */ + @Override + protected void onDetach() { + super.onDetach(); + client.getContextMenu().ensureHidden(this); + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.UIObject#toString() + */ + @Override + public String toString() { + return nodeCaptionSpan.getInnerText(); + } + + /** + * Is the node focused? + * + * @param focused + * True if focused, false if not + */ + public void setFocused(boolean focused) { + if (!this.focused && focused) { + nodeCaptionDiv.addClassName(CLASSNAME_FOCUSED); + + this.focused = focused; + if (BrowserInfo.get().isOpera()) { + nodeCaptionDiv.focus(); + } + treeHasFocus = true; + } else if (this.focused && !focused) { + nodeCaptionDiv.removeClassName(CLASSNAME_FOCUSED); + this.focused = focused; + treeHasFocus = false; + } + } + + /** + * Scrolls the caption into view + */ + public void scrollIntoView() { + WidgetUtil.scrollIntoViewVertically(nodeCaptionDiv); + } + + public void setIcon(String iconUrl, String altText) { + if (icon != null) { + DOM.getFirstChild(nodeCaptionDiv) + .removeChild(icon.getElement()); + } + icon = client.getIcon(iconUrl); + if (icon != null) { + DOM.insertBefore(DOM.getFirstChild(nodeCaptionDiv), + icon.getElement(), nodeCaptionSpan); + icon.setAlternateText(altText); + } + } + + public void setNodeStyleName(String styleName) { + addStyleName(TreeNode.CLASSNAME + "-" + styleName); + setStyleName(nodeCaptionDiv, TreeNode.CLASSNAME + "-caption-" + + styleName, true); + childNodeContainer.addStyleName(TreeNode.CLASSNAME + "-children-" + + styleName); + + } + + } + + @Override + public VDropHandler getDropHandler() { + return dropHandler; + } + + public TreeNode getNodeByKey(String key) { + return keyToNode.get(key); + } + + /** + * Deselects all items in the tree + */ + public void deselectAll() { + for (String key : selectedIds) { + TreeNode node = keyToNode.get(key); + if (node != null) { + node.setSelected(false); + } + } + selectedIds.clear(); + selectionHasChanged = true; + } + + /** + * Selects a range of nodes + * + * @param startNodeKey + * The start node key + * @param endNodeKey + * The end node key + */ + private void selectNodeRange(String startNodeKey, String endNodeKey) { + + TreeNode startNode = keyToNode.get(startNodeKey); + TreeNode endNode = keyToNode.get(endNodeKey); + + // The nodes have the same parent + if (startNode.getParent() == endNode.getParent()) { + doSiblingSelection(startNode, endNode); + + // The start node is a grandparent of the end node + } else if (startNode.isGrandParentOf(endNode)) { + doRelationSelection(startNode, endNode); + + // The end node is a grandparent of the start node + } else if (endNode.isGrandParentOf(startNode)) { + doRelationSelection(endNode, startNode); + + } else { + doNoRelationSelection(startNode, endNode); + } + } + + /** + * Selects a node and deselect all other nodes + * + * @param node + * The node to select + */ + private void selectNode(TreeNode node, boolean deselectPrevious) { + if (deselectPrevious) { + deselectAll(); + } + + if (node != null) { + node.setSelected(true); + selectedIds.add(node.key); + lastSelection = node; + } + selectionHasChanged = true; + } + + /** + * Deselects a node + * + * @param node + * The node to deselect + */ + private void deselectNode(TreeNode node) { + node.setSelected(false); + selectedIds.remove(node.key); + selectionHasChanged = true; + } + + /** + * Selects all the open children to a node + * + * @param node + * The parent node + */ + private void selectAllChildren(TreeNode node, boolean includeRootNode) { + if (includeRootNode) { + node.setSelected(true); + selectedIds.add(node.key); + } + + for (TreeNode child : node.getChildren()) { + if (!child.isLeaf() && child.getState()) { + selectAllChildren(child, true); + } else { + child.setSelected(true); + selectedIds.add(child.key); + } + } + selectionHasChanged = true; + } + + /** + * Selects all children until a stop child is reached + * + * @param root + * The root not to start from + * @param stopNode + * The node to finish with + * @param includeRootNode + * Should the root node be selected + * @param includeStopNode + * Should the stop node be selected + * + * @return Returns false if the stop child was found, else true if all + * children was selected + */ + private boolean selectAllChildrenUntil(TreeNode root, TreeNode stopNode, + boolean includeRootNode, boolean includeStopNode) { + if (includeRootNode) { + root.setSelected(true); + selectedIds.add(root.key); + } + if (root.getState() && root != stopNode) { + for (TreeNode child : root.getChildren()) { + if (!child.isLeaf() && child.getState() && child != stopNode) { + if (!selectAllChildrenUntil(child, stopNode, true, + includeStopNode)) { + return false; + } + } else if (child == stopNode) { + if (includeStopNode) { + child.setSelected(true); + selectedIds.add(child.key); + } + return false; + } else { + child.setSelected(true); + selectedIds.add(child.key); + } + } + } + selectionHasChanged = true; + + return true; + } + + /** + * Select a range between two nodes which have no relation to each other + * + * @param startNode + * The start node to start the selection from + * @param endNode + * The end node to end the selection to + */ + private void doNoRelationSelection(TreeNode startNode, TreeNode endNode) { + + TreeNode commonParent = getCommonGrandParent(startNode, endNode); + TreeNode startBranch = null, endBranch = null; + + // Find the children of the common parent + List children; + if (commonParent != null) { + children = commonParent.getChildren(); + } else { + children = getRootNodes(); + } + + // Find the start and end branches + for (TreeNode node : children) { + if (nodeIsInBranch(startNode, node)) { + startBranch = node; + } + if (nodeIsInBranch(endNode, node)) { + endBranch = node; + } + } + + // Swap nodes if necessary + if (children.indexOf(startBranch) > children.indexOf(endBranch)) { + TreeNode temp = startBranch; + startBranch = endBranch; + endBranch = temp; + + temp = startNode; + startNode = endNode; + endNode = temp; + } + + // Select all children under the start node + selectAllChildren(startNode, true); + TreeNode startParent = startNode.getParentNode(); + TreeNode currentNode = startNode; + while (startParent != null && startParent != commonParent) { + List startChildren = startParent.getChildren(); + for (int i = startChildren.indexOf(currentNode) + 1; i < startChildren + .size(); i++) { + selectAllChildren(startChildren.get(i), true); + } + + currentNode = startParent; + startParent = startParent.getParentNode(); + } + + // Select nodes until the end node is reached + for (int i = children.indexOf(startBranch) + 1; i <= children + .indexOf(endBranch); i++) { + selectAllChildrenUntil(children.get(i), endNode, true, true); + } + + // Ensure end node was selected + endNode.setSelected(true); + selectedIds.add(endNode.key); + selectionHasChanged = true; + } + + /** + * Examines the children of the branch node and returns true if a node is in + * that branch + * + * @param node + * The node to search for + * @param branch + * The branch to search in + * @return True if found, false if not found + */ + private boolean nodeIsInBranch(TreeNode node, TreeNode branch) { + if (node == branch) { + return true; + } + for (TreeNode child : branch.getChildren()) { + if (child == node) { + return true; + } + if (!child.isLeaf() && child.getState()) { + if (nodeIsInBranch(node, child)) { + return true; + } + } + } + return false; + } + + /** + * Selects a range of items which are in direct relation with each other.
+ * NOTE: The start node MUST be before the end node! + * + * @param startNode + * + * @param endNode + */ + private void doRelationSelection(TreeNode startNode, TreeNode endNode) { + TreeNode currentNode = endNode; + while (currentNode != startNode) { + currentNode.setSelected(true); + selectedIds.add(currentNode.key); + + // Traverse children above the selection + List subChildren = currentNode.getParentNode() + .getChildren(); + if (subChildren.size() > 1) { + selectNodeRange(subChildren.iterator().next().key, + currentNode.key); + } else if (subChildren.size() == 1) { + TreeNode n = subChildren.get(0); + n.setSelected(true); + selectedIds.add(n.key); + } + + currentNode = currentNode.getParentNode(); + } + startNode.setSelected(true); + selectedIds.add(startNode.key); + selectionHasChanged = true; + } + + /** + * Selects a range of items which have the same parent. + * + * @param startNode + * The start node + * @param endNode + * The end node + */ + private void doSiblingSelection(TreeNode startNode, TreeNode endNode) { + TreeNode parent = startNode.getParentNode(); + + List children; + if (parent == null) { + // Topmost parent + children = getRootNodes(); + } else { + children = parent.getChildren(); + } + + // Swap start and end point if needed + if (children.indexOf(startNode) > children.indexOf(endNode)) { + TreeNode temp = startNode; + startNode = endNode; + endNode = temp; + } + + Iterator childIter = children.iterator(); + boolean startFound = false; + while (childIter.hasNext()) { + TreeNode node = childIter.next(); + if (node == startNode) { + startFound = true; + } + + if (startFound && node != endNode && node.getState()) { + selectAllChildren(node, true); + } else if (startFound && node != endNode) { + node.setSelected(true); + selectedIds.add(node.key); + } + + if (node == endNode) { + node.setSelected(true); + selectedIds.add(node.key); + break; + } + } + selectionHasChanged = true; + } + + /** + * Returns the first common parent of two nodes + * + * @param node1 + * The first node + * @param node2 + * The second node + * @return The common parent or null + */ + public TreeNode getCommonGrandParent(TreeNode node1, TreeNode node2) { + // If either one does not have a grand parent then return null + if (node1.getParentNode() == null || node2.getParentNode() == null) { + return null; + } + + // If the nodes are parents of each other then return null + if (node1.isGrandParentOf(node2) || node2.isGrandParentOf(node1)) { + return null; + } + + // Get parents of node1 + List parents1 = new ArrayList(); + TreeNode parent1 = node1.getParentNode(); + while (parent1 != null) { + parents1.add(parent1); + parent1 = parent1.getParentNode(); + } + + // Get parents of node2 + List parents2 = new ArrayList(); + TreeNode parent2 = node2.getParentNode(); + while (parent2 != null) { + parents2.add(parent2); + parent2 = parent2.getParentNode(); + } + + // Search the parents for the first common parent + for (int i = 0; i < parents1.size(); i++) { + parent1 = parents1.get(i); + for (int j = 0; j < parents2.size(); j++) { + parent2 = parents2.get(j); + if (parent1 == parent2) { + return parent1; + } + } + } + + return null; + } + + /** + * Sets the node currently in focus + * + * @param node + * The node to focus or null to remove the focus completely + * @param scrollIntoView + * Scroll the node into view + */ + public void setFocusedNode(TreeNode node, boolean scrollIntoView) { + // Unfocus previously focused node + if (focusedNode != null) { + focusedNode.setFocused(false); + + Roles.getTreeRole().removeAriaActivedescendantProperty( + focusedNode.getElement()); + } + + if (node != null) { + node.setFocused(true); + Roles.getTreeitemRole().setAriaSelectedState(node.getElement(), + SelectedValue.TRUE); + + /* + * FIXME: This code needs to be changed when the keyboard navigation + * doesn't immediately trigger a selection change anymore. + * + * Right now this function is called before and after the Tree is + * rebuilt when up/down arrow keys are pressed. This leads to the + * problem, that the newly selected item is announced too often with + * a screen reader. + * + * Behaviour is different when using the Tree with and without + * screen reader. + */ + if (node.key.equals(lastNodeKey)) { + Roles.getTreeRole().setAriaActivedescendantProperty( + getFocusElement(), Id.of(node.getElement())); + } else { + lastNodeKey = node.key; + } + } + + focusedNode = node; + + if (node != null && scrollIntoView) { + /* + * Delay scrolling the focused node into view if we are still + * rendering. #5396 + */ + if (!rendering) { + node.scrollIntoView(); + } else { + Scheduler.get().scheduleDeferred(new Command() { + @Override + public void execute() { + focusedNode.scrollIntoView(); + } + }); + } + } + } + + /** + * Focuses a node and scrolls it into view + * + * @param node + * The node to focus + */ + public void setFocusedNode(TreeNode node) { + setFocusedNode(node, true); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event + * .dom.client.FocusEvent) + */ + @Override + public void onFocus(FocusEvent event) { + treeHasFocus = true; + // If no node has focus, focus the first item in the tree + if (focusedNode == null && lastSelection == null && selectable) { + setFocusedNode(getFirstRootNode(), false); + } else if (focusedNode != null && selectable) { + setFocusedNode(focusedNode, false); + } else if (lastSelection != null && selectable) { + setFocusedNode(lastSelection, false); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event + * .dom.client.BlurEvent) + */ + @Override + public void onBlur(BlurEvent event) { + treeHasFocus = false; + if (focusedNode != null) { + focusedNode.setFocused(false); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google + * .gwt.event.dom.client.KeyPressEvent) + */ + @Override + public void onKeyPress(KeyPressEvent event) { + NativeEvent nativeEvent = event.getNativeEvent(); + int keyCode = nativeEvent.getKeyCode(); + if (keyCode == 0 && nativeEvent.getCharCode() == ' ') { + // Provide a keyCode for space to be compatible with FireFox + // keypress event + keyCode = CHARCODE_SPACE; + } + if (handleKeyNavigation(keyCode, + event.isControlKeyDown() || event.isMetaKeyDown(), + event.isShiftKeyDown())) { + event.preventDefault(); + event.stopPropagation(); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt + * .event.dom.client.KeyDownEvent) + */ + @Override + public void onKeyDown(KeyDownEvent event) { + if (handleKeyNavigation(event.getNativeEvent().getKeyCode(), + event.isControlKeyDown() || event.isMetaKeyDown(), + event.isShiftKeyDown())) { + event.preventDefault(); + event.stopPropagation(); + } + } + + /** + * Handles the keyboard navigation + * + * @param keycode + * The keycode of the pressed key + * @param ctrl + * Was ctrl pressed + * @param shift + * Was shift pressed + * @return Returns true if the key was handled, else false + */ + protected boolean handleKeyNavigation(int keycode, boolean ctrl, + boolean shift) { + // Navigate down + if (keycode == getNavigationDownKey()) { + TreeNode node = null; + // If node is open and has children then move in to the children + if (!focusedNode.isLeaf() && focusedNode.getState() + && focusedNode.getChildren().size() > 0) { + node = focusedNode.getChildren().get(0); + } + + // Else move down to the next sibling + else { + node = getNextSibling(focusedNode); + if (node == null) { + // Else jump to the parent and try to select the next + // sibling there + TreeNode current = focusedNode; + while (node == null && current.getParentNode() != null) { + node = getNextSibling(current.getParentNode()); + current = current.getParentNode(); + } + } + } + + if (node != null) { + setFocusedNode(node); + if (selectable) { + if (!ctrl && !shift) { + selectNode(node, true); + } else if (shift && isMultiselect) { + deselectAll(); + selectNodeRange(lastSelection.key, node.key); + } else if (shift) { + selectNode(node, true); + } + } + showTooltipForKeyboardNavigation(node); + } + return true; + } + + // Navigate up + if (keycode == getNavigationUpKey()) { + TreeNode prev = getPreviousSibling(focusedNode); + TreeNode node = null; + if (prev != null) { + node = getLastVisibleChildInTree(prev); + } else if (focusedNode.getParentNode() != null) { + node = focusedNode.getParentNode(); + } + if (node != null) { + setFocusedNode(node); + if (selectable) { + if (!ctrl && !shift) { + selectNode(node, true); + } else if (shift && isMultiselect) { + deselectAll(); + selectNodeRange(lastSelection.key, node.key); + } else if (shift) { + selectNode(node, true); + } + } + showTooltipForKeyboardNavigation(node); + } + return true; + } + + // Navigate left (close branch) + if (keycode == getNavigationLeftKey()) { + if (!focusedNode.isLeaf() && focusedNode.getState()) { + focusedNode.setState(false, true); + } else if (focusedNode.getParentNode() != null + && (focusedNode.isLeaf() || !focusedNode.getState())) { + + if (ctrl || !selectable) { + setFocusedNode(focusedNode.getParentNode()); + } else if (shift) { + doRelationSelection(focusedNode.getParentNode(), + focusedNode); + setFocusedNode(focusedNode.getParentNode()); + } else { + focusAndSelectNode(focusedNode.getParentNode()); + } + } + showTooltipForKeyboardNavigation(focusedNode); + return true; + } + + // Navigate right (open branch) + if (keycode == getNavigationRightKey()) { + if (!focusedNode.isLeaf() && !focusedNode.getState()) { + focusedNode.setState(true, true); + } else if (!focusedNode.isLeaf()) { + if (ctrl || !selectable) { + setFocusedNode(focusedNode.getChildren().get(0)); + } else if (shift) { + setSelected(focusedNode, true); + setFocusedNode(focusedNode.getChildren().get(0)); + setSelected(focusedNode, true); + } else { + focusAndSelectNode(focusedNode.getChildren().get(0)); + } + } + showTooltipForKeyboardNavigation(focusedNode); + return true; + } + + // Selection + if (keycode == getNavigationSelectKey()) { + if (!focusedNode.isSelected()) { + selectNode( + focusedNode, + (!isMultiselect || multiSelectMode == MULTISELECT_MODE_SIMPLE) + && selectable); + } else { + deselectNode(focusedNode); + } + return true; + } + + // Home selection + if (keycode == getNavigationStartKey()) { + TreeNode node = getFirstRootNode(); + if (ctrl || !selectable) { + setFocusedNode(node); + } else if (shift) { + deselectAll(); + selectNodeRange(focusedNode.key, node.key); + } else { + selectNode(node, true); + } + sendSelectionToServer(); + showTooltipForKeyboardNavigation(node); + return true; + } + + // End selection + if (keycode == getNavigationEndKey()) { + TreeNode lastNode = getLastRootNode(); + TreeNode node = getLastVisibleChildInTree(lastNode); + if (ctrl || !selectable) { + setFocusedNode(node); + } else if (shift) { + deselectAll(); + selectNodeRange(focusedNode.key, node.key); + } else { + selectNode(node, true); + } + sendSelectionToServer(); + showTooltipForKeyboardNavigation(node); + return true; + } + + return false; + } + + private void showTooltipForKeyboardNavigation(TreeNode node) { + if (connector != null) { + getClient().getVTooltip().showAssistive( + connector.getTooltipInfo(node.nodeCaptionSpan)); + } + } + + private void focusAndSelectNode(TreeNode node) { + /* + * Keyboard navigation doesn't work reliably if the tree is in + * multiselect mode as well as isNullSelectionAllowed = false. It first + * tries to deselect the old focused node, which fails since there must + * be at least one selection. After this the newly focused node is + * selected and we've ended up with two selected nodes even though we + * only navigated with the arrow keys. + * + * Because of this, we first select the next node and later de-select + * the old one. + */ + TreeNode oldFocusedNode = focusedNode; + setFocusedNode(node); + setSelected(focusedNode, true); + setSelected(oldFocusedNode, false); + } + + /** + * Traverses the tree to the bottom most child + * + * @param root + * The root of the tree + * @return The bottom most child + */ + private TreeNode getLastVisibleChildInTree(TreeNode root) { + if (root.isLeaf() || !root.getState() || root.getChildren().size() == 0) { + return root; + } + List children = root.getChildren(); + return getLastVisibleChildInTree(children.get(children.size() - 1)); + } + + /** + * Gets the next sibling in the tree + * + * @param node + * The node to get the sibling for + * @return The sibling node or null if the node is the last sibling + */ + private TreeNode getNextSibling(TreeNode node) { + TreeNode parent = node.getParentNode(); + List children; + if (parent == null) { + children = getRootNodes(); + } else { + children = parent.getChildren(); + } + + int idx = children.indexOf(node); + if (idx < children.size() - 1) { + return children.get(idx + 1); + } + + return null; + } + + /** + * Returns the previous sibling in the tree + * + * @param node + * The node to get the sibling for + * @return The sibling node or null if the node is the first sibling + */ + private TreeNode getPreviousSibling(TreeNode node) { + TreeNode parent = node.getParentNode(); + List children; + if (parent == null) { + children = getRootNodes(); + } else { + children = parent.getChildren(); + } + + int idx = children.indexOf(node); + if (idx > 0) { + return children.get(idx - 1); + } + + return null; + } + + /** + * Add this to the element mouse down event by using element.setPropertyJSO + * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again + * when the mouse is depressed in the mouse up event. + * + * @return Returns the JSO preventing text selection + */ + private native JavaScriptObject applyDisableTextSelectionIEHack() + /*-{ + return function(){ return false; }; + }-*/; + + /** + * Get the key that moves the selection head upwards. By default it is the + * up arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationUpKey() { + return KeyCodes.KEY_UP; + } + + /** + * Get the key that moves the selection head downwards. By default it is the + * down arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationDownKey() { + return KeyCodes.KEY_DOWN; + } + + /** + * Get the key that scrolls to the left in the table. By default it is the + * left arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationLeftKey() { + return KeyCodes.KEY_LEFT; + } + + /** + * Get the key that scroll to the right on the table. By default it is the + * right arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationRightKey() { + return KeyCodes.KEY_RIGHT; + } + + /** + * Get the key that selects an item in the table. By default it is the space + * bar key but by overriding this you can change the key to whatever you + * want. + * + * @return + */ + protected int getNavigationSelectKey() { + return CHARCODE_SPACE; + } + + /** + * Get the key the moves the selection one page up in the table. By default + * this is the Page Up key but by overriding this you can change the key to + * whatever you want. + * + * @return + */ + protected int getNavigationPageUpKey() { + return KeyCodes.KEY_PAGEUP; + } + + /** + * Get the key the moves the selection one page down in the table. By + * default this is the Page Down key but by overriding this you can change + * the key to whatever you want. + * + * @return + */ + protected int getNavigationPageDownKey() { + return KeyCodes.KEY_PAGEDOWN; + } + + /** + * Get the key the moves the selection to the beginning of the table. By + * default this is the Home key but by overriding this you can change the + * key to whatever you want. + * + * @return + */ + protected int getNavigationStartKey() { + return KeyCodes.KEY_HOME; + } + + /** + * Get the key the moves the selection to the end of the table. By default + * this is the End key but by overriding this you can change the key to + * whatever you want. + * + * @return + */ + protected int getNavigationEndKey() { + return KeyCodes.KEY_END; + } + + private final String SUBPART_NODE_PREFIX = "n"; + private final String EXPAND_IDENTIFIER = "expand"; + + /* + * In webkit, focus may have been requested for this component but not yet + * gained. Use this to trac if tree has gained the focus on webkit. See + * FocusImplSafari and #6373 + */ + private boolean treeHasFocus; + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ui.SubPartAware#getSubPartElement(java + * .lang.String) + */ + @Override + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + if ("fe".equals(subPart)) { + if (BrowserInfo.get().isOpera() && focusedNode != null) { + return focusedNode.getElement(); + } + return getFocusElement(); + } + + if (subPart.startsWith(SUBPART_NODE_PREFIX + "[")) { + boolean expandCollapse = false; + + // Node + String[] nodes = subPart.split("/"); + TreeNode treeNode = null; + try { + for (String node : nodes) { + if (node.startsWith(SUBPART_NODE_PREFIX)) { + + // skip SUBPART_NODE_PREFIX"[" + node = node.substring(SUBPART_NODE_PREFIX.length() + 1); + // skip "]" + node = node.substring(0, node.length() - 1); + int position = Integer.parseInt(node); + if (treeNode == null) { + treeNode = getRootNodes().get(position); + } else { + treeNode = treeNode.getChildren().get(position); + } + } else if (node.startsWith(EXPAND_IDENTIFIER)) { + expandCollapse = true; + } + } + + if (expandCollapse) { + return treeNode.getElement(); + } else { + return DOM.asOld(treeNode.nodeCaptionSpan); + } + } catch (Exception e) { + // Invalid locator string or node could not be found + return null; + } + } + return null; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ui.SubPartAware#getSubPartName(com.google + * .gwt.user.client.Element) + */ + @Override + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + // Supported identifiers: + // + // n[index]/n[index]/n[index]{/expand} + // + // Ends with "/expand" if the target is expand/collapse indicator, + // otherwise ends with the node + + boolean isExpandCollapse = false; + + if (!getElement().isOrHasChild(subElement)) { + return null; + } + + if (subElement == getFocusElement()) { + return "fe"; + } + + TreeNode treeNode = WidgetUtil.findWidget(subElement, TreeNode.class); + if (treeNode == null) { + // Did not click on a node, let somebody else take care of the + // locator string + return null; + } + + if (subElement == treeNode.getElement()) { + // Targets expand/collapse arrow + isExpandCollapse = true; + } + + ArrayList positions = new ArrayList(); + while (treeNode.getParentNode() != null) { + positions.add(0, + treeNode.getParentNode().getChildren().indexOf(treeNode)); + treeNode = treeNode.getParentNode(); + } + positions.add(0, getRootNodes().indexOf(treeNode)); + + String locator = ""; + for (Integer i : positions) { + locator += SUBPART_NODE_PREFIX + "[" + i + "]/"; + } + + locator = locator.substring(0, locator.length() - 1); + if (isExpandCollapse) { + locator += "/" + EXPAND_IDENTIFIER; + } + return locator; + } + + @Override + public Action[] getActions() { + if (bodyActionKeys == null) { + return new Action[] {}; + } + final Action[] actions = new Action[bodyActionKeys.length]; + for (int i = 0; i < actions.length; i++) { + final String actionKey = bodyActionKeys[i]; + final TreeAction a = new TreeAction(this, null, actionKey); + a.setCaption(getActionCaption(actionKey)); + a.setIconUrl(getActionIcon(actionKey)); + actions[i] = a; + } + return actions; + } + + @Override + public ApplicationConnection getClient() { + return client; + } + + @Override + public String getPaintableId() { + return paintableId; + } + + private void handleBodyContextMenu(ContextMenuEvent event) { + if (!readonly && !disabled) { + if (bodyActionKeys != null) { + int left = event.getNativeEvent().getClientX(); + int top = event.getNativeEvent().getClientY(); + top += Window.getScrollTop(); + left += Window.getScrollLeft(); + client.getContextMenu().showAt(this, left, top); + } + event.stopPropagation(); + event.preventDefault(); + } + } + + public void registerAction(String key, String caption, String iconUrl) { + actionMap.put(key + "_c", caption); + if (iconUrl != null) { + actionMap.put(key + "_i", iconUrl); + } else { + actionMap.remove(key + "_i"); + } + + } + + public void registerNode(TreeNode treeNode) { + keyToNode.put(treeNode.key, treeNode); + } + + public void clearNodeToKeyMap() { + keyToNode.clear(); + } + + @Override + public void bindAriaCaption( + com.google.gwt.user.client.Element captionElement) { + AriaHelper.bindCaption(body, captionElement); + } + + /** + * Tell LayoutManager that a layout is needed later for this VTree + */ + private void doLayout() { + // IE8 needs a hack to measure the tree again after update + WidgetUtil.forceIE8Redraw(getElement()); + + // This calls LayoutManager setNeedsMeasure and layoutNow + Util.notifyParentOfSizeChange(this, false); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VTreeTable.java b/client/src/main/java/com/vaadin/client/ui/VTreeTable.java new file mode 100644 index 0000000000..0ba84af4bb --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VTreeTable.java @@ -0,0 +1,904 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import com.google.gwt.animation.client.Animation; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.SpanElement; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.dom.client.TableCellElement; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ComputedStyle; +import com.vaadin.client.UIDL; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.VTreeTable.VTreeTableScrollBody.VTreeTableRow; + +public class VTreeTable extends VScrollTable { + + /** For internal use only. May be removed or replaced in the future. */ + public static class PendingNavigationEvent { + public final int keycode; + public final boolean ctrl; + public final boolean shift; + + public PendingNavigationEvent(int keycode, boolean ctrl, boolean shift) { + this.keycode = keycode; + this.ctrl = ctrl; + this.shift = shift; + } + + @Override + public String toString() { + String string = "Keyboard event: " + keycode; + if (ctrl) { + string += " + ctrl"; + } + if (shift) { + string += " + shift"; + } + return string; + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public boolean collapseRequest; + + private boolean selectionPending; + + /** For internal use only. May be removed or replaced in the future. */ + public int colIndexOfHierarchy; + + /** For internal use only. May be removed or replaced in the future. */ + public String collapsedRowKey; + + /** For internal use only. May be removed or replaced in the future. */ + public VTreeTableScrollBody scrollBody; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean animationsEnabled; + + /** For internal use only. May be removed or replaced in the future. */ + public LinkedList pendingNavigationEvents = new LinkedList(); + + /** For internal use only. May be removed or replaced in the future. */ + public boolean focusParentResponsePending; + + @Override + protected VScrollTableBody createScrollBody() { + scrollBody = new VTreeTableScrollBody(); + return scrollBody; + } + + /* + * Overridden to allow animation of expands and collapses of nodes. + */ + @Override + public void addAndRemoveRows(UIDL partialRowAdditions) { + if (partialRowAdditions == null) { + return; + } + + if (animationsEnabled) { + if (partialRowAdditions.hasAttribute("hide")) { + scrollBody.unlinkRowsAnimatedAndUpdateCacheWhenFinished( + partialRowAdditions.getIntAttribute("firstprowix"), + partialRowAdditions.getIntAttribute("numprows")); + } else { + scrollBody.insertRowsAnimated(partialRowAdditions, + partialRowAdditions.getIntAttribute("firstprowix"), + partialRowAdditions.getIntAttribute("numprows")); + discardRowsOutsideCacheWindow(); + } + } else { + super.addAndRemoveRows(partialRowAdditions); + } + } + + @Override + protected int getHierarchyColumnIndex() { + return colIndexOfHierarchy + (showRowHeaders ? 1 : 0); + } + + public class VTreeTableScrollBody extends VScrollTable.VScrollTableBody { + private int indentWidth = -1; + private int maxIndent = 0; + + protected VTreeTableScrollBody() { + super(); + } + + @Override + protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) { + if (uidl.hasAttribute("gen_html")) { + // This is a generated row. + return new VTreeTableGeneratedRow(uidl, aligns2); + } + return new VTreeTableRow(uidl, aligns2); + } + + public class VTreeTableRow extends + VScrollTable.VScrollTableBody.VScrollTableRow { + + private boolean isTreeCellAdded = false; + private SpanElement treeSpacer; + private boolean open; + private int depth; + private boolean canHaveChildren; + protected Widget widgetInHierarchyColumn; + + public VTreeTableRow(UIDL uidl, char[] aligns2) { + super(uidl, aligns2); + // this fix causes #15118 and doesn't work for treetable anyway + applyZeroWidthFix = false; + } + + @Override + public void addCell(UIDL rowUidl, String text, char align, + String style, boolean textIsHTML, boolean isSorted, + String description) { + super.addCell(rowUidl, text, align, style, textIsHTML, + isSorted, description); + + addTreeSpacer(rowUidl); + } + + protected boolean addTreeSpacer(UIDL rowUidl) { + if (cellShowsTreeHierarchy(getElement().getChildCount() - 1)) { + Element container = (Element) getElement().getLastChild() + .getFirstChild(); + + if (rowUidl.hasAttribute("icon")) { + Icon icon = client.getIcon(rowUidl + .getStringAttribute("icon")); + icon.setAlternateText("icon"); + container.insertFirst(icon.getElement()); + } + + String classname = "v-treetable-treespacer"; + if (rowUidl.getBooleanAttribute("ca")) { + canHaveChildren = true; + open = rowUidl.getBooleanAttribute("open"); + classname += open ? " v-treetable-node-open" + : " v-treetable-node-closed"; + } + + treeSpacer = Document.get().createSpanElement(); + + treeSpacer.setClassName(classname); + container.insertFirst(treeSpacer); + depth = rowUidl.hasAttribute("depth") ? rowUidl + .getIntAttribute("depth") : 0; + setIndent(); + isTreeCellAdded = true; + return true; + } + return false; + } + + private boolean cellShowsTreeHierarchy(int curColIndex) { + if (isTreeCellAdded) { + return false; + } + return curColIndex == getHierarchyColumnIndex(); + } + + @Override + public void onBrowserEvent(Event event) { + if (event.getEventTarget().cast() == treeSpacer + && treeSpacer.getClassName().contains("node")) { + if (event.getTypeInt() == Event.ONMOUSEUP) { + sendToggleCollapsedUpdate(getKey()); + } + return; + } + super.onBrowserEvent(event); + } + + @Override + public void addCell(UIDL rowUidl, Widget w, char align, + String style, boolean isSorted, String description) { + super.addCell(rowUidl, w, align, style, isSorted, description); + if (addTreeSpacer(rowUidl)) { + widgetInHierarchyColumn = w; + } + + } + + private void setIndent() { + if (getIndentWidth() > 0) { + treeSpacer.getParentElement().getStyle() + .setPaddingLeft(getIndent(), Unit.PX); + treeSpacer.getStyle().setWidth(getIndent(), Unit.PX); + int colWidth = getColWidth(getHierarchyColumnIndex()); + if (colWidth > 0 && getIndent() > colWidth) { + VTreeTable.this.setColWidth(getHierarchyColumnIndex(), + getIndent(), false); + } + } + } + + @Override + protected void onAttach() { + super.onAttach(); + if (getIndentWidth() < 0) { + detectIndent(this); + // If we detect indent here then the size of the hierarchy + // column is still wrong as it has been set when the indent + // was not known. + int w = getCellWidthFromDom(getHierarchyColumnIndex()); + if (w >= 0) { + setColWidth(getHierarchyColumnIndex(), w); + } + } + } + + private int getCellWidthFromDom(int cellIndex) { + final Element cell = DOM.getChild(getElement(), cellIndex); + String w = cell.getStyle().getProperty("width"); + if (w == null || "".equals(w) || !w.endsWith("px")) { + return -1; + } else { + return Integer.parseInt(w.substring(0, w.length() - 2)); + } + } + + private int getHierarchyAndIconWidth() { + int consumedSpace = treeSpacer.getOffsetWidth(); + if (treeSpacer.getParentElement().getChildCount() > 2) { + // icon next to tree spacer + consumedSpace += ((com.google.gwt.dom.client.Element) treeSpacer + .getNextSibling()).getOffsetWidth(); + } + return consumedSpace; + } + + @Override + protected void setCellWidth(int cellIx, int width) { + if (cellIx == getHierarchyColumnIndex()) { + // take indentation padding into account if this is the + // hierarchy column + int indent = getIndent(); + if (indent != -1) { + width = Math.max(width - indent, 0); + } + } + super.setCellWidth(cellIx, width); + } + + private int getIndent() { + return (depth + 1) * getIndentWidth(); + } + } + + protected class VTreeTableGeneratedRow extends VTreeTableRow { + private boolean spanColumns; + private boolean htmlContentAllowed; + + public VTreeTableGeneratedRow(UIDL uidl, char[] aligns) { + super(uidl, aligns); + addStyleName("v-table-generated-row"); + } + + public boolean isSpanColumns() { + return spanColumns; + } + + @Override + protected void initCellWidths() { + if (spanColumns) { + setSpannedColumnWidthAfterDOMFullyInited(); + } else { + super.initCellWidths(); + } + } + + private void setSpannedColumnWidthAfterDOMFullyInited() { + // Defer setting width on spanned columns to make sure that + // they are added to the DOM before trying to calculate + // widths. + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + + @Override + public void execute() { + if (showRowHeaders) { + setCellWidth(0, tHead.getHeaderCell(0) + .getWidthWithIndent()); + calcAndSetSpanWidthOnCell(1); + } else { + calcAndSetSpanWidthOnCell(0); + } + } + }); + } + + @Override + protected boolean isRenderHtmlInCells() { + return htmlContentAllowed; + } + + @Override + protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, + int visibleColumnIndex) { + htmlContentAllowed = uidl.getBooleanAttribute("gen_html"); + spanColumns = uidl.getBooleanAttribute("gen_span"); + + final Iterator cells = uidl.getChildIterator(); + if (spanColumns) { + int colCount = uidl.getChildCount(); + if (cells.hasNext()) { + final Object cell = cells.next(); + if (cell instanceof String) { + addSpannedCell(uidl, cell.toString(), aligns[0], + "", htmlContentAllowed, false, null, + colCount); + } else { + addSpannedCell(uidl, (Widget) cell, aligns[0], "", + false, colCount); + } + } + } else { + super.addCellsFromUIDL(uidl, aligns, col, + visibleColumnIndex); + } + } + + private void addSpannedCell(UIDL rowUidl, Widget w, char align, + String style, boolean sorted, int colCount) { + TableCellElement td = DOM.createTD().cast(); + td.setColSpan(colCount); + initCellWithWidget(w, align, style, sorted, td); + td.getStyle().setHeight(getRowHeight(), Unit.PX); + if (addTreeSpacer(rowUidl)) { + widgetInHierarchyColumn = w; + } + } + + private void addSpannedCell(UIDL rowUidl, String text, char align, + String style, boolean textIsHTML, boolean sorted, + String description, int colCount) { + // String only content is optimized by not using Label widget + final TableCellElement td = DOM.createTD().cast(); + td.setColSpan(colCount); + initCellWithText(text, align, style, textIsHTML, sorted, + description, td); + td.getStyle().setHeight(getRowHeight(), Unit.PX); + addTreeSpacer(rowUidl); + } + + @Override + protected void setCellWidth(int cellIx, int width) { + if (isSpanColumns()) { + if (showRowHeaders) { + if (cellIx == 0) { + super.setCellWidth(0, width); + } else { + // We need to recalculate the spanning TDs width for + // every cellIx in order to support column resizing. + calcAndSetSpanWidthOnCell(1); + } + } else { + // Same as above. + calcAndSetSpanWidthOnCell(0); + } + } else { + super.setCellWidth(cellIx, width); + } + } + + private void calcAndSetSpanWidthOnCell(final int cellIx) { + int spanWidth = 0; + for (int ix = (showRowHeaders ? 1 : 0); ix < tHead + .getVisibleCellCount(); ix++) { + spanWidth += tHead.getHeaderCell(ix).getOffsetWidth(); + } + WidgetUtil.setWidthExcludingPaddingAndBorder( + (Element) getElement().getChild(cellIx), spanWidth, 13, + false); + } + } + + private int getIndentWidth() { + return indentWidth; + } + + @Override + protected int getMaxIndent() { + return maxIndent; + } + + @Override + protected void calculateMaxIndent() { + int maxIndent = 0; + Iterator iterator = iterator(); + while (iterator.hasNext()) { + VTreeTableRow next = (VTreeTableRow) iterator.next(); + maxIndent = Math.max(maxIndent, next.getIndent()); + } + this.maxIndent = maxIndent; + } + + private void detectIndent(VTreeTableRow vTreeTableRow) { + indentWidth = vTreeTableRow.treeSpacer.getOffsetWidth(); + if (indentWidth == 0) { + indentWidth = -1; + return; + } + Iterator iterator = iterator(); + while (iterator.hasNext()) { + VTreeTableRow next = (VTreeTableRow) iterator.next(); + next.setIndent(); + } + calculateMaxIndent(); + } + + protected void unlinkRowsAnimatedAndUpdateCacheWhenFinished( + final int firstIndex, final int rows) { + List rowsToDelete = new ArrayList(); + for (int ix = firstIndex; ix < firstIndex + rows; ix++) { + VScrollTableRow row = getRowByRowIndex(ix); + if (row != null) { + rowsToDelete.add(row); + } + } + if (!rowsToDelete.isEmpty()) { + // #8810 Only animate if there's something to animate + RowCollapseAnimation anim = new RowCollapseAnimation( + rowsToDelete) { + @Override + protected void onComplete() { + super.onComplete(); + // Actually unlink the rows and update the cache after + // the + // animation is done. + unlinkAndReindexRows(firstIndex, rows); + discardRowsOutsideCacheWindow(); + ensureCacheFilled(); + } + }; + anim.run(150); + } + } + + protected List insertRowsAnimated(UIDL rowData, + int firstIndex, int rows) { + List insertedRows = insertAndReindexRows(rowData, + firstIndex, rows); + if (!insertedRows.isEmpty()) { + // Only animate if there's something to animate (#8810) + RowExpandAnimation anim = new RowExpandAnimation(insertedRows); + anim.run(150); + } + scrollBody.calculateMaxIndent(); + return insertedRows; + } + + /** + * Prepares the table for animation by copying the background colors of + * all TR elements to their respective TD elements if the TD element is + * transparent. This is needed, since if TDs have transparent + * backgrounds, the rows sliding behind them are visible. + */ + private class AnimationPreparator { + private final int lastItemIx; + + public AnimationPreparator(int lastItemIx) { + this.lastItemIx = lastItemIx; + } + + public void prepareTableForAnimation() { + int ix = lastItemIx; + VScrollTableRow row = null; + while ((row = getRowByRowIndex(ix)) != null) { + copyTRBackgroundsToTDs(row); + --ix; + } + } + + private void copyTRBackgroundsToTDs(VScrollTableRow row) { + Element tr = row.getElement(); + ComputedStyle cs = new ComputedStyle(tr); + String backgroundAttachment = cs + .getProperty("backgroundAttachment"); + String backgroundClip = cs.getProperty("backgroundClip"); + String backgroundColor = cs.getProperty("backgroundColor"); + String backgroundImage = cs.getProperty("backgroundImage"); + String backgroundOrigin = cs.getProperty("backgroundOrigin"); + for (int ix = 0; ix < tr.getChildCount(); ix++) { + Element td = tr.getChild(ix).cast(); + if (!elementHasBackground(td)) { + td.getStyle().setProperty("backgroundAttachment", + backgroundAttachment); + td.getStyle().setProperty("backgroundClip", + backgroundClip); + td.getStyle().setProperty("backgroundColor", + backgroundColor); + td.getStyle().setProperty("backgroundImage", + backgroundImage); + td.getStyle().setProperty("backgroundOrigin", + backgroundOrigin); + } + } + } + + private boolean elementHasBackground(Element element) { + ComputedStyle cs = new ComputedStyle(element); + String clr = cs.getProperty("backgroundColor"); + String img = cs.getProperty("backgroundImage"); + return !("rgba(0, 0, 0, 0)".equals(clr.trim()) + || "transparent".equals(clr.trim()) || img == null); + } + + public void restoreTableAfterAnimation() { + int ix = lastItemIx; + VScrollTableRow row = null; + while ((row = getRowByRowIndex(ix)) != null) { + restoreStyleForTDsInRow(row); + + --ix; + } + } + + private void restoreStyleForTDsInRow(VScrollTableRow row) { + Element tr = row.getElement(); + for (int ix = 0; ix < tr.getChildCount(); ix++) { + Element td = tr.getChild(ix).cast(); + td.getStyle().clearProperty("backgroundAttachment"); + td.getStyle().clearProperty("backgroundClip"); + td.getStyle().clearProperty("backgroundColor"); + td.getStyle().clearProperty("backgroundImage"); + td.getStyle().clearProperty("backgroundOrigin"); + } + } + } + + /** + * Animates row expansion using the GWT animation framework. + * + * The idea is as follows: + * + * 1. Insert all rows normally + * + * 2. Insert a newly created DIV containing a new TABLE element below + * the DIV containing the actual scroll table body. + * + * 3. Clone the rows that were inserted in step 1 and attach the clones + * to the new TABLE element created in step 2. + * + * 4. The new DIV from step 2 is absolutely positioned so that the last + * inserted row is just behind the row that was expanded. + * + * 5. Hide the contents of the originally inserted rows by setting the + * DIV.v-table-cell-wrapper to display:none;. + * + * 6. Set the height of the originally inserted rows to 0. + * + * 7. The animation loop slides the DIV from step 2 downwards, while at + * the same pace growing the height of each of the inserted rows from 0 + * to full height. The first inserted row grows from 0 to full and after + * this the second row grows from 0 to full, etc until all rows are full + * height. + * + * 8. Remove the DIV from step 2 + * + * 9. Restore display:block; to the DIV.v-table-cell-wrapper elements. + * + * 10. DONE + */ + private class RowExpandAnimation extends Animation { + + private final List rows; + private Element cloneDiv; + private Element cloneTable; + private AnimationPreparator preparator; + + /** + * @param rows + * List of rows to animate. Must not be empty. + */ + public RowExpandAnimation(List rows) { + this.rows = rows; + buildAndInsertAnimatingDiv(); + preparator = new AnimationPreparator(rows.get(0).getIndex() - 1); + preparator.prepareTableForAnimation(); + for (VScrollTableRow row : rows) { + cloneAndAppendRow(row); + row.addStyleName("v-table-row-animating"); + setCellWrapperDivsToDisplayNone(row); + row.setHeight(getInitialHeight()); + } + } + + protected String getInitialHeight() { + return "0px"; + } + + private void cloneAndAppendRow(VScrollTableRow row) { + Element clonedTR = null; + clonedTR = row.getElement().cloneNode(true).cast(); + clonedTR.getStyle().setVisibility(Visibility.VISIBLE); + cloneTable.appendChild(clonedTR); + } + + protected double getBaseOffset() { + return rows.get(0).getAbsoluteTop() + - rows.get(0).getParent().getAbsoluteTop() + - rows.size() * getRowHeight(); + } + + private void buildAndInsertAnimatingDiv() { + cloneDiv = DOM.createDiv(); + cloneDiv.addClassName("v-treetable-animation-clone-wrapper"); + cloneTable = DOM.createTable(); + cloneTable.addClassName("v-treetable-animation-clone"); + cloneDiv.appendChild(cloneTable); + insertAnimatingDiv(); + } + + private void insertAnimatingDiv() { + Element tableBody = getElement(); + Element tableBodyParent = tableBody.getParentElement(); + tableBodyParent.insertAfter(cloneDiv, tableBody); + } + + @Override + protected void onUpdate(double progress) { + animateDiv(progress); + animateRowHeights(progress); + } + + private void animateDiv(double progress) { + double offset = calculateDivOffset(progress, getRowHeight()); + + cloneDiv.getStyle().setTop(getBaseOffset() + offset, Unit.PX); + } + + private void animateRowHeights(double progress) { + double rh = getRowHeight(); + double vlh = calculateHeightOfAllVisibleLines(progress, rh); + int ix = 0; + + while (ix < rows.size()) { + double height = vlh < rh ? vlh : rh; + rows.get(ix).setHeight(height + "px"); + vlh -= height; + ix++; + } + } + + protected double calculateHeightOfAllVisibleLines(double progress, + double rh) { + return rows.size() * rh * progress; + } + + protected double calculateDivOffset(double progress, double rh) { + return progress * rows.size() * rh; + } + + @Override + protected void onComplete() { + preparator.restoreTableAfterAnimation(); + for (VScrollTableRow row : rows) { + resetCellWrapperDivsDisplayProperty(row); + row.removeStyleName("v-table-row-animating"); + } + Element tableBodyParent = getElement().getParentElement(); + tableBodyParent.removeChild(cloneDiv); + } + + private void setCellWrapperDivsToDisplayNone(VScrollTableRow row) { + Element tr = row.getElement(); + for (int ix = 0; ix < tr.getChildCount(); ix++) { + getWrapperDiv(tr, ix).getStyle().setDisplay(Display.NONE); + } + } + + private Element getWrapperDiv(Element tr, int tdIx) { + Element td = tr.getChild(tdIx).cast(); + return td.getChild(0).cast(); + } + + private void resetCellWrapperDivsDisplayProperty(VScrollTableRow row) { + Element tr = row.getElement(); + for (int ix = 0; ix < tr.getChildCount(); ix++) { + getWrapperDiv(tr, ix).getStyle().clearProperty("display"); + } + } + + } + + /** + * This is the inverse of the RowExpandAnimation and is implemented by + * extending it and overriding the calculation of offsets and heights. + */ + private class RowCollapseAnimation extends RowExpandAnimation { + + private final List rows; + + /** + * @param rows + * List of rows to animate. Must not be empty. + */ + public RowCollapseAnimation(List rows) { + super(rows); + this.rows = rows; + } + + @Override + protected String getInitialHeight() { + return getRowHeight() + "px"; + } + + @Override + protected double getBaseOffset() { + return getRowHeight(); + } + + @Override + protected double calculateHeightOfAllVisibleLines(double progress, + double rh) { + return rows.size() * rh * (1 - progress); + } + + @Override + protected double calculateDivOffset(double progress, double rh) { + return -super.calculateDivOffset(progress, rh); + } + } + } + + /** + * Icons rendered into first actual column in TreeTable, not to row header + * cell + */ + @Override + protected String buildCaptionHtmlSnippet(UIDL uidl) { + if (uidl.getTag().equals("column")) { + return super.buildCaptionHtmlSnippet(uidl); + } else { + String s = uidl.getStringAttribute("caption"); + return s; + } + } + + /** For internal use only. May be removed or replaced in the future. */ + @Override + public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { + if (collapseRequest || focusParentResponsePending) { + // Enqueue the event if there might be pending content changes from + // the server + if (pendingNavigationEvents.size() < 10) { + // Only keep 10 keyboard events in the queue + PendingNavigationEvent pendingNavigationEvent = new PendingNavigationEvent( + keycode, ctrl, shift); + pendingNavigationEvents.add(pendingNavigationEvent); + } + return true; + } + + VTreeTableRow focusedRow = (VTreeTableRow) getFocusedRow(); + if (focusedRow != null) { + if (focusedRow.canHaveChildren + && ((keycode == KeyCodes.KEY_RIGHT && !focusedRow.open) || (keycode == KeyCodes.KEY_LEFT && focusedRow.open))) { + if (!ctrl) { + client.updateVariable(paintableId, "selectCollapsed", true, + false); + } + sendSelectedRows(false); + sendToggleCollapsedUpdate(focusedRow.getKey()); + return true; + } else if (keycode == KeyCodes.KEY_RIGHT && focusedRow.open) { + // already expanded, move selection down if next is on a deeper + // level (is-a-child) + VTreeTableScrollBody body = (VTreeTableScrollBody) focusedRow + .getParent(); + Iterator iterator = body.iterator(); + VTreeTableRow next = null; + while (iterator.hasNext()) { + next = (VTreeTableRow) iterator.next(); + if (next == focusedRow) { + next = (VTreeTableRow) iterator.next(); + break; + } + } + if (next != null) { + if (next.depth > focusedRow.depth) { + selectionPending = true; + return super.handleNavigation(getNavigationDownKey(), + ctrl, shift); + } + } else { + // Note, a minor change here for a bit false behavior if + // cache rows is disabled + last visible row + no childs for + // the node + selectionPending = true; + return super.handleNavigation(getNavigationDownKey(), ctrl, + shift); + } + } else if (keycode == KeyCodes.KEY_LEFT) { + // already collapsed move selection up to parent node + // do on the server side as the parent is not necessary + // rendered on the client, could check if parent is visible if + // a performance issue arises + + client.updateVariable(paintableId, "focusParent", + focusedRow.getKey(), true); + + // Set flag that we should enqueue navigation events until we + // get a response to this request + focusParentResponsePending = true; + + return true; + } + } + return super.handleNavigation(keycode, ctrl, shift); + } + + private void sendToggleCollapsedUpdate(String rowKey) { + collapsedRowKey = rowKey; + collapseRequest = true; + client.updateVariable(paintableId, "toggleCollapsed", rowKey, true); + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (event.getTypeInt() == Event.ONKEYUP && selectionPending) { + sendSelectedRows(); + } + } + + @Override + protected void sendSelectedRows(boolean immediately) { + super.sendSelectedRows(immediately); + selectionPending = false; + } + + @Override + protected void reOrderColumn(String columnKey, int newIndex) { + super.reOrderColumn(columnKey, newIndex); + // current impl not intelligent enough to survive without visiting the + // server to redraw content + client.sendPendingVariableChanges(); + } + + @Override + public void setStyleName(String style) { + super.setStyleName(style + " v-treetable"); + } + + @Override + public void updateTotalRows(UIDL uidl) { + // Make sure that initializedAndAttached & al are not reset when the + // totalrows are updated on expand/collapse requests. + int newTotalRows = uidl.getIntAttribute("totalrows"); + setTotalRows(newTotalRows); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VTwinColSelect.java b/client/src/main/java/com/vaadin/client/ui/VTwinColSelect.java new file mode 100644 index 0000000000..853bd8d456 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VTwinColSelect.java @@ -0,0 +1,630 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.DoubleClickEvent; +import com.google.gwt.event.dom.client.DoubleClickHandler; +import com.google.gwt.event.dom.client.HasDoubleClickHandlers; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.MouseDownEvent; +import com.google.gwt.event.dom.client.MouseDownHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.Panel; +import com.vaadin.client.StyleConstants; +import com.vaadin.client.UIDL; +import com.vaadin.client.WidgetUtil; +import com.vaadin.shared.ui.twincolselect.TwinColSelectConstants; + +public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, + MouseDownHandler, DoubleClickHandler, SubPartAware { + + public static final String CLASSNAME = "v-select-twincol"; + + private static final int VISIBLE_COUNT = 10; + + private static final int DEFAULT_COLUMN_COUNT = 10; + + private final DoubleClickListBox options; + + private final DoubleClickListBox selections; + + /** For internal use only. May be removed or replaced in the future. */ + public FlowPanel captionWrapper; + + private HTML optionsCaption = null; + + private HTML selectionsCaption = null; + + private final VButton add; + + private final VButton remove; + + private final FlowPanel buttons; + + private final Panel panel; + + /** + * A ListBox which catches double clicks + * + */ + public class DoubleClickListBox extends ListBox implements + HasDoubleClickHandlers { + public DoubleClickListBox(boolean isMultipleSelect) { + super(isMultipleSelect); + } + + public DoubleClickListBox() { + super(); + } + + @Override + public HandlerRegistration addDoubleClickHandler( + DoubleClickHandler handler) { + return addDomHandler(handler, DoubleClickEvent.getType()); + } + } + + public VTwinColSelect() { + super(CLASSNAME); + + captionWrapper = new FlowPanel(); + + options = new DoubleClickListBox(); + options.addClickHandler(this); + options.addDoubleClickHandler(this); + options.setVisibleItemCount(VISIBLE_COUNT); + options.setStyleName(CLASSNAME + "-options"); + + selections = new DoubleClickListBox(); + selections.addClickHandler(this); + selections.addDoubleClickHandler(this); + selections.setVisibleItemCount(VISIBLE_COUNT); + selections.setStyleName(CLASSNAME + "-selections"); + + buttons = new FlowPanel(); + buttons.setStyleName(CLASSNAME + "-buttons"); + add = new VButton(); + add.setText(">>"); + add.addClickHandler(this); + remove = new VButton(); + remove.setText("<<"); + remove.addClickHandler(this); + + panel = ((Panel) optionsContainer); + + panel.add(captionWrapper); + captionWrapper.getElement().getStyle().setOverflow(Overflow.HIDDEN); + // Hide until there actually is a caption to prevent IE from rendering + // extra empty space + captionWrapper.setVisible(false); + + panel.add(options); + buttons.add(add); + final HTML br = new HTML(""); + br.setStyleName(CLASSNAME + "-deco"); + buttons.add(br); + buttons.add(remove); + panel.add(buttons); + panel.add(selections); + + options.addKeyDownHandler(this); + options.addMouseDownHandler(this); + + selections.addMouseDownHandler(this); + selections.addKeyDownHandler(this); + + updateEnabledState(); + } + + public HTML getOptionsCaption() { + if (optionsCaption == null) { + optionsCaption = new HTML(); + optionsCaption.setStyleName(CLASSNAME + "-caption-left"); + optionsCaption.getElement().getStyle() + .setFloat(com.google.gwt.dom.client.Style.Float.LEFT); + captionWrapper.add(optionsCaption); + } + + return optionsCaption; + } + + public HTML getSelectionsCaption() { + if (selectionsCaption == null) { + selectionsCaption = new HTML(); + selectionsCaption.setStyleName(CLASSNAME + "-caption-right"); + selectionsCaption.getElement().getStyle() + .setFloat(com.google.gwt.dom.client.Style.Float.RIGHT); + captionWrapper.add(selectionsCaption); + } + + return selectionsCaption; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void updateCaptions(UIDL uidl) { + String leftCaption = (uidl + .hasAttribute(TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION) ? uidl + .getStringAttribute(TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION) + : null); + String rightCaption = (uidl + .hasAttribute(TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION) ? uidl + .getStringAttribute(TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION) + : null); + + boolean hasCaptions = (leftCaption != null || rightCaption != null); + + if (leftCaption == null) { + removeOptionsCaption(); + } else { + getOptionsCaption().setText(leftCaption); + + } + + if (rightCaption == null) { + removeSelectionsCaption(); + } else { + getSelectionsCaption().setText(rightCaption); + } + + captionWrapper.setVisible(hasCaptions); + } + + private void removeOptionsCaption() { + if (optionsCaption == null) { + return; + } + + if (optionsCaption.getParent() != null) { + captionWrapper.remove(optionsCaption); + } + + optionsCaption = null; + } + + private void removeSelectionsCaption() { + if (selectionsCaption == null) { + return; + } + + if (selectionsCaption.getParent() != null) { + captionWrapper.remove(selectionsCaption); + } + + selectionsCaption = null; + } + + @Override + public void buildOptions(UIDL uidl) { + options.setMultipleSelect(isMultiselect()); + selections.setMultipleSelect(isMultiselect()); + options.clear(); + selections.clear(); + for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { + final UIDL optionUidl = (UIDL) i.next(); + if (optionUidl.hasAttribute("selected")) { + selections.addItem(optionUidl.getStringAttribute("caption"), + optionUidl.getStringAttribute("key")); + } else { + options.addItem(optionUidl.getStringAttribute("caption"), + optionUidl.getStringAttribute("key")); + } + } + + if (getRows() > 0) { + options.setVisibleItemCount(getRows()); + selections.setVisibleItemCount(getRows()); + + } + } + + @Override + protected String[] getSelectedItems() { + final ArrayList selectedItemKeys = new ArrayList(); + for (int i = 0; i < selections.getItemCount(); i++) { + selectedItemKeys.add(selections.getValue(i)); + } + return selectedItemKeys.toArray(new String[selectedItemKeys.size()]); + } + + private boolean[] getSelectionBitmap(ListBox listBox) { + final boolean[] selectedIndexes = new boolean[listBox.getItemCount()]; + for (int i = 0; i < listBox.getItemCount(); i++) { + if (listBox.isItemSelected(i)) { + selectedIndexes[i] = true; + } else { + selectedIndexes[i] = false; + } + } + return selectedIndexes; + } + + private void addItem() { + Set movedItems = moveSelectedItems(options, selections); + selectedKeys.addAll(movedItems); + + client.updateVariable(paintableId, "selected", + selectedKeys.toArray(new String[selectedKeys.size()]), + isImmediate()); + } + + private void removeItem() { + Set movedItems = moveSelectedItems(selections, options); + selectedKeys.removeAll(movedItems); + + client.updateVariable(paintableId, "selected", + selectedKeys.toArray(new String[selectedKeys.size()]), + isImmediate()); + } + + private Set moveSelectedItems(ListBox source, ListBox target) { + final boolean[] sel = getSelectionBitmap(source); + final Set movedItems = new HashSet(); + int lastSelected = 0; + for (int i = 0; i < sel.length; i++) { + if (sel[i]) { + final int optionIndex = i + - (sel.length - source.getItemCount()); + movedItems.add(source.getValue(optionIndex)); + + // Move selection to another column + final String text = source.getItemText(optionIndex); + final String value = source.getValue(optionIndex); + target.addItem(text, value); + target.setItemSelected(target.getItemCount() - 1, true); + source.removeItem(optionIndex); + + if (source.getItemCount() > 0) { + lastSelected = optionIndex > 0 ? optionIndex - 1 : 0; + } + } + } + + if (source.getItemCount() > 0) { + source.setSelectedIndex(lastSelected); + } + + // If no items are left move the focus to the selections + if (source.getItemCount() == 0) { + target.setFocus(true); + } else { + source.setFocus(true); + } + + return movedItems; + } + + @Override + public void onClick(ClickEvent event) { + super.onClick(event); + if (event.getSource() == add) { + addItem(); + + } else if (event.getSource() == remove) { + removeItem(); + + } else if (event.getSource() == options) { + // unselect all in other list, to avoid mistakes (i.e wrong button) + final int c = selections.getItemCount(); + for (int i = 0; i < c; i++) { + selections.setItemSelected(i, false); + } + } else if (event.getSource() == selections) { + // unselect all in other list, to avoid mistakes (i.e wrong button) + final int c = options.getItemCount(); + for (int i = 0; i < c; i++) { + options.setItemSelected(i, false); + } + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void clearInternalHeights() { + selections.setHeight(""); + options.setHeight(""); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setInternalHeights() { + int captionHeight = WidgetUtil.getRequiredHeight(captionWrapper); + int totalHeight = getOffsetHeight(); + + String selectHeight = (totalHeight - captionHeight) + "px"; + + selections.setHeight(selectHeight); + options.setHeight(selectHeight); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void clearInternalWidths() { + int cols = -1; + if (getColumns() > 0) { + cols = getColumns(); + } else { + cols = DEFAULT_COLUMN_COUNT; + } + + if (cols >= 0) { + String colWidth = cols + "em"; + String containerWidth = (2 * cols + 4) + "em"; + // Caption wrapper width == optionsSelect + buttons + + // selectionsSelect + String captionWrapperWidth = (2 * cols + 4 - 0.5) + "em"; + + options.setWidth(colWidth); + if (optionsCaption != null) { + optionsCaption.setWidth(colWidth); + } + selections.setWidth(colWidth); + if (selectionsCaption != null) { + selectionsCaption.setWidth(colWidth); + } + buttons.setWidth("3.5em"); + optionsContainer.setWidth(containerWidth); + captionWrapper.setWidth(captionWrapperWidth); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setInternalWidths() { + getElement().getStyle().setPosition(Position.RELATIVE); + int bordersAndPaddings = WidgetUtil.measureHorizontalPaddingAndBorder( + buttons.getElement(), 0); + + int buttonWidth = WidgetUtil.getRequiredWidth(buttons); + int totalWidth = getOffsetWidth(); + + int spaceForSelect = (totalWidth - buttonWidth - bordersAndPaddings) / 2; + + options.setWidth(spaceForSelect + "px"); + if (optionsCaption != null) { + optionsCaption.setWidth(spaceForSelect + "px"); + } + + selections.setWidth(spaceForSelect + "px"); + if (selectionsCaption != null) { + selectionsCaption.setWidth(spaceForSelect + "px"); + } + captionWrapper.setWidth("100%"); + } + + @Override + public void setTabIndex(int tabIndex) { + options.setTabIndex(tabIndex); + selections.setTabIndex(tabIndex); + add.setTabIndex(tabIndex); + remove.setTabIndex(tabIndex); + } + + @Override + public void updateEnabledState() { + boolean enabled = isEnabled() && !isReadonly(); + options.setEnabled(enabled); + selections.setEnabled(enabled); + add.setEnabled(enabled); + remove.setEnabled(enabled); + add.setStyleName(StyleConstants.DISABLED, !enabled); + remove.setStyleName(StyleConstants.DISABLED, !enabled); + } + + @Override + public void focus() { + options.setFocus(true); + } + + /** + * Get the key that selects an item in the table. By default it is the Enter + * key but by overriding this you can change the key to whatever you want. + * + * @return + */ + protected int getNavigationSelectKey() { + return KeyCodes.KEY_ENTER; + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt + * .event.dom.client.KeyDownEvent) + */ + @Override + public void onKeyDown(KeyDownEvent event) { + int keycode = event.getNativeKeyCode(); + + // Catch tab and move between select:s + if (keycode == KeyCodes.KEY_TAB && event.getSource() == options) { + // Prevent default behavior + event.preventDefault(); + + // Remove current selections + for (int i = 0; i < options.getItemCount(); i++) { + options.setItemSelected(i, false); + } + + // Focus selections + selections.setFocus(true); + } + + if (keycode == KeyCodes.KEY_TAB && event.isShiftKeyDown() + && event.getSource() == selections) { + // Prevent default behavior + event.preventDefault(); + + // Remove current selections + for (int i = 0; i < selections.getItemCount(); i++) { + selections.setItemSelected(i, false); + } + + // Focus options + options.setFocus(true); + } + + if (keycode == getNavigationSelectKey()) { + // Prevent default behavior + event.preventDefault(); + + // Decide which select the selection was made in + if (event.getSource() == options) { + // Prevents the selection to become a single selection when + // using Enter key + // as the selection key (default) + options.setFocus(false); + + addItem(); + + } else if (event.getSource() == selections) { + // Prevents the selection to become a single selection when + // using Enter key + // as the selection key (default) + selections.setFocus(false); + + removeItem(); + } + } + + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google + * .gwt.event.dom.client.MouseDownEvent) + */ + @Override + public void onMouseDown(MouseDownEvent event) { + // Ensure that items are deselected when selecting + // from a different source. See #3699 for details. + if (event.getSource() == options) { + for (int i = 0; i < selections.getItemCount(); i++) { + selections.setItemSelected(i, false); + } + } else if (event.getSource() == selections) { + for (int i = 0; i < options.getItemCount(); i++) { + options.setItemSelected(i, false); + } + } + + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.DoubleClickHandler#onDoubleClick(com. + * google.gwt.event.dom.client.DoubleClickEvent) + */ + @Override + public void onDoubleClick(DoubleClickEvent event) { + if (event.getSource() == options) { + addItem(); + options.setSelectedIndex(-1); + options.setFocus(false); + } else if (event.getSource() == selections) { + removeItem(); + selections.setSelectedIndex(-1); + selections.setFocus(false); + } + + } + + private static final String SUBPART_OPTION_SELECT = "leftSelect"; + private static final String SUBPART_OPTION_SELECT_ITEM = SUBPART_OPTION_SELECT + + "-item"; + private static final String SUBPART_SELECTION_SELECT = "rightSelect"; + private static final String SUBPART_SELECTION_SELECT_ITEM = SUBPART_SELECTION_SELECT + + "-item"; + private static final String SUBPART_LEFT_CAPTION = "leftCaption"; + private static final String SUBPART_RIGHT_CAPTION = "rightCaption"; + private static final String SUBPART_ADD_BUTTON = "add"; + private static final String SUBPART_REMOVE_BUTTON = "remove"; + + @Override + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + if (SUBPART_OPTION_SELECT.equals(subPart)) { + return options.getElement(); + } else if (subPart.startsWith(SUBPART_OPTION_SELECT_ITEM)) { + String idx = subPart.substring(SUBPART_OPTION_SELECT_ITEM.length()); + return (com.google.gwt.user.client.Element) options.getElement() + .getChild(Integer.parseInt(idx)); + } else if (SUBPART_SELECTION_SELECT.equals(subPart)) { + return selections.getElement(); + } else if (subPart.startsWith(SUBPART_SELECTION_SELECT_ITEM)) { + String idx = subPart.substring(SUBPART_SELECTION_SELECT_ITEM + .length()); + return (com.google.gwt.user.client.Element) selections.getElement() + .getChild(Integer.parseInt(idx)); + } else if (optionsCaption != null + && SUBPART_LEFT_CAPTION.equals(subPart)) { + return optionsCaption.getElement(); + } else if (selectionsCaption != null + && SUBPART_RIGHT_CAPTION.equals(subPart)) { + return selectionsCaption.getElement(); + } else if (SUBPART_ADD_BUTTON.equals(subPart)) { + return add.getElement(); + } else if (SUBPART_REMOVE_BUTTON.equals(subPart)) { + return remove.getElement(); + } + + return null; + } + + @Override + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + if (optionsCaption != null + && optionsCaption.getElement().isOrHasChild(subElement)) { + return SUBPART_LEFT_CAPTION; + } else if (selectionsCaption != null + && selectionsCaption.getElement().isOrHasChild(subElement)) { + return SUBPART_RIGHT_CAPTION; + } else if (options.getElement().isOrHasChild(subElement)) { + if (options.getElement() == subElement) { + return SUBPART_OPTION_SELECT; + } else { + int idx = WidgetUtil.getChildElementIndex(subElement); + return SUBPART_OPTION_SELECT_ITEM + idx; + } + } else if (selections.getElement().isOrHasChild(subElement)) { + if (selections.getElement() == subElement) { + return SUBPART_SELECTION_SELECT; + } else { + int idx = WidgetUtil.getChildElementIndex(subElement); + return SUBPART_SELECTION_SELECT_ITEM + idx; + } + } else if (add.getElement().isOrHasChild(subElement)) { + return SUBPART_ADD_BUTTON; + } else if (remove.getElement().isOrHasChild(subElement)) { + return SUBPART_REMOVE_BUTTON; + } + + return null; + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VUI.java b/client/src/main/java/com/vaadin/client/ui/VUI.java new file mode 100644 index 0000000000..08641ad6ba --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VUI.java @@ -0,0 +1,508 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.ArrayList; + +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.dom.client.HasScrollHandlers; +import com.google.gwt.event.dom.client.ScrollEvent; +import com.google.gwt.event.dom.client.ScrollHandler; +import com.google.gwt.event.logical.shared.HasResizeHandlers; +import com.google.gwt.event.logical.shared.ResizeEvent; +import com.google.gwt.event.logical.shared.ResizeHandler; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.http.client.URL; +import com.google.gwt.user.client.History; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.SimplePanel; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorMap; +import com.vaadin.client.Focusable; +import com.vaadin.client.LayoutManager; +import com.vaadin.client.Profiler; +import com.vaadin.client.VConsole; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; +import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler; +import com.vaadin.client.ui.ui.UIConnector; +import com.vaadin.shared.ApplicationConstants; +import com.vaadin.shared.ui.ui.UIConstants; + +/** + * + */ +public class VUI extends SimplePanel implements ResizeHandler, + Window.ClosingHandler, ShortcutActionHandlerOwner, Focusable, + com.google.gwt.user.client.ui.Focusable, HasResizeHandlers, + HasScrollHandlers { + + private static int MONITOR_PARENT_TIMER_INTERVAL = 1000; + + /** For internal use only. May be removed or replaced in the future. */ + public String id; + + /** For internal use only. May be removed or replaced in the future. */ + public ShortcutActionHandler actionHandler; + + /* + * Last known window size used to detect whether VView should be layouted + * again. Detection must check window size, because the VView size might be + * fixed and thus not automatically adapt to changed window sizes. + */ + private int windowWidth; + private int windowHeight; + + /* + * Last know view size used to detect whether new dimensions should be sent + * to the server. + */ + private int viewWidth; + private int viewHeight; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection connection; + + /** + * Keep track of possible parent size changes when an embedded application. + * + * Uses {@link #parentWidth} and {@link #parentHeight} as an optimization to + * keep track of when there is a real change. + */ + private Timer resizeTimer; + + /** stored width of parent for embedded application auto-resize */ + private int parentWidth; + + /** stored height of parent for embedded application auto-resize */ + private int parentHeight; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean immediate; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean resizeLazy = false; + + private HandlerRegistration historyHandlerRegistration; + + private TouchScrollHandler touchScrollHandler; + + /** + * The current URI fragment, used to avoid sending updates if nothing has + * changed. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public String currentFragment; + + /** + * Listener for URI fragment changes. Notifies the server of the new value + * whenever the value changes. + */ + private final ValueChangeHandler historyChangeHandler = new ValueChangeHandler() { + + @Override + public void onValueChange(ValueChangeEvent event) { + String newFragment = event.getValue(); + + // Send the location to the server if the fragment has changed + // and flush active connectors in UI. + if (!newFragment.equals(currentFragment) && connection != null) { + /* + * Ensure the fragment is properly encoded in all browsers + * (#10769) + * + * createUrlBuilder does not properly pass an empty fragment to + * UrlBuilder on Webkit browsers so do it manually (#11686) + */ + String location = Window.Location + .createUrlBuilder() + .setHash( + URL.decodeQueryString(Window.Location.getHash())) + .buildString(); + + currentFragment = newFragment; + connection.flushActiveConnector(); + connection.updateVariable(id, UIConstants.LOCATION_VARIABLE, + location, true); + } + } + }; + + private VLazyExecutor delayedResizeExecutor = new VLazyExecutor(200, + new ScheduledCommand() { + + @Override + public void execute() { + performSizeCheck(); + } + + }); + + private Element storedFocus; + + public VUI() { + super(); + // Allow focusing the view by using the focus() method, the view + // should not be in the document focus flow + getElement().setTabIndex(-1); + makeScrollable(); + } + + /** + * Start to periodically monitor for parent element resizes if embedded + * application (e.g. portlet). + */ + @Override + protected void onLoad() { + super.onLoad(); + if (isMonitoringParentSize()) { + resizeTimer = new Timer() { + + @Override + public void run() { + // trigger check to see if parent size has changed, + // recalculate layouts + performSizeCheck(); + resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL); + } + }; + resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL); + } + } + + @Override + protected void onAttach() { + super.onAttach(); + historyHandlerRegistration = History + .addValueChangeHandler(historyChangeHandler); + currentFragment = History.getToken(); + } + + @Override + protected void onDetach() { + super.onDetach(); + historyHandlerRegistration.removeHandler(); + historyHandlerRegistration = null; + } + + /** + * Stop monitoring for parent element resizes. + */ + + @Override + protected void onUnload() { + if (resizeTimer != null) { + resizeTimer.cancel(); + resizeTimer = null; + } + super.onUnload(); + } + + /** + * Called when the window or parent div might have been resized. + * + * This immediately checks the sizes of the window and the parent div (if + * monitoring it) and triggers layout recalculation if they have changed. + */ + protected void performSizeCheck() { + windowSizeMaybeChanged(Window.getClientWidth(), + Window.getClientHeight()); + } + + /** + * Called when the window or parent div might have been resized. + * + * This immediately checks the sizes of the window and the parent div (if + * monitoring it) and triggers layout recalculation if they have changed. + * + * @param newWindowWidth + * The new width of the window + * @param newWindowHeight + * The new height of the window + * + * @deprecated use {@link #performSizeCheck()} + */ + @Deprecated + protected void windowSizeMaybeChanged(int newWindowWidth, + int newWindowHeight) { + if (connection == null) { + // Connection is null if the timer fires before the first UIDL + // update + return; + } + + boolean changed = false; + ComponentConnector connector = ConnectorMap.get(connection) + .getConnector(this); + if (windowWidth != newWindowWidth) { + windowWidth = newWindowWidth; + changed = true; + connector.getLayoutManager().reportOuterWidth(connector, + newWindowWidth); + VConsole.log("New window width: " + windowWidth); + } + if (windowHeight != newWindowHeight) { + windowHeight = newWindowHeight; + changed = true; + connector.getLayoutManager().reportOuterHeight(connector, + newWindowHeight); + VConsole.log("New window height: " + windowHeight); + } + Element parentElement = getElement().getParentElement(); + if (isMonitoringParentSize() && parentElement != null) { + // check also for parent size changes + int newParentWidth = parentElement.getClientWidth(); + int newParentHeight = parentElement.getClientHeight(); + if (parentWidth != newParentWidth) { + parentWidth = newParentWidth; + changed = true; + VConsole.log("New parent width: " + parentWidth); + } + if (parentHeight != newParentHeight) { + parentHeight = newParentHeight; + changed = true; + VConsole.log("New parent height: " + parentHeight); + } + } + if (changed) { + /* + * If the window size has changed, layout the VView again and send + * new size to the server if the size changed. (Just checking VView + * size would cause us to ignore cases when a relatively sized VView + * should shrink as the content's size is fixed and would thus not + * automatically shrink.) + */ + VConsole.log("Running layout functions due to window or parent resize"); + + // update size to avoid (most) redundant re-layout passes + // there can still be an extra layout recalculation if webkit + // overflow fix updates the size in a deferred block + if (isMonitoringParentSize() && parentElement != null) { + parentWidth = parentElement.getClientWidth(); + parentHeight = parentElement.getClientHeight(); + } + + sendClientResized(); + + LayoutManager layoutManager = connector.getLayoutManager(); + if (layoutManager.isLayoutRunning()) { + layoutManager.layoutLater(); + } else { + layoutManager.layoutNow(); + } + } + } + + /** + * @return the name of the theme in use by this UI. + * @deprecated as of 7.3. Use {@link UIConnector#getActiveTheme()} instead. + */ + @Deprecated + public String getTheme() { + return ((UIConnector) ConnectorMap.get(connection).getConnector(this)) + .getActiveTheme(); + } + + /** + * Returns true if the body is NOT generated, i.e if someone else has made + * the page that we're running in. Otherwise we're in charge of the whole + * page. + * + * @return true if we're running embedded + */ + public boolean isEmbedded() { + return !getElement().getOwnerDocument().getBody().getClassName() + .contains(ApplicationConstants.GENERATED_BODY_CLASSNAME); + } + + /** + * Returns true if the size of the parent should be checked periodically and + * the application should react to its changes. + * + * @return true if size of parent should be tracked + */ + protected boolean isMonitoringParentSize() { + // could also perform a more specific check (Liferay portlet) + return isEmbedded(); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google + * .gwt.event.logical.shared.ResizeEvent) + */ + + @Override + public void onResize(ResizeEvent event) { + triggerSizeChangeCheck(); + } + + /** + * Called when a resize event is received. + * + * This may trigger a lazy refresh or perform the size check immediately + * depending on the browser used and whether the server side requests + * resizes to be lazy. + */ + private void triggerSizeChangeCheck() { + /* + * IE (pre IE9 at least) will give us some false resize events due to + * problems with scrollbars. Firefox 3 might also produce some extra + * events. We postpone both the re-layouting and the server side event + * for a while to deal with these issues. + * + * We may also postpone these events to avoid slowness when resizing the + * browser window. Constantly recalculating the layout causes the resize + * operation to be really slow with complex layouts. + */ + boolean lazy = resizeLazy || BrowserInfo.get().isIE8(); + + if (lazy) { + delayedResizeExecutor.trigger(); + } else { + performSizeCheck(); + } + } + + /** + * Send new dimensions to the server. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public void sendClientResized() { + Profiler.enter("VUI.sendClientResized"); + Element parentElement = getElement().getParentElement(); + int viewHeight = parentElement.getClientHeight(); + int viewWidth = parentElement.getClientWidth(); + + ResizeEvent.fire(this, viewWidth, viewHeight); + Profiler.leave("VUI.sendClientResized"); + } + + public native static void goTo(String url) + /*-{ + $wnd.location = url; + }-*/; + + @Override + public void onWindowClosing(Window.ClosingEvent event) { + // Change focus on this window in order to ensure that all state is + // collected from textfields + // TODO this is a naive hack, that only works with text fields and may + // cause some odd issues. Should be replaced with a decent solution, see + // also related BeforeShortcutActionListener interface. Same interface + // might be usable here. + VTextField.flushChangesFromFocusedTextField(); + } + + private native static void loadAppIdListFromDOM(ArrayList list) + /*-{ + var j; + for(j in $wnd.vaadin.vaadinConfigurations) { + // $entry not needed as function is not exported + list.@java.util.Collection::add(Ljava/lang/Object;)(j); + } + }-*/; + + @Override + public ShortcutActionHandler getShortcutActionHandler() { + return actionHandler; + } + + @Override + public void focus() { + setFocus(true); + } + + /** + * Ensures the widget is scrollable eg. after style name changes. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public void makeScrollable() { + if (touchScrollHandler == null) { + touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); + } + touchScrollHandler.addElement(getElement()); + } + + @Override + public HandlerRegistration addResizeHandler(ResizeHandler resizeHandler) { + return addHandler(resizeHandler, ResizeEvent.getType()); + } + + @Override + public HandlerRegistration addScrollHandler(ScrollHandler scrollHandler) { + return addHandler(scrollHandler, ScrollEvent.getType()); + } + + @Override + public int getTabIndex() { + return FocusUtil.getTabIndex(this); + } + + @Override + public void setAccessKey(char key) { + FocusUtil.setAccessKey(this, key); + } + + @Override + public void setFocus(boolean focused) { + FocusUtil.setFocus(this, focused); + } + + @Override + public void setTabIndex(int index) { + FocusUtil.setTabIndex(this, index); + } + + /** + * Allows to store the currently focused Element. + * + * Current use case is to store the focus when a Window is opened. Does + * currently handle only a single value. Needs to be extended for #12158 + * + * @param focusedElement + */ + public void storeFocus() { + storedFocus = WidgetUtil.getFocusedElement(); + } + + /** + * Restores the previously stored focus Element. + * + * Current use case is to restore the focus when a Window is closed. Does + * currently handle only a single value. Needs to be extended for #12158 + * + * @return the lastFocusElementBeforeDialogOpened + */ + public void focusStoredElement() { + if (storedFocus != null) { + storedFocus.focus(); + } + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VUnknownComponent.java b/client/src/main/java/com/vaadin/client/ui/VUnknownComponent.java new file mode 100644 index 0000000000..89907854de --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VUnknownComponent.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.vaadin.client.SimpleTree; + +public class VUnknownComponent extends Composite { + + com.google.gwt.user.client.ui.Label caption = new com.google.gwt.user.client.ui.Label(); + SimpleTree uidlTree; + protected VerticalPanel panel; + + public VUnknownComponent() { + panel = new VerticalPanel(); + panel.add(caption); + initWidget(panel); + setStyleName("vaadin-unknown"); + caption.setStyleName("vaadin-unknown-caption"); + } + + public void setCaption(String c) { + caption.getElement().setInnerHTML(c); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VUpload.java b/client/src/main/java/com/vaadin/client/ui/VUpload.java new file mode 100644 index 0000000000..2800acccf9 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VUpload.java @@ -0,0 +1,393 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.FormElement; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.FileUpload; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FormPanel; +import com.google.gwt.user.client.ui.Hidden; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ConnectorMap; +import com.vaadin.client.StyleConstants; +import com.vaadin.client.VConsole; +import com.vaadin.client.ui.upload.UploadConnector; +import com.vaadin.client.ui.upload.UploadIFrameOnloadStrategy; +import com.vaadin.shared.ui.upload.UploadServerRpc; + +/** + * + * Note, we are not using GWT FormPanel as we want to listen submitcomplete + * events even though the upload component is already detached. + * + */ +public class VUpload extends SimplePanel { + + private final class MyFileUpload extends FileUpload { + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (event.getTypeInt() == Event.ONCHANGE) { + if (immediate && fu.getFilename() != null + && !"".equals(fu.getFilename())) { + submit(); + } + } else if (BrowserInfo.get().isIE() + && event.getTypeInt() == Event.ONFOCUS) { + // IE and user has clicked on hidden textarea part of upload + // field. Manually open file selector, other browsers do it by + // default. + fireNativeClick(fu.getElement()); + // also remove focus to enable hack if user presses cancel + // button + fireNativeBlur(fu.getElement()); + } + } + } + + public static final String CLASSNAME = "v-upload"; + + /** + * FileUpload component that opens native OS dialog to select file. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public FileUpload fu = new MyFileUpload(); + + Panel panel = new FlowPanel(); + + UploadIFrameOnloadStrategy onloadstrategy = GWT + .create(UploadIFrameOnloadStrategy.class); + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public String paintableId; + + /** + * Button that initiates uploading. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public final VButton submitButton; + + /** + * When expecting big files, programmer may initiate some UI changes when + * uploading the file starts. Bit after submitting file we'll visit the + * server to check possible changes. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public Timer t; + + /** + * some browsers tries to send form twice if submit is called in button + * click handler, some don't submit at all without it, so we need to track + * if form is already being submitted + */ + private boolean submitted = false; + + private boolean enabled = true; + + private boolean immediate; + + private Hidden maxfilesize = new Hidden(); + + /** For internal use only. May be removed or replaced in the future. */ + public FormElement element; + + private com.google.gwt.dom.client.Element synthesizedFrame; + + /** For internal use only. May be removed or replaced in the future. */ + public int nextUploadId; + + public VUpload() { + super(com.google.gwt.dom.client.Document.get().createFormElement()); + + element = getElement().cast(); + setEncoding(getElement(), FormPanel.ENCODING_MULTIPART); + element.setMethod(FormPanel.METHOD_POST); + + setWidget(panel); + panel.add(maxfilesize); + panel.add(fu); + submitButton = new VButton(); + submitButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + if (immediate) { + // fire click on upload (eg. focused button and hit space) + fireNativeClick(fu.getElement()); + } else { + submit(); + } + } + }); + panel.add(submitButton); + + setStyleName(CLASSNAME); + } + + private static native void setEncoding(Element form, String encoding) + /*-{ + form.enctype = encoding; + // For IE8 + form.encoding = encoding; + }-*/; + + /** For internal use only. May be removed or replaced in the future. */ + public void setImmediate(boolean booleanAttribute) { + if (immediate != booleanAttribute) { + immediate = booleanAttribute; + if (immediate) { + fu.sinkEvents(Event.ONCHANGE); + fu.sinkEvents(Event.ONFOCUS); + } + } + setStyleName(getElement(), CLASSNAME + "-immediate", immediate); + } + + private static native void fireNativeClick(Element element) + /*-{ + element.click(); + }-*/; + + private static native void fireNativeBlur(Element element) + /*-{ + element.blur(); + }-*/; + + /** For internal use only. May be removed or replaced in the future. */ + public void disableUpload() { + setEnabledForSubmitButton(false); + if (!submitted) { + // Cannot disable the fileupload while submitting or the file won't + // be submitted at all + fu.getElement().setPropertyBoolean("disabled", true); + } + enabled = false; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void enableUpload() { + setEnabledForSubmitButton(true); + fu.getElement().setPropertyBoolean("disabled", false); + enabled = true; + if (submitted) { + /* + * An old request is still in progress (most likely cancelled), + * ditching that target frame to make it possible to send a new + * file. A new target frame is created later." + */ + cleanTargetFrame(); + submitted = false; + } + } + + private void setEnabledForSubmitButton(boolean enabled) { + submitButton.setEnabled(enabled); + submitButton.setStyleName(StyleConstants.DISABLED, !enabled); + } + + /** + * Re-creates file input field and populates panel. This is needed as we + * want to clear existing values from our current file input field. + */ + private void rebuildPanel() { + panel.remove(submitButton); + panel.remove(fu); + fu = new MyFileUpload(); + fu.setName(paintableId + "_file"); + fu.getElement().setPropertyBoolean("disabled", !enabled); + panel.add(fu); + panel.add(submitButton); + if (immediate) { + fu.sinkEvents(Event.ONCHANGE); + } + } + + /** + * Called by JSNI (hooked via {@link #onloadstrategy}) + */ + private void onSubmitComplete() { + /* Needs to be run dereferred to avoid various browser issues. */ + Scheduler.get().scheduleDeferred(new Command() { + @Override + public void execute() { + if (submitted) { + if (client != null) { + if (t != null) { + t.cancel(); + } + VConsole.log("VUpload:Submit complete"); + ((UploadConnector) ConnectorMap.get(client) + .getConnector(VUpload.this)).getRpcProxy( + UploadServerRpc.class).poll(); + } + + rebuildPanel(); + + submitted = false; + enableUpload(); + if (!isAttached()) { + /* + * Upload is complete when upload is already abandoned. + */ + cleanTargetFrame(); + } + } + } + }); + } + + ScheduledCommand startUploadCmd = new ScheduledCommand() { + + @Override + public void execute() { + element.submit(); + submitted = true; + + disableUpload(); + + /* + * Visit server a moment after upload has started to see possible + * changes from UploadStarted event. Will be cleared on complete. + * + * Must get the id here as the upload can finish before the timer + * expires and in that case nextUploadId has been updated and is + * wrong. + */ + final int thisUploadId = nextUploadId; + t = new Timer() { + @Override + public void run() { + // Only visit the server if the upload has not already + // finished + if (thisUploadId == nextUploadId) { + VConsole.log("Visiting server to see if upload started event changed UI."); + client.updateVariable(paintableId, "pollForStart", + thisUploadId, true); + } + } + }; + t.schedule(800); + } + + }; + + /** For internal use only. May be removed or replaced in the future. */ + public void submit() { + if (submitted || !enabled) { + VConsole.log("Submit cancelled (disabled or already submitted)"); + return; + } + if (fu.getFilename().length() == 0) { + VConsole.log("Submitting empty selection (no file)"); + } + // flush possibly pending variable changes, so they will be handled + // before upload + client.sendPendingVariableChanges(); + + // This is done as deferred because sendPendingVariableChanges is also + // deferred and we want to start the upload only after the changes have + // been sent to the server + Scheduler.get().scheduleDeferred(startUploadCmd); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void disableTitle(boolean disable) { + if (disable) { + // Disable title attribute for upload element. + if (BrowserInfo.get().isChrome()) { + // In Chrome title has to be set to " " to make it invisible + fu.setTitle(" "); + } else if (BrowserInfo.get().isFirefox()) { + // In FF title has to be set to empty string to make it + // invisible + // Method setTitle removes title attribute when it's an empty + // string, so setAttribute() should be used here + fu.getElement().setAttribute("title", ""); + } + // For other browsers absent title doesn't show default tooltip for + // input element + } else { + fu.setTitle(null); + } + } + + @Override + protected void onAttach() { + super.onAttach(); + if (client != null) { + ensureTargetFrame(); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void ensureTargetFrame() { + if (synthesizedFrame == null) { + // Attach a hidden IFrame to the form. This is the target iframe to + // which the form will be submitted. We have to create the iframe + // using innerHTML, because setting an iframe's 'name' property + // dynamically doesn't work on most browsers. + DivElement dummy = Document.get().createDivElement(); + dummy.setInnerHTML(""); + getWidget().browserElement = DOM.getFirstChild(getWidget() + .getElement()); + } + resourceElement = getWidget().browserElement; + objectElement = null; + setResourceUrl(getResourceUrl("src")); + clearBrowserElement = false; + } else { + VConsole.error("Unknown Embedded type '" + getWidget().type + + "'"); + } + } else if (uidl.hasAttribute("mimetype")) { + // remove old style name related to type + if (getWidget().type != null) { + getWidget().removeStyleName( + VEmbedded.CLASSNAME + "-" + getWidget().type); + } + // remove old style name related to mime type + if (getWidget().mimetype != null) { + getWidget().removeStyleName( + VEmbedded.CLASSNAME + "-" + getWidget().mimetype); + } + final String mime = uidl.getStringAttribute("mimetype"); + if (mime.equals("application/x-shockwave-flash")) { + getWidget().mimetype = "flash"; + // Handle embedding of Flash + getWidget().addStyleName(VEmbedded.CLASSNAME + "-flash"); + getWidget().setHTML(getWidget().createFlashEmbed(uidl)); + + } else if (mime.equals("image/svg+xml")) { + getWidget().mimetype = "svg"; + getWidget().addStyleName(VEmbedded.CLASSNAME + "-svg"); + String data; + Map parameters = VEmbedded.getParameters(uidl); + ObjectElement obj = Document.get().createObjectElement(); + resourceElement = null; + if (parameters.get("data") == null) { + objectElement = obj; + data = getResourceUrl("src"); + setResourceUrl(data); + } else { + objectElement = null; + data = "data:image/svg+xml," + parameters.get("data"); + obj.setData(data); + } + getWidget().setHTML(""); + obj.setType(mime); + if (!isUndefinedWidth()) { + obj.getStyle().setProperty("width", "100%"); + } + if (!isUndefinedHeight()) { + obj.getStyle().setProperty("height", "100%"); + } + if (uidl.hasAttribute("classid")) { + obj.setAttribute("classid", + uidl.getStringAttribute("classid")); + } + if (uidl.hasAttribute("codebase")) { + obj.setAttribute("codebase", + uidl.getStringAttribute("codebase")); + } + if (uidl.hasAttribute("codetype")) { + obj.setAttribute("codetype", + uidl.getStringAttribute("codetype")); + } + if (uidl.hasAttribute("archive")) { + obj.setAttribute("archive", + uidl.getStringAttribute("archive")); + } + if (uidl.hasAttribute("standby")) { + obj.setAttribute("standby", + uidl.getStringAttribute("standby")); + } + getWidget().getElement().appendChild(obj); + if (uidl.hasAttribute(EmbeddedConstants.ALTERNATE_TEXT)) { + obj.setInnerText(uidl + .getStringAttribute(EmbeddedConstants.ALTERNATE_TEXT)); + } + } else { + VConsole.error("Unknown Embedded mimetype '" + mime + "'"); + } + } else { + VConsole.error("Unknown Embedded; no type or mimetype attribute"); + } + + if (clearBrowserElement) { + getWidget().browserElement = null; + } + } + + @Override + public VEmbedded getWidget() { + return (VEmbedded) super.getWidget(); + } + + @Override + public EmbeddedState getState() { + return (EmbeddedState) super.getState(); + } + + protected final ClickEventHandler clickEventHandler = new ClickEventHandler( + this) { + + @Override + protected void fireClick(NativeEvent event, + MouseEventDetails mouseDetails) { + getRpcProxy(EmbeddedServerRpc.class).click(mouseDetails); + } + + }; + +} diff --git a/client/src/main/java/com/vaadin/client/ui/flash/FlashConnector.java b/client/src/main/java/com/vaadin/client/ui/flash/FlashConnector.java new file mode 100644 index 0000000000..e859e9cbf1 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/flash/FlashConnector.java @@ -0,0 +1,55 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.flash; + +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.VFlash; +import com.vaadin.shared.ui.AbstractEmbeddedState; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.flash.FlashState; + +@Connect(com.vaadin.ui.Flash.class) +public class FlashConnector extends AbstractComponentConnector { + + @Override + public VFlash getWidget() { + return (VFlash) super.getWidget(); + } + + @Override + public FlashState getState() { + return (FlashState) super.getState(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + + super.onStateChanged(stateChangeEvent); + + getWidget().setSource( + getResourceUrl(AbstractEmbeddedState.SOURCE_RESOURCE)); + getWidget().setArchive(getState().archive); + getWidget().setClassId(getState().classId); + getWidget().setCodebase(getState().codebase); + getWidget().setCodetype(getState().codetype); + getWidget().setStandby(getState().standby); + getWidget().setAlternateText(getState().alternateText); + getWidget().setEmbedParams(getState().embedParams); + + getWidget().rebuildIfNeeded(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/form/FormConnector.java b/client/src/main/java/com/vaadin/client/ui/form/FormConnector.java new file mode 100644 index 0000000000..857c2bd40e --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/form/FormConnector.java @@ -0,0 +1,236 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.form; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorHierarchyChangeEvent; +import com.vaadin.client.LayoutManager; +import com.vaadin.client.Paintable; +import com.vaadin.client.TooltipInfo; +import com.vaadin.client.UIDL; +import com.vaadin.client.VCaption; +import com.vaadin.client.ui.AbstractComponentContainerConnector; +import com.vaadin.client.ui.ShortcutActionHandler; +import com.vaadin.client.ui.VForm; +import com.vaadin.client.ui.layout.ElementResizeEvent; +import com.vaadin.client.ui.layout.ElementResizeListener; +import com.vaadin.client.ui.layout.MayScrollChildren; +import com.vaadin.shared.ui.ComponentStateUtil; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.form.FormState; +import com.vaadin.ui.Form; + +@Connect(Form.class) +public class FormConnector extends AbstractComponentContainerConnector + implements Paintable, MayScrollChildren { + + private final ElementResizeListener footerResizeListener = new ElementResizeListener() { + @Override + public void onElementResize(ElementResizeEvent e) { + VForm form = getWidget(); + + LayoutManager lm = getLayoutManager(); + int footerHeight = 0; + if (form.footer != null) { + footerHeight += lm.getOuterHeight(form.footer.getElement()); + } + + if (form.errorMessage.isVisible()) { + footerHeight += lm.getOuterHeight(form.errorMessage + .getElement()); + footerHeight -= lm.getMarginTop(form.errorMessage.getElement()); + form.errorMessage.getElement().getStyle() + .setMarginTop(-footerHeight, Unit.PX); + form.footerContainer.getStyle().clearMarginTop(); + } else { + form.footerContainer.getStyle().setMarginTop(-footerHeight, + Unit.PX); + } + + form.fieldContainer.getStyle().setPaddingBottom(footerHeight, + Unit.PX); + } + }; + + @Override + protected void init() { + getLayoutManager().addElementResizeListener( + getWidget().errorMessage.getElement(), footerResizeListener); + } + + @Override + public void onUnregister() { + VForm form = getWidget(); + getLayoutManager().removeElementResizeListener( + form.errorMessage.getElement(), footerResizeListener); + if (form.footer != null) { + getLayoutManager().removeElementResizeListener( + form.footer.getElement(), footerResizeListener); + } + } + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().client = client; + getWidget().id = uidl.getId(); + + if (!isRealUpdate(uidl)) { + return; + } + + boolean legendEmpty = true; + if (getState().caption != null) { + VCaption.setCaptionText(getWidget().caption, getState()); + legendEmpty = false; + } else { + getWidget().caption.setInnerText(""); + } + if (getWidget().icon != null) { + getWidget().legend.removeChild(getWidget().icon.getElement()); + } + if (getIconUri() != null) { + getWidget().icon = client.getIcon(getIconUri()); + getWidget().legend.insertFirst(getWidget().icon.getElement()); + + legendEmpty = false; + } + if (legendEmpty) { + getWidget().addStyleDependentName("nocaption"); + } else { + getWidget().removeStyleDependentName("nocaption"); + } + + if (null != getState().errorMessage) { + getWidget().errorMessage.updateMessage(getState().errorMessage); + getWidget().errorMessage.setVisible(true); + } else { + getWidget().errorMessage.setVisible(false); + } + + if (ComponentStateUtil.hasDescription(getState())) { + getWidget().desc.setInnerHTML(getState().description); + if (getWidget().desc.getParentElement() == null) { + getWidget().fieldSet.insertAfter(getWidget().desc, + getWidget().legend); + } + } else { + getWidget().desc.setInnerHTML(""); + if (getWidget().desc.getParentElement() != null) { + getWidget().fieldSet.removeChild(getWidget().desc); + } + } + + // also recalculates size of the footer if undefined size form - see + // #3710 + client.runDescendentsLayout(getWidget()); + + // We may have actions attached + if (uidl.getChildCount() >= 1) { + UIDL childUidl = uidl.getChildByTagName("actions"); + if (childUidl != null) { + if (getWidget().shortcutHandler == null) { + getWidget().shortcutHandler = new ShortcutActionHandler( + getConnectorId(), client); + getWidget().keyDownRegistration = getWidget() + .addDomHandler(getWidget(), KeyDownEvent.getType()); + } + getWidget().shortcutHandler.updateActionMap(childUidl); + } + } else if (getWidget().shortcutHandler != null) { + getWidget().keyDownRegistration.removeHandler(); + getWidget().shortcutHandler = null; + getWidget().keyDownRegistration = null; + } + } + + @Override + public void updateCaption(ComponentConnector component) { + // NOP form don't render caption for neither field layout nor footer + // layout + } + + @Override + public VForm getWidget() { + return (VForm) super.getWidget(); + } + + @Override + public boolean isReadOnly() { + return super.isReadOnly() || getState().propertyReadOnly; + } + + @Override + public FormState getState() { + return (FormState) super.getState(); + } + + private ComponentConnector getFooter() { + return (ComponentConnector) getState().footer; + } + + private ComponentConnector getLayout() { + return (ComponentConnector) getState().layout; + } + + @Override + public void onConnectorHierarchyChange( + ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) { + Widget newFooterWidget = null; + ComponentConnector footer = getFooter(); + + if (footer != null) { + newFooterWidget = footer.getWidget(); + Widget currentFooter = getWidget().footer; + if (currentFooter != null) { + // Remove old listener + getLayoutManager().removeElementResizeListener( + currentFooter.getElement(), footerResizeListener); + } + getLayoutManager().addElementResizeListener( + newFooterWidget.getElement(), footerResizeListener); + } + getWidget().setFooterWidget(newFooterWidget); + + Widget newLayoutWidget = null; + ComponentConnector newLayout = getLayout(); + if (newLayout != null) { + newLayoutWidget = newLayout.getWidget(); + } + getWidget().setLayoutWidget(newLayoutWidget); + } + + @Override + public TooltipInfo getTooltipInfo(Element element) { + // Form shows its description and error message + // as a part of the actual layout + return null; + } + + @Override + public boolean hasTooltip() { + return false; + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/formlayout/FormLayoutConnector.java b/client/src/main/java/com/vaadin/client/ui/formlayout/FormLayoutConnector.java new file mode 100644 index 0000000000..3f0b4345c4 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/formlayout/FormLayoutConnector.java @@ -0,0 +1,294 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.formlayout; + +import java.util.HashMap; +import java.util.Map; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorHierarchyChangeEvent; +import com.vaadin.client.LayoutManager; +import com.vaadin.client.TooltipInfo; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractFieldConnector; +import com.vaadin.client.ui.AbstractLayoutConnector; +import com.vaadin.client.ui.PostLayoutListener; +import com.vaadin.client.ui.VFormLayout; +import com.vaadin.client.ui.VFormLayout.Caption; +import com.vaadin.client.ui.VFormLayout.ErrorFlag; +import com.vaadin.client.ui.VFormLayout.VFormLayoutTable; +import com.vaadin.client.ui.layout.ElementResizeEvent; +import com.vaadin.client.ui.layout.ElementResizeListener; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.MarginInfo; +import com.vaadin.shared.ui.orderedlayout.FormLayoutState; +import com.vaadin.ui.FormLayout; + +@Connect(FormLayout.class) +public class FormLayoutConnector extends AbstractLayoutConnector implements + PostLayoutListener { + + private Map oldMaxWidths = null; + + private static final ElementResizeListener dummyFirstCellResizeListener = new ElementResizeListener() { + @Override + public void onElementResize(ElementResizeEvent e) { + // Ignore event, listener added just to make measurements available + } + }; + + // Detects situations when there's something inside the FormLayout that + // prevents it from shrinking + private ElementResizeListener resizeListener = new ElementResizeListener() { + @Override + public void onElementResize(ElementResizeEvent e) { + LayoutManager layoutManager = getLayoutManager(); + double tableWidth = layoutManager + .getOuterWidthDouble(getWidget().table.getElement()); + double ownWidth = layoutManager.getInnerWidthDouble(getWidget() + .getElement()); + if (ownWidth < tableWidth) { + // Something inside the table prevents it from shrinking, + // temporarily force column widths + double excessWidth = tableWidth - ownWidth; + + // All td elements in the component column have the same width, + // so we only need to check the width of the first one to know + // how wide the column is. + Element firstComponentTd = findFirstComponentTd(); + if (firstComponentTd == null) { + // Can't do anything if there are no rows + return; + } + + double componentColWidth = layoutManager + .getOuterWidthDouble(firstComponentTd); + + if (componentColWidth == -1) { + // Didn't get a proper width reading, best to not touch + // anything + return; + } + + // Restrict content td width + // Round down to prevent interactions with fractional sizes of + // other columns + int targetWidth = (int) Math.floor(componentColWidth + - excessWidth); + + // Target might be negative if captions are wider than the total + // available width + targetWidth = Math.max(0, targetWidth); + + if (oldMaxWidths == null) { + oldMaxWidths = new HashMap(); + } + + for (ComponentConnector child : getChildComponents()) { + Element childElement = child.getWidget().getElement(); + if (!oldMaxWidths.containsKey(child)) { + oldMaxWidths.put(child, + childElement.getPropertyString("maxWidth")); + } + childElement.getStyle().setPropertyPx("maxWidth", + targetWidth); + layoutManager.reportOuterWidth(child, targetWidth); + } + } + } + }; + + @Override + protected void init() { + super.init(); + getLayoutManager().addElementResizeListener( + getWidget().table.getElement(), resizeListener); + getLayoutManager().addElementResizeListener(getWidget().getElement(), + resizeListener); + addComponentCellListener(); + } + + @Override + public void onUnregister() { + getLayoutManager().removeElementResizeListener( + getWidget().table.getElement(), resizeListener); + getLayoutManager().removeElementResizeListener( + getWidget().getElement(), resizeListener); + removeComponentCellListener(); + super.onUnregister(); + } + + @Override + public FormLayoutState getState() { + return (FormLayoutState) super.getState(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + VFormLayoutTable formLayoutTable = getWidget().table; + + formLayoutTable.setMargins(new MarginInfo(getState().marginsBitmask)); + formLayoutTable.setSpacing(getState().spacing); + + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + VFormLayout formLayout = getWidget(); + VFormLayoutTable formLayoutTable = getWidget().table; + + removeComponentCellListener(); + + int childId = 0; + + formLayoutTable.setRowCount(getChildComponents().size()); + + for (ComponentConnector child : getChildComponents()) { + Widget childWidget = child.getWidget(); + + Caption caption = formLayoutTable.getCaption(childWidget); + if (caption == null) { + caption = formLayout.new Caption(child); + caption.addClickHandler(formLayoutTable); + } + + ErrorFlag error = formLayoutTable.getError(childWidget); + if (error == null) { + error = formLayout.new ErrorFlag(child); + } + + formLayoutTable.setChild(childId, childWidget, caption, error); + childId++; + } + + for (ComponentConnector oldChild : event.getOldChildren()) { + if (oldChild.getParent() == this) { + continue; + } + + formLayoutTable.cleanReferences(oldChild.getWidget()); + } + + addComponentCellListener(); + } + + private void addComponentCellListener() { + Element td = findFirstComponentTd(); + if (td != null) { + getLayoutManager().addElementResizeListener(td, + dummyFirstCellResizeListener); + } + } + + private void removeComponentCellListener() { + Element td = findFirstComponentTd(); + if (td != null) { + getLayoutManager().removeElementResizeListener(td, + dummyFirstCellResizeListener); + } + } + + private Element findFirstComponentTd() { + VFormLayoutTable table = getWidget().table; + if (table.getRowCount() == 0) { + return null; + } else { + return table.getCellFormatter().getElement(0, + VFormLayoutTable.COLUMN_WIDGET); + } + } + + @Override + public void updateCaption(ComponentConnector component) { + getWidget().table.updateCaption(component.getWidget(), + component.getState(), component.isEnabled()); + boolean hideErrors = false; + + // FIXME This incorrectly depends on AbstractFieldConnector + if (component instanceof AbstractFieldConnector) { + hideErrors = ((AbstractFieldConnector) component).getState().hideErrors; + } + + getWidget().table.updateError(component.getWidget(), + component.getState().errorMessage, hideErrors); + } + + @Override + public VFormLayout getWidget() { + return (VFormLayout) super.getWidget(); + } + + @Override + public TooltipInfo getTooltipInfo(Element element) { + TooltipInfo info = null; + + if (element != getWidget().getElement()) { + Object node = WidgetUtil.findWidget(element, + VFormLayout.Caption.class); + + if (node != null) { + VFormLayout.Caption caption = (VFormLayout.Caption) node; + info = caption.getOwner().getTooltipInfo(element); + } else { + + node = WidgetUtil.findWidget(element, + VFormLayout.ErrorFlag.class); + + if (node != null) { + VFormLayout.ErrorFlag flag = (VFormLayout.ErrorFlag) node; + info = flag.getOwner().getTooltipInfo(element); + } + } + } + + if (info == null) { + info = super.getTooltipInfo(element); + } + + return info; + } + + @Override + public boolean hasTooltip() { + /* + * Tooltips are fetched from child connectors -> there's no quick way of + * checking whether there might a tooltip hiding somewhere + */ + return true; + } + + @Override + public void postLayout() { + if (oldMaxWidths != null) { + for (ComponentConnector child : getChildComponents()) { + Element childNode = child.getWidget().getElement(); + String oldValue = oldMaxWidths.get(child); + if (oldValue == null) { + childNode.getStyle().clearProperty("maxWidth"); + } else { + childNode.getStyle().setProperty("maxWidth", oldValue); + } + } + oldMaxWidths = null; + } + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/gridlayout/GridLayoutConnector.java b/client/src/main/java/com/vaadin/client/ui/gridlayout/GridLayoutConnector.java new file mode 100644 index 0000000000..4d1ce692ad --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/gridlayout/GridLayoutConnector.java @@ -0,0 +1,227 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.gridlayout; + +import java.util.Map.Entry; + +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorHierarchyChangeEvent; +import com.vaadin.client.DirectionalManagedLayout; +import com.vaadin.client.Paintable; +import com.vaadin.client.UIDL; +import com.vaadin.client.VCaption; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractComponentContainerConnector; +import com.vaadin.client.ui.LayoutClickEventHandler; +import com.vaadin.client.ui.VGridLayout; +import com.vaadin.client.ui.VGridLayout.Cell; +import com.vaadin.client.ui.layout.VLayoutSlot; +import com.vaadin.shared.Connector; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.LayoutClickRpc; +import com.vaadin.shared.ui.MarginInfo; +import com.vaadin.shared.ui.gridlayout.GridLayoutServerRpc; +import com.vaadin.shared.ui.gridlayout.GridLayoutState; +import com.vaadin.shared.ui.gridlayout.GridLayoutState.ChildComponentData; +import com.vaadin.ui.GridLayout; + +@Connect(GridLayout.class) +public class GridLayoutConnector extends AbstractComponentContainerConnector + implements Paintable, DirectionalManagedLayout { + + private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( + this) { + + @Override + protected ComponentConnector getChildComponent( + com.google.gwt.user.client.Element element) { + return getWidget().getComponent(element); + } + + @Override + protected LayoutClickRpc getLayoutClickRPC() { + return getRpcProxy(GridLayoutServerRpc.class); + } + + }; + + @Override + public void init() { + super.init(); + getWidget().client = getConnection(); + + getLayoutManager().registerDependency(this, + getWidget().spacingMeasureElement); + } + + @Override + public void onUnregister() { + VGridLayout layout = getWidget(); + getLayoutManager().unregisterDependency(this, + layout.spacingMeasureElement); + + // Unregister caption size dependencies + for (ComponentConnector child : getChildComponents()) { + Cell cell = layout.widgetToCell.get(child.getWidget()); + cell.slot.setCaption(null); + } + } + + @Override + public GridLayoutState getState() { + return (GridLayoutState) super.getState(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + clickEventHandler.handleEventHandlerRegistration(); + + getWidget().hideEmptyRowsAndColumns = getState().hideEmptyRowsAndColumns; + + } + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + VGridLayout layout = getWidget(); + + if (!isRealUpdate(uidl)) { + return; + } + + initSize(); + + for (Entry entry : getState().childData + .entrySet()) { + ComponentConnector child = (ComponentConnector) entry.getKey(); + + Cell cell = getCell(child); + + ChildComponentData childComponentData = entry.getValue(); + cell.updateCell(childComponentData); + } + + layout.colExpandRatioArray = uidl.getIntArrayAttribute("colExpand"); + layout.rowExpandRatioArray = uidl.getIntArrayAttribute("rowExpand"); + + layout.updateMarginStyleNames(new MarginInfo(getState().marginsBitmask)); + layout.updateSpacingStyleName(getState().spacing); + getLayoutManager().setNeedsLayout(this); + } + + private Cell getCell(ComponentConnector child) { + VGridLayout layout = getWidget(); + Cell cell = layout.widgetToCell.get(child.getWidget()); + + if (cell == null) { + ChildComponentData childComponentData = getState().childData + .get(child); + int row = childComponentData.row1; + int col = childComponentData.column1; + + cell = layout.createNewCell(row, col); + } + return cell; + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + VGridLayout layout = getWidget(); + + // clean non rendered components + for (ComponentConnector oldChild : event.getOldChildren()) { + if (oldChild.getParent() == this) { + continue; + } + + Widget childWidget = oldChild.getWidget(); + layout.remove(childWidget); + } + + initSize(); + + for (ComponentConnector componentConnector : getChildComponents()) { + Cell cell = getCell(componentConnector); + + cell.setComponent(componentConnector, getChildComponents()); + } + + } + + private void initSize() { + VGridLayout layout = getWidget(); + int cols = getState().columns; + int rows = getState().rows; + + layout.columnWidths = new int[cols]; + layout.rowHeights = new int[rows]; + layout.explicitRowRatios = getState().explicitRowRatios; + layout.explicitColRatios = getState().explicitColRatios; + layout.setSize(rows, cols); + } + + @Override + public void updateCaption(ComponentConnector childConnector) { + VGridLayout layout = getWidget(); + Cell cell = layout.widgetToCell.get(childConnector.getWidget()); + if (VCaption.isNeeded(childConnector.getState())) { + VLayoutSlot layoutSlot = cell.slot; + VCaption caption = layoutSlot.getCaption(); + if (caption == null) { + caption = new VCaption(childConnector, getConnection()); + Widget widget = childConnector.getWidget(); + + layout.setCaption(widget, caption); + } + caption.updateCaption(); + } else { + layout.setCaption(childConnector.getWidget(), null); + getLayoutManager().setNeedsLayout(this); + } + } + + @Override + public VGridLayout getWidget() { + return (VGridLayout) super.getWidget(); + } + + @Override + public void layoutVertically() { + getWidget().updateHeight(); + } + + @Override + public void layoutHorizontally() { + getWidget().updateWidth(); + } + + @Override + protected void updateWidgetSize(String newWidth, String newHeight) { + // Prevent the element from momentarily shrinking to zero size + // when the size is set to undefined by a state change but before + // it is recomputed in the layout phase. This may affect scroll + // position in some cases; see #13386. + if (!isUndefinedHeight()) { + getWidget().setHeight(newHeight); + } + if (!isUndefinedWidth()) { + getWidget().setWidth(newWidth); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/image/ImageConnector.java b/client/src/main/java/com/vaadin/client/ui/image/ImageConnector.java new file mode 100644 index 0000000000..e4ba4af070 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/image/ImageConnector.java @@ -0,0 +1,82 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.image; + +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.LoadEvent; +import com.google.gwt.event.dom.client.LoadHandler; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.ClickEventHandler; +import com.vaadin.client.ui.VImage; +import com.vaadin.shared.MouseEventDetails; +import com.vaadin.shared.ui.AbstractEmbeddedState; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.image.ImageServerRpc; +import com.vaadin.shared.ui.image.ImageState; + +@Connect(com.vaadin.ui.Image.class) +public class ImageConnector extends AbstractComponentConnector { + + @Override + protected void init() { + super.init(); + getWidget().addHandler(new LoadHandler() { + + @Override + public void onLoad(LoadEvent event) { + getLayoutManager().setNeedsMeasure(ImageConnector.this); + } + + }, LoadEvent.getType()); + } + + @Override + public VImage getWidget() { + return (VImage) super.getWidget(); + } + + @Override + public ImageState getState() { + return (ImageState) super.getState(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + clickEventHandler.handleEventHandlerRegistration(); + + String url = getResourceUrl(AbstractEmbeddedState.SOURCE_RESOURCE); + getWidget().setUrl(url != null ? url : ""); + + String alt = getState().alternateText; + // Some browsers turn a null alt text into a literal "null" + getWidget().setAltText(alt != null ? alt : ""); + } + + protected final ClickEventHandler clickEventHandler = new ClickEventHandler( + this) { + + @Override + protected void fireClick(NativeEvent event, + MouseEventDetails mouseDetails) { + getRpcProxy(ImageServerRpc.class).click(mouseDetails); + } + + }; + +} diff --git a/client/src/main/java/com/vaadin/client/ui/label/LabelConnector.java b/client/src/main/java/com/vaadin/client/ui/label/LabelConnector.java new file mode 100644 index 0000000000..fc94f27cf0 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/label/LabelConnector.java @@ -0,0 +1,82 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.label; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.PreElement; +import com.vaadin.client.Profiler; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.VLabel; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.Connect.LoadStyle; +import com.vaadin.shared.ui.label.LabelState; +import com.vaadin.ui.Label; + +@Connect(value = Label.class, loadStyle = LoadStyle.EAGER) +public class LabelConnector extends AbstractComponentConnector { + + @Override + public LabelState getState() { + return (LabelState) super.getState(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + boolean sinkOnloads = false; + Profiler.enter("LabelConnector.onStateChanged update content"); + switch (getState().contentMode) { + case PREFORMATTED: + PreElement preElement = Document.get().createPreElement(); + preElement.setInnerText(getState().text); + // clear existing content + getWidget().setHTML(""); + // add preformatted text to dom + getWidget().getElement().appendChild(preElement); + break; + + case TEXT: + getWidget().setText(getState().text); + break; + + case HTML: + case RAW: + sinkOnloads = true; + case XML: + getWidget().setHTML(getState().text); + break; + default: + getWidget().setText(""); + break; + + } + Profiler.leave("LabelConnector.onStateChanged update content"); + + if (sinkOnloads) { + Profiler.enter("LabelConnector.onStateChanged sinkOnloads"); + WidgetUtil.sinkOnloadForImages(getWidget().getElement()); + Profiler.leave("LabelConnector.onStateChanged sinkOnloads"); + } + } + + @Override + public VLabel getWidget() { + return (VLabel) super.getWidget(); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/layout/ComponentConnectorLayoutSlot.java b/client/src/main/java/com/vaadin/client/ui/layout/ComponentConnectorLayoutSlot.java new file mode 100644 index 0000000000..b323fde1db --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/layout/ComponentConnectorLayoutSlot.java @@ -0,0 +1,111 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.layout; + +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.LayoutManager; +import com.vaadin.client.VCaption; +import com.vaadin.client.ui.ManagedLayout; + +public class ComponentConnectorLayoutSlot extends VLayoutSlot { + + final ComponentConnector child; + final ManagedLayout layout; + + public ComponentConnectorLayoutSlot(String baseClassName, + ComponentConnector child, ManagedLayout layout) { + super(baseClassName, child.getWidget()); + this.child = child; + this.layout = layout; + } + + public ComponentConnector getChild() { + return child; + } + + @Override + protected int getCaptionHeight() { + VCaption caption = getCaption(); + return caption != null ? getLayoutManager().getOuterHeight( + caption.getElement()) : 0; + } + + @Override + protected int getCaptionWidth() { + VCaption caption = getCaption(); + return caption != null ? getLayoutManager().getOuterWidth( + caption.getElement()) : 0; + } + + public LayoutManager getLayoutManager() { + return layout.getLayoutManager(); + } + + @Override + public void setCaption(VCaption caption) { + VCaption oldCaption = getCaption(); + if (oldCaption != null) { + getLayoutManager().unregisterDependency(layout, + oldCaption.getElement()); + } + super.setCaption(caption); + if (caption != null) { + getLayoutManager().registerDependency( + (ManagedLayout) child.getParent(), caption.getElement()); + } + } + + @Override + protected void reportActualRelativeHeight(int allocatedHeight) { + getLayoutManager().reportOuterHeight(child, allocatedHeight); + } + + @Override + protected void reportActualRelativeWidth(int allocatedWidth) { + getLayoutManager().reportOuterWidth(child, allocatedWidth); + } + + @Override + public int getWidgetHeight() { + return getLayoutManager() + .getOuterHeight(child.getWidget().getElement()); + } + + @Override + public int getWidgetWidth() { + return getLayoutManager().getOuterWidth(child.getWidget().getElement()); + } + + @Override + public boolean isUndefinedHeight() { + return child.isUndefinedHeight(); + } + + @Override + public boolean isUndefinedWidth() { + return child.isUndefinedWidth(); + } + + @Override + public boolean isRelativeHeight() { + return child.isRelativeHeight(); + } + + @Override + public boolean isRelativeWidth() { + return child.isRelativeWidth(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/layout/ElementResizeEvent.java b/client/src/main/java/com/vaadin/client/ui/layout/ElementResizeEvent.java new file mode 100644 index 0000000000..a1f75baff4 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/layout/ElementResizeEvent.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.layout; + +import com.google.gwt.dom.client.Element; +import com.vaadin.client.LayoutManager; + +public class ElementResizeEvent { + private final Element element; + private final LayoutManager layoutManager; + + public ElementResizeEvent(LayoutManager layoutManager, Element element) { + this.layoutManager = layoutManager; + this.element = element; + } + + public Element getElement() { + return element; + } + + public LayoutManager getLayoutManager() { + return layoutManager; + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/layout/ElementResizeListener.java b/client/src/main/java/com/vaadin/client/ui/layout/ElementResizeListener.java new file mode 100644 index 0000000000..97ca34a8a4 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/layout/ElementResizeListener.java @@ -0,0 +1,21 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.layout; + +public interface ElementResizeListener { + public void onElementResize(ElementResizeEvent e); +} diff --git a/client/src/main/java/com/vaadin/client/ui/layout/LayoutDependencyTree.java b/client/src/main/java/com/vaadin/client/ui/layout/LayoutDependencyTree.java new file mode 100644 index 0000000000..27733bfbe3 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/layout/LayoutDependencyTree.java @@ -0,0 +1,763 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.layout; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.logging.Logger; + +import com.google.gwt.core.client.JsArrayString; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorMap; +import com.vaadin.client.FastStringMap; +import com.vaadin.client.FastStringSet; +import com.vaadin.client.HasComponentsConnector; +import com.vaadin.client.JsArrayObject; +import com.vaadin.client.Profiler; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.Util; +import com.vaadin.client.VConsole; +import com.vaadin.client.ui.ManagedLayout; +import com.vaadin.shared.AbstractComponentState; + +/** + * Internal class used to keep track of layout dependencies during one layout + * run. This class is not intended to be used directly by applications. + * + * @author Vaadin Ltd + * @since 7.0.0 + */ +public class LayoutDependencyTree { + private class LayoutDependency { + private final ComponentConnector connector; + private final int direction; + + private boolean needsLayout = false; + private boolean needsMeasure = false; + + private boolean scrollingParentCached = false; + private ComponentConnector scrollingBoundary = null; + + private FastStringSet measureBlockers = FastStringSet.create(); + private FastStringSet layoutBlockers = FastStringSet.create(); + + public LayoutDependency(ComponentConnector connector, int direction) { + this.connector = connector; + this.direction = direction; + } + + private void addLayoutBlocker(ComponentConnector blocker) { + String blockerId = blocker.getConnectorId(); + if (!layoutBlockers.contains(blockerId)) { + boolean wasEmpty = layoutBlockers.isEmpty(); + layoutBlockers.add(blockerId); + if (wasEmpty) { + if (needsLayout) { + getLayoutQueue(direction).remove( + connector.getConnectorId()); + } else { + // Propagation already done if needsLayout is set + propagatePotentialLayout(); + } + } + } + } + + private void removeLayoutBlocker(ComponentConnector blocker) { + String blockerId = blocker.getConnectorId(); + if (layoutBlockers.contains(blockerId)) { + layoutBlockers.remove(blockerId); + if (layoutBlockers.isEmpty()) { + if (needsLayout) { + getLayoutQueue(direction).add( + connector.getConnectorId()); + } else { + propagateNoUpcomingLayout(); + } + } + } + } + + private void addMeasureBlocker(ComponentConnector blocker) { + String blockerId = blocker.getConnectorId(); + boolean alreadyAdded = measureBlockers.contains(blockerId); + if (alreadyAdded) { + return; + } + boolean wasEmpty = measureBlockers.isEmpty(); + measureBlockers.add(blockerId); + if (wasEmpty) { + if (needsMeasure) { + getMeasureQueue(direction).remove( + connector.getConnectorId()); + } else { + propagatePotentialResize(); + } + } + } + + private void removeMeasureBlocker(ComponentConnector blocker) { + String blockerId = blocker.getConnectorId(); + boolean alreadyRemoved = !measureBlockers.contains(blockerId); + if (alreadyRemoved) { + return; + } + measureBlockers.remove(blockerId); + if (measureBlockers.isEmpty()) { + if (needsMeasure) { + getMeasureQueue(direction).add(connector.getConnectorId()); + } else { + propagateNoUpcomingResize(); + } + } + } + + public void setNeedsMeasure(boolean needsMeasure) { + if (needsMeasure && !this.needsMeasure) { + // If enabling needsMeasure + this.needsMeasure = needsMeasure; + + if (measureBlockers.isEmpty()) { + // Add to queue if there are no blockers + getMeasureQueue(direction).add(connector.getConnectorId()); + // Only need to propagate if not already propagated when + // setting blockers + propagatePotentialResize(); + } + } else if (!needsMeasure && this.needsMeasure + && measureBlockers.isEmpty()) { + // Only disable if there are no blockers (elements gets measured + // in both directions even if there is a blocker in one + // direction) + this.needsMeasure = needsMeasure; + getMeasureQueue(direction).remove(connector.getConnectorId()); + propagateNoUpcomingResize(); + } + } + + public void setNeedsLayout(boolean needsLayout) { + if (!(connector instanceof ManagedLayout)) { + throw new IllegalStateException( + "Only managed layouts can need layout, layout attempted for " + + Util.getConnectorString(connector)); + } + if (needsLayout && !this.needsLayout) { + // If enabling needsLayout + this.needsLayout = needsLayout; + + if (layoutBlockers.isEmpty()) { + // Add to queue if there are no blockers + getLayoutQueue(direction).add(connector.getConnectorId()); + // Only need to propagate if not already propagated when + // setting blockers + propagatePotentialLayout(); + } + } else if (!needsLayout && this.needsLayout + && layoutBlockers.isEmpty()) { + // Only disable if there are no layout blockers + // (SimpleManagedLayout gets layouted in both directions + // even if there is a blocker in one direction) + this.needsLayout = needsLayout; + getLayoutQueue(direction).remove(connector.getConnectorId()); + propagateNoUpcomingLayout(); + } + } + + private void propagatePotentialResize() { + JsArrayString needsSizeForLayout = getNeedsSizeForLayout(); + int length = needsSizeForLayout.length(); + for (int i = 0; i < length; i++) { + String needsSizeId = needsSizeForLayout.get(i); + LayoutDependency layoutDependency = getDependency(needsSizeId, + direction); + layoutDependency.addLayoutBlocker(connector); + } + } + + private JsArrayString getNeedsSizeForLayout() { + // Find all connectors that need the size of this connector for + // layouting + + // Parent needs size if it isn't relative? + // Connector itself needs size if it isn't undefined? + // Children doesn't care? + + JsArrayString needsSize = JsArrayObject.createArray().cast(); + + if (!isUndefinedInDirection(connector, direction)) { + needsSize.push(connector.getConnectorId()); + } + if (!isRelativeInDirection(connector, direction)) { + ServerConnector parent = connector.getParent(); + if (parent instanceof ComponentConnector) { + needsSize.push(parent.getConnectorId()); + } + } + + return needsSize; + } + + private void propagateNoUpcomingResize() { + JsArrayString needsSizeForLayout = getNeedsSizeForLayout(); + int length = needsSizeForLayout.length(); + for (int i = 0; i < length; i++) { + String mightNeedLayoutId = needsSizeForLayout.get(i); + LayoutDependency layoutDependency = getDependency( + mightNeedLayoutId, direction); + layoutDependency.removeLayoutBlocker(connector); + } + } + + private void propagatePotentialLayout() { + JsArrayString resizedByLayout = getResizedByLayout(); + int length = resizedByLayout.length(); + for (int i = 0; i < length; i++) { + String sizeMightChangeId = resizedByLayout.get(i); + LayoutDependency layoutDependency = getDependency( + sizeMightChangeId, direction); + layoutDependency.addMeasureBlocker(connector); + } + } + + private JsArrayString getResizedByLayout() { + // Components that might get resized by a layout of this component + + // Parent never resized + // Connector itself resized if undefined + // Children resized if relative + + JsArrayString resized = JsArrayObject.createArray().cast(); + if (isUndefinedInDirection(connector, direction)) { + resized.push(connector.getConnectorId()); + } + + if (connector instanceof HasComponentsConnector) { + HasComponentsConnector container = (HasComponentsConnector) connector; + for (ComponentConnector child : container.getChildComponents()) { + if (!Util.shouldSkipMeasurementOfConnector(child, connector) + && isRelativeInDirection(child, direction)) { + resized.push(child.getConnectorId()); + } + } + } + + return resized; + } + + private void propagateNoUpcomingLayout() { + JsArrayString resizedByLayout = getResizedByLayout(); + int length = resizedByLayout.length(); + for (int i = 0; i < length; i++) { + String sizeMightChangeId = resizedByLayout.get(i); + LayoutDependency layoutDependency = getDependency( + sizeMightChangeId, direction); + layoutDependency.removeMeasureBlocker(connector); + } + } + + public void markSizeAsChanged() { + Profiler.enter("LayoutDependency.markSizeAsChanged phase 1"); + // When the size has changed, all that use that size should be + // layouted + JsArrayString needsSizeForLayout = getNeedsSizeForLayout(); + int length = needsSizeForLayout.length(); + for (int i = 0; i < length; i++) { + String connectorId = needsSizeForLayout.get(i); + LayoutDependency layoutDependency = getDependency(connectorId, + direction); + if (layoutDependency.connector instanceof ManagedLayout) { + Profiler.enter("LayoutDependency.markSizeAsChanged setNeedsLayout"); + layoutDependency.setNeedsLayout(true); + Profiler.leave("LayoutDependency.markSizeAsChanged setNeedsLayout"); + } else { + Profiler.enter("LayoutDependency.markSizeAsChanged propagatePostLayoutMeasure"); + // Should simulate setNeedsLayout(true) + markAsLayouted -> + // propagate needs measure + layoutDependency.propagatePostLayoutMeasure(); + Profiler.leave("LayoutDependency.markSizeAsChanged propagatePostLayoutMeasure"); + } + } + Profiler.leave("LayoutDependency.markSizeAsChanged phase 1"); + + Profiler.enter("LayoutDependency.markSizeAsChanged scrollbars"); + // Should also go through the hierarchy to discover appeared or + // disappeared scrollbars + ComponentConnector scrollingBoundary = getScrollingBoundary(connector); + if (scrollingBoundary != null) { + getDependency(scrollingBoundary.getConnectorId(), + getOppositeDirection()).setNeedsMeasure(true); + } + Profiler.leave("LayoutDependency.markSizeAsChanged scrollbars"); + + } + + /** + * Go up the hierarchy to find a component whose size might have changed + * in the other direction because changes to this component causes + * scrollbars to appear or disappear. + * + * @return + */ + private LayoutDependency findPotentiallyChangedScrollbar() { + ComponentConnector currentConnector = connector; + while (true) { + ServerConnector parent = currentConnector.getParent(); + if (!(parent instanceof ComponentConnector)) { + return null; + } + if (parent instanceof MayScrollChildren) { + return getDependency(currentConnector.getConnectorId(), + getOppositeDirection()); + } + currentConnector = (ComponentConnector) parent; + } + } + + private int getOppositeDirection() { + return direction == HORIZONTAL ? VERTICAL : HORIZONTAL; + } + + public void markAsLayouted() { + if (!layoutBlockers.isEmpty()) { + // Don't do anything if there are layout blockers (SimpleLayout + // gets layouted in both directions even if one direction is + // blocked) + return; + } + setNeedsLayout(false); + propagatePostLayoutMeasure(); + } + + private void propagatePostLayoutMeasure() { + Profiler.enter("LayoutDependency.propagatePostLayoutMeasure getResizedByLayout"); + JsArrayString resizedByLayout = getResizedByLayout(); + Profiler.leave("LayoutDependency.propagatePostLayoutMeasure getResizedByLayout"); + int length = resizedByLayout.length(); + for (int i = 0; i < length; i++) { + Profiler.enter("LayoutDependency.propagatePostLayoutMeasure setNeedsMeasure"); + String resizedId = resizedByLayout.get(i); + LayoutDependency layoutDependency = getDependency(resizedId, + direction); + layoutDependency.setNeedsMeasure(true); + Profiler.leave("LayoutDependency.propagatePostLayoutMeasure setNeedsMeasure"); + } + + // Special case for e.g. wrapping texts + Profiler.enter("LayoutDependency.propagatePostLayoutMeasure horizontal case"); + if (direction == HORIZONTAL && !connector.isUndefinedWidth() + && connector.isUndefinedHeight()) { + LayoutDependency dependency = getDependency( + connector.getConnectorId(), VERTICAL); + dependency.setNeedsMeasure(true); + } + Profiler.leave("LayoutDependency.propagatePostLayoutMeasure horizontal case"); + } + + @Override + public String toString() { + String s = getCompactConnectorString(connector) + "\n"; + if (direction == VERTICAL) { + s += "Vertical"; + } else { + s += "Horizontal"; + } + AbstractComponentState state = connector.getState(); + s += " sizing: " + + getSizeDefinition(direction == VERTICAL ? state.height + : state.width) + "\n"; + + if (needsLayout) { + s += "Needs layout\n"; + } + if (getLayoutQueue(direction).contains(connector.getConnectorId())) { + s += "In layout queue\n"; + } + s += "Layout blockers: " + blockersToString(layoutBlockers) + "\n"; + + if (needsMeasure) { + s += "Needs measure\n"; + } + if (getMeasureQueue(direction).contains(connector.getConnectorId())) { + s += "In measure queue\n"; + } + s += "Measure blockers: " + blockersToString(measureBlockers); + + return s; + } + + public boolean noMoreChangesExpected() { + return !needsLayout && !needsMeasure && layoutBlockers.isEmpty() + && measureBlockers.isEmpty(); + } + + } + + private static final int HORIZONTAL = 0; + private static final int VERTICAL = 1; + + @SuppressWarnings("unchecked") + private final FastStringMap[] dependenciesInDirection = new FastStringMap[] { + FastStringMap.create(), FastStringMap.create() }; + + private final FastStringSet[] measureQueueInDirection = new FastStringSet[] { + FastStringSet.create(), FastStringSet.create() }; + + private final FastStringSet[] layoutQueueInDirection = new FastStringSet[] { + FastStringSet.create(), FastStringSet.create() }; + + private final ApplicationConnection connection; + + public LayoutDependencyTree(ApplicationConnection connection) { + this.connection = connection; + } + + public void setNeedsMeasure(ComponentConnector connector, + boolean needsMeasure) { + setNeedsHorizontalMeasure(connector, needsMeasure); + setNeedsVerticalMeasure(connector, needsMeasure); + } + + /** + * @param connectorId + * @param needsMeasure + * + * @deprecated As of 7.4.2, use + * {@link #setNeedsMeasure(ComponentConnector, boolean)} for + * improved performance. + */ + @Deprecated + public void setNeedsMeasure(String connectorId, boolean needsMeasure) { + ComponentConnector connector = (ComponentConnector) ConnectorMap.get( + connection).getConnector(connectorId); + if (connector == null) { + return; + } + + setNeedsMeasure(connector, needsMeasure); + } + + public void setNeedsHorizontalMeasure(ComponentConnector connector, + boolean needsMeasure) { + LayoutDependency dependency = getDependency(connector, HORIZONTAL); + dependency.setNeedsMeasure(needsMeasure); + } + + public void setNeedsHorizontalMeasure(String connectorId, + boolean needsMeasure) { + // Ensure connector exists + ComponentConnector connector = (ComponentConnector) ConnectorMap.get( + connection).getConnector(connectorId); + if (connector == null) { + return; + } + + setNeedsHorizontalMeasure(connector, needsMeasure); + } + + public void setNeedsVerticalMeasure(ComponentConnector connector, + boolean needsMeasure) { + LayoutDependency dependency = getDependency(connector, VERTICAL); + dependency.setNeedsMeasure(needsMeasure); + } + + public void setNeedsVerticalMeasure(String connectorId, boolean needsMeasure) { + // Ensure connector exists + ComponentConnector connector = (ComponentConnector) ConnectorMap.get( + connection).getConnector(connectorId); + if (connector == null) { + return; + } + + setNeedsVerticalMeasure(connector, needsMeasure); + } + + private LayoutDependency getDependency(ComponentConnector connector, + int direction) { + return getDependency(connector.getConnectorId(), connector, direction); + } + + private LayoutDependency getDependency(String connectorId, int direction) { + return getDependency(connectorId, null, direction); + } + + private LayoutDependency getDependency(String connectorId, + ComponentConnector connector, int direction) { + FastStringMap dependencies = dependenciesInDirection[direction]; + LayoutDependency dependency = dependencies.get(connectorId); + if (dependency == null) { + if (connector == null) { + connector = (ComponentConnector) ConnectorMap.get(connection) + .getConnector(connectorId); + if (connector == null) { + getLogger().warning( + "No connector found for id " + connectorId + + " while creating LayoutDependency"); + return null; + } + } + dependency = new LayoutDependency(connector, direction); + dependencies.put(connectorId, dependency); + } + return dependency; + } + + private FastStringSet getLayoutQueue(int direction) { + return layoutQueueInDirection[direction]; + } + + private FastStringSet getMeasureQueue(int direction) { + return measureQueueInDirection[direction]; + } + + /** + * @param layout + * @param needsLayout + * + * @deprecated As of 7.0.1, use + * {@link #setNeedsHorizontalLayout(String, boolean)} for + * improved performance. + */ + @Deprecated + public void setNeedsHorizontalLayout(ManagedLayout layout, + boolean needsLayout) { + setNeedsHorizontalLayout(layout.getConnectorId(), needsLayout); + } + + public void setNeedsHorizontalLayout(String connectorId, boolean needsLayout) { + LayoutDependency dependency = getDependency(connectorId, HORIZONTAL); + if (dependency != null) { + dependency.setNeedsLayout(needsLayout); + } else { + getLogger().warning( + "No dependency found in setNeedsHorizontalLayout"); + } + } + + /** + * @param layout + * @param needsLayout + * + * @deprecated As of 7.0.1, use + * {@link #setNeedsVerticalLayout(String, boolean)} for improved + * performance. + */ + @Deprecated + public void setNeedsVerticalLayout(ManagedLayout layout, boolean needsLayout) { + setNeedsVerticalLayout(layout.getConnectorId(), needsLayout); + } + + public void setNeedsVerticalLayout(String connectorId, boolean needsLayout) { + LayoutDependency dependency = getDependency(connectorId, VERTICAL); + if (dependency != null) { + dependency.setNeedsLayout(needsLayout); + } else { + getLogger() + .warning("No dependency found in setNeedsVerticalLayout"); + } + + } + + public void markAsHorizontallyLayouted(ManagedLayout layout) { + LayoutDependency dependency = getDependency(layout.getConnectorId(), + HORIZONTAL); + dependency.markAsLayouted(); + } + + public void markAsVerticallyLayouted(ManagedLayout layout) { + LayoutDependency dependency = getDependency(layout.getConnectorId(), + VERTICAL); + dependency.markAsLayouted(); + } + + public void markHeightAsChanged(ComponentConnector connector) { + LayoutDependency dependency = getDependency(connector.getConnectorId(), + VERTICAL); + dependency.markSizeAsChanged(); + } + + public void markWidthAsChanged(ComponentConnector connector) { + LayoutDependency dependency = getDependency(connector.getConnectorId(), + HORIZONTAL); + dependency.markSizeAsChanged(); + } + + private static boolean isRelativeInDirection(ComponentConnector connector, + int direction) { + if (direction == HORIZONTAL) { + return connector.isRelativeWidth(); + } else { + return connector.isRelativeHeight(); + } + } + + private static boolean isUndefinedInDirection(ComponentConnector connector, + int direction) { + if (direction == VERTICAL) { + return connector.isUndefinedHeight(); + } else { + return connector.isUndefinedWidth(); + } + } + + private static String getCompactConnectorString(ServerConnector connector) { + return connector.getClass().getSimpleName() + " (" + + connector.getConnectorId() + ")"; + } + + private static String getSizeDefinition(String size) { + if (size == null || size.length() == 0) { + return "undefined"; + } else if (size.endsWith("%")) { + return "relative"; + } else { + return "fixed"; + } + } + + private String blockersToString(FastStringSet blockers) { + StringBuilder b = new StringBuilder("["); + + ConnectorMap connectorMap = ConnectorMap.get(connection); + JsArrayString blockersDump = blockers.dump(); + for (int i = 0; i < blockersDump.length(); i++) { + ServerConnector blocker = connectorMap.getConnector(blockersDump + .get(i)); + if (b.length() != 1) { + b.append(", "); + } + b.append(getCompactConnectorString(blocker)); + } + b.append(']'); + return b.toString(); + } + + public boolean hasConnectorsToMeasure() { + return !measureQueueInDirection[HORIZONTAL].isEmpty() + || !measureQueueInDirection[VERTICAL].isEmpty(); + } + + public boolean hasHorizontalConnectorToLayout() { + return !getLayoutQueue(HORIZONTAL).isEmpty(); + } + + public boolean hasVerticaConnectorToLayout() { + return !getLayoutQueue(VERTICAL).isEmpty(); + } + + /** + * @return + * @deprecated As of 7.0.1, use {@link #getHorizontalLayoutTargetsJsArray()} + * for improved performance. + */ + @Deprecated + public ManagedLayout[] getHorizontalLayoutTargets() { + return asManagedLayoutArray(getHorizontalLayoutTargetsJsArray()); + } + + /** + * @return + * @deprecated As of 7.0.1, use {@link #getVerticalLayoutTargetsJsArray()} + * for improved performance. + */ + @Deprecated + public ManagedLayout[] getVerticalLayoutTargets() { + return asManagedLayoutArray(getVerticalLayoutTargetsJsArray()); + } + + private ManagedLayout[] asManagedLayoutArray(JsArrayString connectorIdArray) { + int length = connectorIdArray.length(); + ConnectorMap connectorMap = ConnectorMap.get(connection); + ManagedLayout[] result = new ManagedLayout[length]; + for (int i = 0; i < length; i++) { + result[i] = (ManagedLayout) connectorMap + .getConnector(connectorIdArray.get(i)); + } + return result; + } + + public JsArrayString getHorizontalLayoutTargetsJsArray() { + return getLayoutQueue(HORIZONTAL).dump(); + } + + public JsArrayString getVerticalLayoutTargetsJsArray() { + return getLayoutQueue(VERTICAL).dump(); + } + + /** + * @return + * @deprecated As of 7.0.1, use {@link #getMeasureTargetsJsArray()} for + * improved performance. + */ + @Deprecated + public Collection getMeasureTargets() { + JsArrayString targetIds = getMeasureTargetsJsArray(); + int length = targetIds.length(); + ArrayList targets = new ArrayList( + length); + ConnectorMap connectorMap = ConnectorMap.get(connection); + + for (int i = 0; i < length; i++) { + targets.add((ComponentConnector) connectorMap + .getConnector(targetIds.get(i))); + } + return targets; + } + + public JsArrayString getMeasureTargetsJsArray() { + FastStringSet allMeasuredTargets = FastStringSet.create(); + allMeasuredTargets.addAll(getMeasureQueue(HORIZONTAL)); + allMeasuredTargets.addAll(getMeasureQueue(VERTICAL)); + return allMeasuredTargets.dump(); + } + + public void logDependencyStatus(ComponentConnector connector) { + VConsole.log("===="); + String connectorId = connector.getConnectorId(); + VConsole.log(getDependency(connectorId, HORIZONTAL).toString()); + VConsole.log(getDependency(connectorId, VERTICAL).toString()); + } + + public boolean noMoreChangesExpected(ComponentConnector connector) { + return getDependency(connector.getConnectorId(), HORIZONTAL) + .noMoreChangesExpected() + && getDependency(connector.getConnectorId(), VERTICAL) + .noMoreChangesExpected(); + } + + public ComponentConnector getScrollingBoundary(ComponentConnector connector) { + LayoutDependency dependency = getDependency(connector.getConnectorId(), + HORIZONTAL); + if (!dependency.scrollingParentCached) { + ServerConnector parent = dependency.connector.getParent(); + if (parent instanceof MayScrollChildren) { + dependency.scrollingBoundary = connector; + } else if (parent instanceof ComponentConnector) { + dependency.scrollingBoundary = getScrollingBoundary((ComponentConnector) parent); + } else { + // No scrolling parent + } + + dependency.scrollingParentCached = true; + } + return dependency.scrollingBoundary; + } + + private static Logger getLogger() { + return Logger.getLogger(LayoutDependencyTree.class.getName()); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/layout/Margins.java b/client/src/main/java/com/vaadin/client/ui/layout/Margins.java new file mode 100644 index 0000000000..75839c9ce8 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/layout/Margins.java @@ -0,0 +1,98 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.layout; + +public class Margins { + + private int marginTop; + private int marginBottom; + private int marginLeft; + private int marginRight; + + private int horizontal = 0; + private int vertical = 0; + + public Margins(int marginTop, int marginBottom, int marginLeft, + int marginRight) { + super(); + this.marginTop = marginTop; + this.marginBottom = marginBottom; + this.marginLeft = marginLeft; + this.marginRight = marginRight; + + updateHorizontal(); + updateVertical(); + } + + public int getMarginTop() { + return marginTop; + } + + public int getMarginBottom() { + return marginBottom; + } + + public int getMarginLeft() { + return marginLeft; + } + + public int getMarginRight() { + return marginRight; + } + + public int getHorizontal() { + return horizontal; + } + + public int getVertical() { + return vertical; + } + + public void setMarginTop(int marginTop) { + this.marginTop = marginTop; + updateVertical(); + } + + public void setMarginBottom(int marginBottom) { + this.marginBottom = marginBottom; + updateVertical(); + } + + public void setMarginLeft(int marginLeft) { + this.marginLeft = marginLeft; + updateHorizontal(); + } + + public void setMarginRight(int marginRight) { + this.marginRight = marginRight; + updateHorizontal(); + } + + private void updateVertical() { + vertical = marginTop + marginBottom; + } + + private void updateHorizontal() { + horizontal = marginLeft + marginRight; + } + + @Override + public String toString() { + return "Margins [marginLeft=" + marginLeft + ",marginTop=" + marginTop + + ",marginRight=" + marginRight + ",marginBottom=" + + marginBottom + "]"; + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/layout/MayScrollChildren.java b/client/src/main/java/com/vaadin/client/ui/layout/MayScrollChildren.java new file mode 100644 index 0000000000..2a0b821646 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/layout/MayScrollChildren.java @@ -0,0 +1,22 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.layout; + +import com.vaadin.client.HasComponentsConnector; + +public interface MayScrollChildren extends HasComponentsConnector { + +} diff --git a/client/src/main/java/com/vaadin/client/ui/layout/VLayoutSlot.java b/client/src/main/java/com/vaadin/client/ui/layout/VLayoutSlot.java new file mode 100644 index 0000000000..bc0c6739bb --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/layout/VLayoutSlot.java @@ -0,0 +1,306 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.layout; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.VCaption; +import com.vaadin.shared.ui.AlignmentInfo; + +public abstract class VLayoutSlot { + + private final Element wrapper = Document.get().createDivElement(); + + private AlignmentInfo alignment; + private VCaption caption; + private final Widget widget; + + private double expandRatio; + + public VLayoutSlot(String baseClassName, Widget widget) { + this.widget = widget; + + wrapper.setClassName(baseClassName + "-slot"); + } + + public VCaption getCaption() { + return caption; + } + + public void setCaption(VCaption caption) { + if (this.caption != null) { + this.caption.removeFromParent(); + } + this.caption = caption; + if (caption != null) { + // Physical attach. + DOM.insertBefore(wrapper, caption.getElement(), widget.getElement()); + Style style = caption.getElement().getStyle(); + style.setPosition(Position.ABSOLUTE); + style.setTop(0, Unit.PX); + } + } + + public AlignmentInfo getAlignment() { + return alignment; + } + + public Widget getWidget() { + return widget; + } + + public void setAlignment(AlignmentInfo alignment) { + this.alignment = alignment; + // if alignment is something other than topLeft then we need to align + // the component inside this slot + if (alignment != null && (!alignment.isLeft() || !alignment.isTop())) { + widget.getElement().getStyle().setPosition(Position.ABSOLUTE); + } + } + + public void positionHorizontally(double currentLocation, + double allocatedSpace, double marginRight) { + Style style = wrapper.getStyle(); + + double availableWidth = allocatedSpace; + + VCaption caption = getCaption(); + Style captionStyle = caption != null ? caption.getElement().getStyle() + : null; + int captionWidth = getCaptionWidth(); + + boolean captionAboveCompnent; + if (caption == null) { + captionAboveCompnent = false; + style.clearPaddingRight(); + } else { + captionAboveCompnent = !caption.shouldBePlacedAfterComponent(); + if (!captionAboveCompnent) { + availableWidth -= captionWidth; + if (availableWidth < 0) { + availableWidth = 0; + } + captionStyle.clearLeft(); + captionStyle.setRight(0, Unit.PX); + style.setPaddingRight(captionWidth, Unit.PX); + } else { + captionStyle.setLeft(0, Unit.PX); + captionStyle.clearRight(); + style.clearPaddingRight(); + } + } + + if (marginRight > 0) { + style.setMarginRight(marginRight, Unit.PX); + } else { + style.clearMarginRight(); + } + + style.setPropertyPx("width", (int) availableWidth); + + double allocatedContentWidth = 0; + if (isRelativeWidth()) { + String percentWidth = getWidget().getElement().getStyle() + .getWidth(); + double percentage = parsePercent(percentWidth); + allocatedContentWidth = availableWidth * (percentage / 100); + reportActualRelativeWidth(Math.round((float) allocatedContentWidth)); + } + + style.setLeft(Math.round(currentLocation), Unit.PX); + AlignmentInfo alignment = getAlignment(); + if (!alignment.isLeft()) { + double usedWidth; + if (isRelativeWidth()) { + usedWidth = allocatedContentWidth; + } else { + usedWidth = getWidgetWidth(); + } + + double padding = (allocatedSpace - usedWidth); + if (alignment.isHorizontalCenter()) { + padding = padding / 2; + } + + long roundedPadding = Math.round(padding); + if (captionAboveCompnent) { + captionStyle.setLeft(roundedPadding, Unit.PX); + } + widget.getElement().getStyle().setLeft(roundedPadding, Unit.PX); + } else { + if (captionAboveCompnent) { + captionStyle.setLeft(0, Unit.PX); + } + // Reset left when changing back to align left + widget.getElement().getStyle().clearLeft(); + } + + } + + private double parsePercent(String size) { + return Double.parseDouble(size.replaceAll("%", "")); + } + + public void positionVertically(double currentLocation, + double allocatedSpace, double marginBottom) { + Style style = wrapper.getStyle(); + + double contentHeight = allocatedSpace; + + int captionHeight; + VCaption caption = getCaption(); + if (caption == null || caption.shouldBePlacedAfterComponent()) { + style.clearPaddingTop(); + captionHeight = 0; + } else { + captionHeight = getCaptionHeight(); + contentHeight -= captionHeight; + if (contentHeight < 0) { + contentHeight = 0; + } + style.setPaddingTop(captionHeight, Unit.PX); + } + + if (marginBottom > 0) { + style.setMarginBottom(marginBottom, Unit.PX); + } else { + style.clearMarginBottom(); + } + + style.setHeight(contentHeight, Unit.PX); + + double allocatedContentHeight = 0; + if (isRelativeHeight()) { + String height = getWidget().getElement().getStyle().getHeight(); + double percentage = parsePercent(height); + allocatedContentHeight = contentHeight * (percentage / 100); + reportActualRelativeHeight(Math + .round((float) allocatedContentHeight)); + } + + style.setTop(currentLocation, Unit.PX); + double padding = 0; + AlignmentInfo alignment = getAlignment(); + if (!alignment.isTop()) { + double usedHeight; + if (isRelativeHeight()) { + usedHeight = captionHeight + allocatedContentHeight; + } else { + usedHeight = getUsedHeight(); + } + if (alignment.isVerticalCenter()) { + padding = (allocatedSpace - usedHeight) / 2d; + } else { + padding = (allocatedSpace - usedHeight); + } + padding += captionHeight; + + widget.getElement().getStyle().setTop(padding, Unit.PX); + } else { + // Reset top when changing back to align top + widget.getElement().getStyle().clearTop(); + + } + } + + protected void reportActualRelativeHeight(int allocatedHeight) { + // Default implementation does nothing + } + + protected void reportActualRelativeWidth(int allocatedWidth) { + // Default implementation does nothing + } + + public void positionInDirection(double currentLocation, + double allocatedSpace, double endingMargin, boolean isVertical) { + if (isVertical) { + positionVertically(currentLocation, allocatedSpace, endingMargin); + } else { + positionHorizontally(currentLocation, allocatedSpace, endingMargin); + } + } + + public int getWidgetSizeInDirection(boolean isVertical) { + return isVertical ? getWidgetHeight() : getWidgetWidth(); + } + + public int getUsedWidth() { + int widgetWidth = getWidgetWidth(); + if (caption == null) { + return widgetWidth; + } else if (caption.shouldBePlacedAfterComponent()) { + return widgetWidth + getCaptionWidth(); + } else { + return Math.max(widgetWidth, getCaptionWidth()); + } + } + + public int getUsedHeight() { + int widgetHeight = getWidgetHeight(); + if (caption == null) { + return widgetHeight; + } else if (caption.shouldBePlacedAfterComponent()) { + return Math.max(widgetHeight, getCaptionHeight()); + } else { + return widgetHeight + getCaptionHeight(); + } + } + + public int getUsedSizeInDirection(boolean isVertical) { + return isVertical ? getUsedHeight() : getUsedWidth(); + } + + protected abstract int getCaptionHeight(); + + protected abstract int getCaptionWidth(); + + public abstract int getWidgetHeight(); + + public abstract int getWidgetWidth(); + + public abstract boolean isUndefinedHeight(); + + public abstract boolean isUndefinedWidth(); + + public boolean isUndefinedInDirection(boolean isVertical) { + return isVertical ? isUndefinedHeight() : isUndefinedWidth(); + } + + public abstract boolean isRelativeHeight(); + + public abstract boolean isRelativeWidth(); + + public boolean isRelativeInDirection(boolean isVertical) { + return isVertical ? isRelativeHeight() : isRelativeWidth(); + } + + public com.google.gwt.user.client.Element getWrapperElement() { + return DOM.asOld(wrapper); + } + + public void setExpandRatio(double expandRatio) { + this.expandRatio = expandRatio; + } + + public double getExpandRatio() { + return expandRatio; + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/link/LinkConnector.java b/client/src/main/java/com/vaadin/client/ui/link/LinkConnector.java new file mode 100644 index 0000000000..1e77fb51b4 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/link/LinkConnector.java @@ -0,0 +1,103 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.link; + +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.user.client.DOM; +import com.vaadin.client.VCaption; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.Icon; +import com.vaadin.client.ui.VLink; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.link.LinkConstants; +import com.vaadin.shared.ui.link.LinkState; +import com.vaadin.ui.Link; + +@Connect(Link.class) +public class LinkConnector extends AbstractComponentConnector { + + @Override + public LinkState getState() { + return (LinkState) super.getState(); + } + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().enabled = isEnabled(); + + if (stateChangeEvent.hasPropertyChanged("resources")) { + getWidget().src = getResourceUrl(LinkConstants.HREF_RESOURCE); + if (getWidget().src == null) { + getWidget().anchor.removeAttribute("href"); + } else { + getWidget().anchor.setAttribute("href", getWidget().src); + } + } + + getWidget().target = getState().target; + if (getWidget().target == null) { + getWidget().anchor.removeAttribute("target"); + } else { + getWidget().anchor.setAttribute("target", getWidget().target); + } + + getWidget().borderStyle = getState().targetBorder; + getWidget().targetWidth = getState().targetWidth; + getWidget().targetHeight = getState().targetHeight; + + // Set link caption + VCaption.setCaptionText(getWidget().captionElement, getState()); + + // handle error + if (null != getState().errorMessage) { + if (getWidget().errorIndicatorElement == null) { + getWidget().errorIndicatorElement = DOM.createDiv(); + DOM.setElementProperty(getWidget().errorIndicatorElement, + "className", "v-errorindicator"); + } + DOM.insertChild(getWidget().getElement(), + getWidget().errorIndicatorElement, 0); + } else if (getWidget().errorIndicatorElement != null) { + getWidget().errorIndicatorElement.getStyle().setDisplay( + Display.NONE); + } + + if (getWidget().icon != null) { + getWidget().anchor.removeChild(getWidget().icon.getElement()); + getWidget().icon = null; + } + Icon icon = getIcon(); + if (icon != null) { + getWidget().icon = icon; + getWidget().anchor.insertBefore(icon.getElement(), + getWidget().captionElement); + } + } + + @Override + public VLink getWidget() { + return (VLink) super.getWidget(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/listselect/ListSelectConnector.java b/client/src/main/java/com/vaadin/client/ui/listselect/ListSelectConnector.java new file mode 100644 index 0000000000..b867a3358c --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/listselect/ListSelectConnector.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.listselect; + +import com.vaadin.client.ui.VListSelect; +import com.vaadin.client.ui.optiongroup.OptionGroupBaseConnector; +import com.vaadin.shared.ui.Connect; +import com.vaadin.ui.ListSelect; + +@Connect(ListSelect.class) +public class ListSelectConnector extends OptionGroupBaseConnector { + + @Override + public VListSelect getWidget() { + return (VListSelect) super.getWidget(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/menubar/MenuBar.java b/client/src/main/java/com/vaadin/client/ui/menubar/MenuBar.java new file mode 100644 index 0000000000..833fb5a38a --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/menubar/MenuBar.java @@ -0,0 +1,637 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.menubar; + +/* + * Copyright 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +// COPIED HERE DUE package privates in GWT +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.PopupListener; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ui.VOverlay; + +/** + * A standard menu bar widget. A menu bar can contain any number of menu items, + * each of which can either fire a {@link com.google.gwt.user.client.Command} or + * open a cascaded menu bar. + * + *

+ * + *

+ * + *

CSS Style Rules

+ *
    + *
  • .gwt-MenuBar { the menu bar itself }
  • + *
  • .gwt-MenuBar .gwt-MenuItem { menu items }
  • + *
  • + * .gwt-MenuBar .gwt-MenuItem-selected { selected menu items }
  • + *
+ * + *

+ *

Example

+ * {@example com.google.gwt.examples.MenuBarExample} + *

+ * + * @deprecated + */ +@Deprecated +public class MenuBar extends Widget implements PopupListener { + + private final Element body; + private final Element table; + private final Element outer; + + private final ArrayList items = new ArrayList(); + private MenuBar parentMenu; + private PopupPanel popup; + private MenuItem selectedItem; + private MenuBar shownChildMenu; + private final boolean vertical; + private boolean autoOpen; + + /** + * Creates an empty horizontal menu bar. + */ + public MenuBar() { + this(false); + } + + /** + * Creates an empty menu bar. + * + * @param vertical + * true to orient the menu bar vertically + */ + public MenuBar(boolean vertical) { + super(); + + table = DOM.createTable(); + body = DOM.createTBody(); + DOM.appendChild(table, body); + + if (!vertical) { + final Element tr = DOM.createTR(); + DOM.appendChild(body, tr); + } + + this.vertical = vertical; + + outer = DOM.createDiv(); + DOM.appendChild(outer, table); + setElement(outer); + + sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT); + setStyleName("gwt-MenuBar"); + } + + /** + * Adds a menu item to the bar. + * + * @param item + * the item to be added + */ + public void addItem(MenuItem item) { + Element tr; + if (vertical) { + tr = DOM.createTR(); + DOM.appendChild(body, tr); + } else { + tr = DOM.getChild(body, 0); + } + + DOM.appendChild(tr, item.getElement()); + + item.setParentMenu(this); + item.setSelectionStyle(false); + items.add(item); + } + + /** + * Adds a menu item to the bar, that will fire the given command when it is + * selected. + * + * @param text + * the item's text + * @param asHTML + * true to treat the specified text as html + * @param cmd + * the command to be fired + * @return the {@link MenuItem} object created + */ + public MenuItem addItem(String text, boolean asHTML, Command cmd) { + final MenuItem item = new MenuItem(text, asHTML, cmd); + addItem(item); + return item; + } + + /** + * Adds a menu item to the bar, that will open the specified menu when it is + * selected. + * + * @param text + * the item's text + * @param asHTML + * true to treat the specified text as html + * @param popup + * the menu to be cascaded from it + * @return the {@link MenuItem} object created + */ + public MenuItem addItem(String text, boolean asHTML, MenuBar popup) { + final MenuItem item = new MenuItem(text, asHTML, popup); + addItem(item); + return item; + } + + /** + * Adds a menu item to the bar, that will fire the given command when it is + * selected. + * + * @param text + * the item's text + * @param cmd + * the command to be fired + * @return the {@link MenuItem} object created + */ + public MenuItem addItem(String text, Command cmd) { + final MenuItem item = new MenuItem(text, cmd); + addItem(item); + return item; + } + + /** + * Adds a menu item to the bar, that will open the specified menu when it is + * selected. + * + * @param text + * the item's text + * @param popup + * the menu to be cascaded from it + * @return the {@link MenuItem} object created + */ + public MenuItem addItem(String text, MenuBar popup) { + final MenuItem item = new MenuItem(text, popup); + addItem(item); + return item; + } + + /** + * Removes all menu items from this menu bar. + */ + public void clearItems() { + final Element container = getItemContainerElement(); + while (DOM.getChildCount(container) > 0) { + DOM.removeChild(container, DOM.getChild(container, 0)); + } + items.clear(); + } + + /** + * Gets whether this menu bar's child menus will open when the mouse is + * moved over it. + * + * @return true if child menus will auto-open + */ + public boolean getAutoOpen() { + return autoOpen; + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + + final MenuItem item = findItem(DOM.eventGetTarget(event)); + switch (DOM.eventGetType(event)) { + case Event.ONCLICK: { + // Fire an item's command when the user clicks on it. + if (item != null) { + doItemAction(item, true); + } + break; + } + + case Event.ONMOUSEOVER: { + if (item != null) { + itemOver(item); + } + break; + } + + case Event.ONMOUSEOUT: { + if (item != null) { + itemOver(null); + } + break; + } + } + } + + @Override + public void onPopupClosed(PopupPanel sender, boolean autoClosed) { + // If the menu popup was auto-closed, close all of its parents as well. + if (autoClosed) { + closeAllParents(); + } + + // When the menu popup closes, remember that no item is + // currently showing a popup menu. + onHide(); + shownChildMenu = null; + popup = null; + } + + /** + * Removes the specified menu item from the bar. + * + * @param item + * the item to be removed + */ + public void removeItem(MenuItem item) { + final int idx = items.indexOf(item); + if (idx == -1) { + return; + } + + final Element container = getItemContainerElement(); + DOM.removeChild(container, DOM.getChild(container, idx)); + items.remove(idx); + } + + /** + * Sets whether this menu bar's child menus will open when the mouse is + * moved over it. + * + * @param autoOpen + * true to cause child menus to auto-open + */ + public void setAutoOpen(boolean autoOpen) { + this.autoOpen = autoOpen; + } + + /** + * Returns a list containing the MenuItem objects in the menu + * bar. If there are no items in the menu bar, then an empty + * List object will be returned. + * + * @return a list containing the MenuItem objects in the menu + * bar + */ + public List getItems() { + return items; + } + + /** + * Returns the MenuItem that is currently selected + * (highlighted) by the user. If none of the items in the menu are currently + * selected, then null will be returned. + * + * @return the MenuItem that is currently selected, or + * null if no items are currently selected + */ + public MenuItem getSelectedItem() { + return selectedItem; + } + + /** + * Gets the first item from the menu or null if no items. + * + * @since 7.2.6 + * @return the first item from the menu or null if no items. + */ + public MenuItem getFirstItem() { + return items != null && items.size() > 0 ? items.get(0) : null; + } + + /** + * Gest the last item from the menu or null if no items. + * + * @since 7.2.6 + * @return the last item from the menu or null if no items. + */ + public MenuItem getLastItem() { + return items != null && items.size() > 0 ? items.get(items.size() - 1) + : null; + } + + /** + * Gets the index of the selected item. + * + * @since 7.2.6 + * @return the index of the selected item. + */ + public int getSelectedIndex() { + return items != null ? items.indexOf(getSelectedItem()) : -1; + } + + @Override + protected void onDetach() { + // When the menu is detached, make sure to close all of its children. + if (popup != null) { + popup.hide(); + } + + super.onDetach(); + } + + /* + * Closes all parent menu popups. + */ + void closeAllParents() { + MenuBar curMenu = this; + while (curMenu != null) { + curMenu.close(); + + if ((curMenu.parentMenu == null) && (curMenu.selectedItem != null)) { + curMenu.selectedItem.setSelectionStyle(false); + curMenu.selectedItem = null; + } + + curMenu = curMenu.parentMenu; + } + } + + /* + * Performs the action associated with the given menu item. If the item has + * a popup associated with it, the popup will be shown. If it has a command + * associated with it, and 'fireCommand' is true, then the command will be + * fired. Popups associated with other items will be hidden. + * + * @param item the item whose popup is to be shown. @param fireCommand + * true if the item's command should be fired, + * false otherwise. + */ + protected void doItemAction(final MenuItem item, boolean fireCommand) { + // If the given item is already showing its menu, we're done. + if ((shownChildMenu != null) && (item.getSubMenu() == shownChildMenu)) { + return; + } + + // If another item is showing its menu, then hide it. + if (shownChildMenu != null) { + shownChildMenu.onHide(); + popup.hide(); + } + + // If the item has no popup, optionally fire its command. + if (item.getSubMenu() == null) { + if (fireCommand) { + // Close this menu and all of its parents. + closeAllParents(); + + // Fire the item's command. + final Command cmd = item.getCommand(); + if (cmd != null) { + Scheduler.get().scheduleDeferred(cmd); + } + } + return; + } + + // Ensure that the item is selected. + selectItem(item); + + // Create a new popup for this item, and position it next to + // the item (below if this is a horizontal menu bar, to the + // right if it's a vertical bar). + popup = new VOverlay(true) { + { + setWidget(item.getSubMenu()); + item.getSubMenu().onShow(); + setOwner(MenuBar.this); + } + + @Override + public boolean onEventPreview(Event event) { + // Hook the popup panel's event preview. We use this to keep it + // from + // auto-hiding when the parent menu is clicked. + switch (DOM.eventGetType(event)) { + case Event.ONCLICK: + // If the event target is part of the parent menu, suppress + // the + // event altogether. + final Element target = DOM.eventGetTarget(event); + final Element parentMenuElement = item.getParentMenu() + .getElement(); + if (DOM.isOrHasChild(parentMenuElement, target)) { + return false; + } + break; + } + + return super.onEventPreview(event); + } + }; + popup.addPopupListener(this); + + if (vertical) { + popup.setPopupPosition( + item.getAbsoluteLeft() + item.getOffsetWidth(), + item.getAbsoluteTop()); + } else { + popup.setPopupPosition(item.getAbsoluteLeft(), + item.getAbsoluteTop() + item.getOffsetHeight()); + } + + shownChildMenu = item.getSubMenu(); + item.getSubMenu().parentMenu = this; + + // Show the popup, ensuring that the menubar's event preview remains on + // top + // of the popup's. + popup.show(); + } + + void itemOver(MenuItem item) { + if (item == null) { + // Don't clear selection if the currently selected item's menu is + // showing. + if ((selectedItem != null) + && (shownChildMenu == selectedItem.getSubMenu())) { + return; + } + } + + // Style the item selected when the mouse enters. + selectItem(item); + + // If child menus are being shown, or this menu is itself + // a child menu, automatically show an item's child menu + // when the mouse enters. + if (item != null) { + if ((shownChildMenu != null) || (parentMenu != null) || autoOpen) { + doItemAction(item, false); + } + } + } + + public void selectItem(MenuItem item) { + if (item == selectedItem) { + scrollItemIntoView(item); + return; + } + + if (selectedItem != null) { + selectedItem.setSelectionStyle(false); + } + + if (item != null) { + item.setSelectionStyle(true); + } + + selectedItem = item; + + scrollItemIntoView(item); + } + + /* + * Scroll the specified item into view. + */ + private void scrollItemIntoView(MenuItem item) { + if (item != null) { + item.getElement().scrollIntoView(); + } + } + + /** + * Scroll the selected item into view. + * + * @since 7.2.6 + */ + public void scrollSelectionIntoView() { + scrollItemIntoView(selectedItem); + } + + /** + * Sets the menu scroll enabled or disabled. + * + * @since 7.2.6 + * @param enabled + * the enabled state of the scroll. + */ + public void setScrollEnabled(boolean enabled) { + if (enabled) { + if (vertical) { + outer.getStyle().setOverflowY(Overflow.AUTO); + } else { + outer.getStyle().setOverflowX(Overflow.AUTO); + } + + } else { + if (vertical) { + outer.getStyle().clearOverflowY(); + } else { + outer.getStyle().clearOverflowX(); + } + } + } + + /** + * Gets whether the scroll is activate for this menu. + * + * @since 7.2.6 + * @return true if the scroll is active, otherwise false. + */ + public boolean isScrollActive() { + // Element element = getElement(); + // return element.getOffsetHeight() > DOM.getChild(element, 0) + // .getOffsetHeight(); + int outerHeight = outer.getOffsetHeight(); + int tableHeight = table.getOffsetHeight(); + return outerHeight < tableHeight; + } + + /** + * Gets the preferred height of the menu. + * + * @since 7.2.6 + */ + protected int getPreferredHeight() { + return table.getOffsetHeight(); + } + + /** + * Closes this menu (if it is a popup). + */ + private void close() { + if (parentMenu != null) { + parentMenu.popup.hide(); + } + } + + private MenuItem findItem(Element hItem) { + for (int i = 0; i < items.size(); ++i) { + final MenuItem item = items.get(i); + if (DOM.isOrHasChild(item.getElement(), hItem)) { + return item; + } + } + + return null; + } + + private Element getItemContainerElement() { + if (vertical) { + return body; + } else { + return DOM.getChild(body, 0); + } + } + + /* + * This method is called when a menu bar is hidden, so that it can hide any + * child popups that are currently being shown. + */ + private void onHide() { + if (shownChildMenu != null) { + shownChildMenu.onHide(); + popup.hide(); + } + } + + /* + * This method is called when a menu bar is shown. + */ + private void onShow() { + // Select the first item when a menu is shown. + if (items.size() > 0) { + selectItem(items.get(0)); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/menubar/MenuBarConnector.java b/client/src/main/java/com/vaadin/client/ui/menubar/MenuBarConnector.java new file mode 100644 index 0000000000..03eeb85165 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/menubar/MenuBarConnector.java @@ -0,0 +1,223 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.menubar; + +import java.util.Iterator; +import java.util.Stack; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.Command; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.Paintable; +import com.vaadin.client.TooltipInfo; +import com.vaadin.client.UIDL; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.ImageIcon; +import com.vaadin.client.ui.SimpleManagedLayout; +import com.vaadin.client.ui.VMenuBar; +import com.vaadin.shared.ui.ComponentStateUtil; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.menubar.MenuBarConstants; +import com.vaadin.shared.ui.menubar.MenuBarState; + +@Connect(com.vaadin.ui.MenuBar.class) +public class MenuBarConnector extends AbstractComponentConnector implements + Paintable, SimpleManagedLayout { + + /** + * This method must be implemented to update the client-side component from + * UIDL data received from server. + * + * This method is called when the page is loaded for the first time, and + * every time UI changes in the component are received from the server. + */ + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().htmlContentAllowed = uidl + .hasAttribute(MenuBarConstants.HTML_CONTENT_ALLOWED); + + getWidget().openRootOnHover = uidl + .getBooleanAttribute(MenuBarConstants.OPEN_ROOT_MENU_ON_HOWER); + + getWidget().enabled = isEnabled(); + + // For future connections + getWidget().client = client; + getWidget().uidlId = uidl.getId(); + + // Empty the menu every time it receives new information + if (!getWidget().getItems().isEmpty()) { + getWidget().clearItems(); + } + + UIDL options = uidl.getChildUIDL(0); + + if (null != getState() + && !ComponentStateUtil.isUndefinedWidth(getState())) { + UIDL moreItemUIDL = options.getChildUIDL(0); + StringBuffer itemHTML = new StringBuffer(); + + if (moreItemUIDL.hasAttribute("icon")) { + itemHTML.append("\"\""); + } + + String moreItemText = moreItemUIDL.getStringAttribute("text"); + if ("".equals(moreItemText)) { + moreItemText = "►"; + } + itemHTML.append(moreItemText); + + getWidget().moreItem = GWT.create(VMenuBar.CustomMenuItem.class); + getWidget().moreItem.setHTML(itemHTML.toString()); + getWidget().moreItem.setCommand(VMenuBar.emptyCommand); + + getWidget().collapsedRootItems = new VMenuBar(true, getWidget()); + getWidget().moreItem.setSubMenu(getWidget().collapsedRootItems); + getWidget().moreItem.addStyleName(getWidget().getStylePrimaryName() + + "-more-menuitem"); + } + + UIDL uidlItems = uidl.getChildUIDL(1); + Iterator itr = uidlItems.getChildIterator(); + Stack> iteratorStack = new Stack>(); + Stack menuStack = new Stack(); + VMenuBar currentMenu = getWidget(); + + while (itr.hasNext()) { + UIDL item = (UIDL) itr.next(); + VMenuBar.CustomMenuItem currentItem = null; + + final int itemId = item.getIntAttribute("id"); + + boolean itemHasCommand = item.hasAttribute("command"); + boolean itemIsCheckable = item + .hasAttribute(MenuBarConstants.ATTRIBUTE_CHECKED); + + String itemHTML = getWidget().buildItemHTML(item); + + Command cmd = null; + if (!item.hasAttribute("separator")) { + if (itemHasCommand || itemIsCheckable) { + // Construct a command that fires onMenuClick(int) with the + // item's id-number + cmd = new Command() { + @Override + public void execute() { + getWidget().hostReference.onMenuClick(itemId); + } + }; + } + } + + currentItem = currentMenu.addItem(itemHTML.toString(), cmd); + currentItem.updateFromUIDL(item, client); + + if (item.getChildCount() > 0) { + menuStack.push(currentMenu); + iteratorStack.push(itr); + itr = item.getChildIterator(); + currentMenu = new VMenuBar(true, currentMenu); + client.getVTooltip().connectHandlersToWidget(currentMenu); + // this is the top-level style that also propagates to items - + // any item specific styles are set above in + // currentItem.updateFromUIDL(item, client) + if (ComponentStateUtil.hasStyles(getState())) { + for (String style : getState().styles) { + currentMenu.addStyleDependentName(style); + } + } + currentItem.setSubMenu(currentMenu); + } + + while (!itr.hasNext() && !iteratorStack.empty()) { + boolean hasCheckableItem = false; + for (VMenuBar.CustomMenuItem menuItem : currentMenu.getItems()) { + hasCheckableItem = hasCheckableItem + || menuItem.isCheckable(); + } + if (hasCheckableItem) { + currentMenu.addStyleDependentName("check-column"); + } else { + currentMenu.removeStyleDependentName("check-column"); + } + + itr = iteratorStack.pop(); + currentMenu = menuStack.pop(); + } + }// while + + getLayoutManager().setNeedsHorizontalLayout(this); + + }// updateFromUIDL + + @Override + public VMenuBar getWidget() { + return (VMenuBar) super.getWidget(); + } + + @Override + public MenuBarState getState() { + return (MenuBarState) super.getState(); + } + + @Override + public void layout() { + getWidget().iLayout(); + } + + @Override + public TooltipInfo getTooltipInfo(Element element) { + TooltipInfo info = null; + + // Check content of widget to find tooltip for element + if (element != getWidget().getElement()) { + + VMenuBar.CustomMenuItem item = getWidget().getMenuItemWithElement( + element); + if (item != null) { + info = item.getTooltip(); + } + } + + // Use default tooltip if nothing found from DOM three + if (info == null) { + info = super.getTooltipInfo(element); + } + + return info; + } + + @Override + public boolean hasTooltip() { + /* + * Item tooltips are not processed until updateFromUIDL, so we can't be + * sure that there are no tooltips during onStateChange when this method + * is used. + */ + return true; + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/menubar/MenuItem.java b/client/src/main/java/com/vaadin/client/ui/menubar/MenuItem.java new file mode 100644 index 0000000000..bf2fbf8feb --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/menubar/MenuItem.java @@ -0,0 +1,205 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.menubar; + +/* + * Copyright 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +// COPIED HERE DUE package privates in GWT +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.HasHTML; +import com.google.gwt.user.client.ui.UIObject; + +/** + * A widget that can be placed in a + * {@link com.google.gwt.user.client.ui.MenuBar}. Menu items can either fire a + * {@link com.google.gwt.user.client.Command} when they are clicked, or open a + * cascading sub-menu. + * + * @deprecated + */ +@Deprecated +public class MenuItem extends UIObject implements HasHTML { + + private static final String DEPENDENT_STYLENAME_SELECTED_ITEM = "selected"; + + private Command command; + private MenuBar parentMenu, subMenu; + + /** + * Constructs a new menu item that fires a command when it is selected. + * + * @param text + * the item's text + * @param cmd + * the command to be fired when it is selected + */ + public MenuItem(String text, Command cmd) { + this(text, false); + setCommand(cmd); + } + + /** + * Constructs a new menu item that fires a command when it is selected. + * + * @param text + * the item's text + * @param asHTML + * true to treat the specified text as html + * @param cmd + * the command to be fired when it is selected + */ + public MenuItem(String text, boolean asHTML, Command cmd) { + this(text, asHTML); + setCommand(cmd); + } + + /** + * Constructs a new menu item that cascades to a sub-menu when it is + * selected. + * + * @param text + * the item's text + * @param subMenu + * the sub-menu to be displayed when it is selected + */ + public MenuItem(String text, MenuBar subMenu) { + this(text, false); + setSubMenu(subMenu); + } + + /** + * Constructs a new menu item that cascades to a sub-menu when it is + * selected. + * + * @param text + * the item's text + * @param asHTML + * true to treat the specified text as html + * @param subMenu + * the sub-menu to be displayed when it is selected + */ + public MenuItem(String text, boolean asHTML, MenuBar subMenu) { + this(text, asHTML); + setSubMenu(subMenu); + } + + MenuItem(String text, boolean asHTML) { + setElement(DOM.createTD()); + setSelectionStyle(false); + + if (asHTML) { + setHTML(text); + } else { + setText(text); + } + setStyleName("gwt-MenuItem"); + } + + /** + * Gets the command associated with this item. + * + * @return this item's command, or null if none exists + */ + public Command getCommand() { + return command; + } + + @Override + public String getHTML() { + return DOM.getInnerHTML(getElement()); + } + + /** + * Gets the menu that contains this item. + * + * @return the parent menu, or null if none exists. + */ + public MenuBar getParentMenu() { + return parentMenu; + } + + /** + * Gets the sub-menu associated with this item. + * + * @return this item's sub-menu, or null if none exists + */ + public MenuBar getSubMenu() { + return subMenu; + } + + @Override + public String getText() { + return DOM.getInnerText(getElement()); + } + + /** + * Sets the command associated with this item. + * + * @param cmd + * the command to be associated with this item + */ + public void setCommand(Command cmd) { + command = cmd; + } + + @Override + public void setHTML(String html) { + DOM.setInnerHTML(getElement(), html); + } + + /** + * Sets the sub-menu associated with this item. + * + * @param subMenu + * this item's new sub-menu + */ + public void setSubMenu(MenuBar subMenu) { + this.subMenu = subMenu; + } + + @Override + public void setText(String text) { + DOM.setInnerText(getElement(), text); + } + + void setParentMenu(MenuBar parentMenu) { + this.parentMenu = parentMenu; + } + + void setSelectionStyle(boolean selected) { + if (selected) { + addStyleDependentName(DEPENDENT_STYLENAME_SELECTED_ITEM); + } else { + removeStyleDependentName(DEPENDENT_STYLENAME_SELECTED_ITEM); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java b/client/src/main/java/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java new file mode 100644 index 0000000000..65d4a1eb9b --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java @@ -0,0 +1,98 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.nativebutton; + +import com.google.gwt.user.client.DOM; +import com.vaadin.client.VCaption; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.ConnectorFocusAndBlurHandler; +import com.vaadin.client.ui.Icon; +import com.vaadin.client.ui.VNativeButton; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.button.ButtonServerRpc; +import com.vaadin.shared.ui.button.NativeButtonState; +import com.vaadin.ui.NativeButton; + +@Connect(NativeButton.class) +public class NativeButtonConnector extends AbstractComponentConnector { + + @Override + public void init() { + super.init(); + + getWidget().buttonRpcProxy = getRpcProxy(ButtonServerRpc.class); + getWidget().client = getConnection(); + getWidget().paintableId = getConnectorId(); + + ConnectorFocusAndBlurHandler.addHandlers(this); + } + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().disableOnClick = getState().disableOnClick; + + // Set text + VCaption.setCaptionText(getWidget(), getState()); + + // handle error + if (null != getState().errorMessage) { + if (getWidget().errorIndicatorElement == null) { + getWidget().errorIndicatorElement = DOM.createSpan(); + getWidget().errorIndicatorElement + .setClassName("v-errorindicator"); + } + getWidget().getElement().insertBefore( + getWidget().errorIndicatorElement, + getWidget().captionElement); + + } else if (getWidget().errorIndicatorElement != null) { + getWidget().getElement().removeChild( + getWidget().errorIndicatorElement); + getWidget().errorIndicatorElement = null; + } + + if (getWidget().icon != null) { + getWidget().getElement().removeChild(getWidget().icon.getElement()); + getWidget().icon = null; + } + Icon icon = getIcon(); + if (icon != null) { + getWidget().icon = icon; + getWidget().getElement().insertBefore(icon.getElement(), + getWidget().captionElement); + icon.setAlternateText(getState().iconAltText); + } + + } + + @Override + public VNativeButton getWidget() { + return (VNativeButton) super.getWidget(); + } + + @Override + public NativeButtonState getState() { + return (NativeButtonState) super.getState(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java b/client/src/main/java/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java new file mode 100644 index 0000000000..d6ff2015b4 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.nativeselect; + +import com.vaadin.client.ui.ConnectorFocusAndBlurHandler; +import com.vaadin.client.ui.VNativeSelect; +import com.vaadin.client.ui.optiongroup.OptionGroupBaseConnector; +import com.vaadin.shared.ui.Connect; +import com.vaadin.ui.NativeSelect; + +@Connect(NativeSelect.class) +public class NativeSelectConnector extends OptionGroupBaseConnector { + + @Override + protected void init() { + super.init(); + ConnectorFocusAndBlurHandler.addHandlers(this, getWidget().getSelect()); + } + + @Override + public VNativeSelect getWidget() { + return (VNativeSelect) super.getWidget(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/optiongroup/OptionGroupBaseConnector.java b/client/src/main/java/com/vaadin/client/ui/optiongroup/OptionGroupBaseConnector.java new file mode 100644 index 0000000000..0757bc395b --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/optiongroup/OptionGroupBaseConnector.java @@ -0,0 +1,117 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.optiongroup; + +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.Paintable; +import com.vaadin.client.StyleConstants; +import com.vaadin.client.UIDL; +import com.vaadin.client.ui.AbstractFieldConnector; +import com.vaadin.client.ui.VNativeButton; +import com.vaadin.client.ui.VOptionGroupBase; +import com.vaadin.client.ui.VTextField; +import com.vaadin.shared.ui.select.AbstractSelectState; + +public abstract class OptionGroupBaseConnector extends AbstractFieldConnector + implements Paintable { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + // Save details + getWidget().client = client; + getWidget().paintableId = uidl.getId(); + + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().selectedKeys = uidl.getStringArrayVariableAsSet("selected"); + + getWidget().setReadonly(isReadOnly()); + getWidget().multiselect = "multi".equals(uidl + .getStringAttribute("selectmode")); + getWidget().immediate = getState().immediate; + getWidget().nullSelectionAllowed = uidl + .getBooleanAttribute("nullselect"); + getWidget().nullSelectionItemAvailable = uidl + .getBooleanAttribute("nullselectitem"); + + if (uidl.hasAttribute("cols")) { + getWidget().cols = uidl.getIntAttribute("cols"); + } + if (uidl.hasAttribute("rows")) { + getWidget().rows = uidl.getIntAttribute("rows"); + } + + final UIDL ops = uidl.getChildUIDL(0); + + if (getWidget().getColumns() > 0) { + getWidget().container.setWidth(getWidget().getColumns() + "em"); + if (getWidget().container != getWidget().optionsContainer) { + getWidget().optionsContainer.setWidth("100%"); + } + } + + getWidget().buildOptions(ops); + + if (uidl.getBooleanAttribute("allownewitem")) { + if (getWidget().newItemField == null) { + getWidget().newItemButton = new VNativeButton(); + getWidget().newItemButton.setText("+"); + getWidget().newItemButton.addClickHandler(getWidget()); + getWidget().newItemButton + .addStyleName(StyleConstants.UI_WIDGET); + getWidget().newItemField = new VTextField(); + getWidget().newItemField.client = getConnection(); + getWidget().newItemField.paintableId = getConnectorId(); + getWidget().newItemField.addKeyPressHandler(getWidget()); + getWidget().newItemField.addStyleName(StyleConstants.UI_WIDGET); + + } + getWidget().newItemField.setEnabled(getWidget().isEnabled() + && !getWidget().isReadonly()); + getWidget().newItemButton.setEnabled(getWidget().isEnabled() + && !getWidget().isReadonly()); + + if (getWidget().newItemField == null + || getWidget().newItemField.getParent() != getWidget().container) { + getWidget().container.add(getWidget().newItemField); + getWidget().container.add(getWidget().newItemButton); + final int w = getWidget().container.getOffsetWidth() + - getWidget().newItemButton.getOffsetWidth(); + getWidget().newItemField.setWidth(Math.max(w, 0) + "px"); + } + } else if (getWidget().newItemField != null) { + getWidget().container.remove(getWidget().newItemField); + getWidget().container.remove(getWidget().newItemButton); + } + + getWidget().setTabIndex(getState().tabIndex); + + } + + @Override + public VOptionGroupBase getWidget() { + return (VOptionGroupBase) super.getWidget(); + } + + @Override + public AbstractSelectState getState() { + return (AbstractSelectState) super.getState(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/optiongroup/OptionGroupConnector.java b/client/src/main/java/com/vaadin/client/ui/optiongroup/OptionGroupConnector.java new file mode 100644 index 0000000000..f9bdf455f6 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/optiongroup/OptionGroupConnector.java @@ -0,0 +1,87 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.optiongroup; + +import java.util.ArrayList; + +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.UIDL; +import com.vaadin.client.ui.VOptionGroup; +import com.vaadin.shared.EventId; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.optiongroup.OptionGroupConstants; +import com.vaadin.shared.ui.optiongroup.OptionGroupState; +import com.vaadin.ui.OptionGroup; + +@Connect(OptionGroup.class) +public class OptionGroupConnector extends OptionGroupBaseConnector { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().htmlContentAllowed = uidl + .hasAttribute(OptionGroupConstants.HTML_CONTENT_ALLOWED); + + super.updateFromUIDL(uidl, client); + + getWidget().sendFocusEvents = client.hasEventListeners(this, + EventId.FOCUS); + getWidget().sendBlurEvents = client.hasEventListeners(this, + EventId.BLUR); + + if (getWidget().focusHandlers != null) { + for (HandlerRegistration reg : getWidget().focusHandlers) { + reg.removeHandler(); + } + getWidget().focusHandlers.clear(); + getWidget().focusHandlers = null; + + for (HandlerRegistration reg : getWidget().blurHandlers) { + reg.removeHandler(); + } + getWidget().blurHandlers.clear(); + getWidget().blurHandlers = null; + } + + if (getWidget().sendFocusEvents || getWidget().sendBlurEvents) { + getWidget().focusHandlers = new ArrayList(); + getWidget().blurHandlers = new ArrayList(); + + // add focus and blur handlers to checkboxes / radio buttons + for (Widget wid : getWidget().panel) { + if (wid instanceof CheckBox) { + getWidget().focusHandlers.add(((CheckBox) wid) + .addFocusHandler(getWidget())); + getWidget().blurHandlers.add(((CheckBox) wid) + .addBlurHandler(getWidget())); + } + } + } + } + + @Override + public VOptionGroup getWidget() { + return (VOptionGroup) super.getWidget(); + } + + @Override + public OptionGroupState getState() { + return (OptionGroupState) super.getState(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java b/client/src/main/java/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java new file mode 100644 index 0000000000..dbd530dde1 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java @@ -0,0 +1,710 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.orderedlayout; + +import java.util.List; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorHierarchyChangeEvent; +import com.vaadin.client.LayoutManager; +import com.vaadin.client.Profiler; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.TooltipInfo; +import com.vaadin.client.Util; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; +import com.vaadin.client.ui.AbstractFieldConnector; +import com.vaadin.client.ui.AbstractLayoutConnector; +import com.vaadin.client.ui.Icon; +import com.vaadin.client.ui.LayoutClickEventHandler; +import com.vaadin.client.ui.aria.AriaHelper; +import com.vaadin.client.ui.layout.ElementResizeEvent; +import com.vaadin.client.ui.layout.ElementResizeListener; +import com.vaadin.shared.AbstractFieldState; +import com.vaadin.shared.ComponentConstants; +import com.vaadin.shared.communication.URLReference; +import com.vaadin.shared.ui.AlignmentInfo; +import com.vaadin.shared.ui.LayoutClickRpc; +import com.vaadin.shared.ui.MarginInfo; +import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutServerRpc; +import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutState; + +/** + * Base class for vertical and horizontal ordered layouts + */ +public abstract class AbstractOrderedLayoutConnector extends + AbstractLayoutConnector { + + /* + * Handlers & Listeners + */ + + private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( + this) { + + @Override + protected ComponentConnector getChildComponent( + com.google.gwt.user.client.Element element) { + return Util.getConnectorForElement(getConnection(), getWidget(), + element); + } + + @Override + protected LayoutClickRpc getLayoutClickRPC() { + return getRpcProxy(AbstractOrderedLayoutServerRpc.class); + } + }; + + private StateChangeHandler childStateChangeHandler = new StateChangeHandler() { + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + // Child state has changed, update stuff it hasn't already been done + updateInternalState(); + + /* + * Some changes must always be done after each child's own state + * change handler has been run because it might have changed some + * styles that are overridden here. + */ + ServerConnector child = stateChangeEvent.getConnector(); + if (child instanceof ComponentConnector) { + ComponentConnector component = (ComponentConnector) child; + Slot slot = getWidget().getSlot(component.getWidget()); + + slot.setRelativeWidth(component.isRelativeWidth()); + slot.setRelativeHeight(component.isRelativeHeight()); + } + } + }; + + private ElementResizeListener slotCaptionResizeListener = new ElementResizeListener() { + @Override + public void onElementResize(ElementResizeEvent e) { + + // Get all needed element references + Element captionElement = e.getElement(); + + // Caption position determines if the widget element is the first or + // last child inside the caption wrap + CaptionPosition pos = getWidget().getCaptionPositionFromElement( + captionElement.getParentElement()); + + // The default is the last child + Element widgetElement = captionElement.getParentElement() + .getLastChild().cast(); + + // ...but if caption position is bottom or right, the widget is the + // first child + if (pos == CaptionPosition.BOTTOM || pos == CaptionPosition.RIGHT) { + widgetElement = captionElement.getParentElement() + .getFirstChildElement().cast(); + } + + if (captionElement == widgetElement) { + // Caption element already detached + Slot slot = getWidget().getSlot(widgetElement); + if (slot != null) { + slot.setCaptionResizeListener(null); + } + return; + } + + String widgetWidth = widgetElement.getStyle().getWidth(); + String widgetHeight = widgetElement.getStyle().getHeight(); + + if (widgetHeight.endsWith("%") + && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { + getWidget().updateCaptionOffset(captionElement); + } else if (widgetWidth.endsWith("%") + && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) { + getWidget().updateCaptionOffset(captionElement); + } + + updateLayoutHeight(); + + if (needsExpand()) { + getWidget().updateExpandCompensation(); + } + } + }; + + private ElementResizeListener childComponentResizeListener = new ElementResizeListener() { + @Override + public void onElementResize(ElementResizeEvent e) { + updateLayoutHeight(); + if (needsExpand()) { + getWidget().updateExpandCompensation(); + } + } + }; + + private ElementResizeListener spacingResizeListener = new ElementResizeListener() { + @Override + public void onElementResize(ElementResizeEvent e) { + if (needsExpand()) { + getWidget().updateExpandCompensation(); + } + } + }; + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ui.AbstractComponentConnector#init() + */ + @Override + public void init() { + super.init(); + getWidget().setLayoutManager(getLayoutManager()); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ui.AbstractLayoutConnector#getState() + */ + @Override + public AbstractOrderedLayoutState getState() { + return (AbstractOrderedLayoutState) super.getState(); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ui.AbstractComponentConnector#getWidget() + */ + @Override + public VAbstractOrderedLayout getWidget() { + return (VAbstractOrderedLayout) super.getWidget(); + } + + /** + * Keep track of whether any child has relative height. Used to determine + * whether measurements are needed to make relative child heights work + * together with undefined container height. + */ + private boolean hasChildrenWithRelativeHeight = false; + + /** + * Keep track of whether any child has relative width. Used to determine + * whether measurements are needed to make relative child widths work + * together with undefined container width. + */ + private boolean hasChildrenWithRelativeWidth = false; + + /** + * Keep track of whether any child is middle aligned. Used to determine if + * measurements are needed to make middle aligned children work. + */ + private boolean hasChildrenWithMiddleAlignment = false; + + /** + * Keeps track of whether slots should be expanded based on available space. + */ + private boolean needsExpand = false; + + /** + * The id of the previous response for which state changes have been + * processed. If this is the same as the + * {@link ApplicationConnection#getLastSeenServerSyncId()}, it means that we can + * skip some quite expensive calculations because we know that the state + * hasn't changed since the last time the values were calculated. + */ + private int processedResponseId = -1; + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.HasComponentsConnector#updateCaption(com.vaadin + * .client.ComponentConnector) + */ + @Override + public void updateCaption(ComponentConnector connector) { + /* + * Don't directly update captions here to avoid calling e.g. + * updateLayoutHeight() before everything is initialized. + * updateInternalState() will ensure all captions are updated when + * appropriate. + */ + updateInternalState(); + } + + private void updateCaptionInternal(ComponentConnector child) { + Slot slot = getWidget().getSlot(child.getWidget()); + + String caption = child.getState().caption; + URLReference iconUrl = child.getState().resources + .get(ComponentConstants.ICON_RESOURCE); + String iconUrlString = iconUrl != null ? iconUrl.getURL() : null; + Icon icon = child.getConnection().getIcon(iconUrlString); + + List styles = child.getState().styles; + String error = child.getState().errorMessage; + boolean showError = error != null; + if (child.getState() instanceof AbstractFieldState) { + AbstractFieldState abstractFieldState = (AbstractFieldState) child + .getState(); + showError = showError && !abstractFieldState.hideErrors; + } + boolean required = false; + if (child instanceof AbstractFieldConnector) { + required = ((AbstractFieldConnector) child).isRequired(); + } + boolean enabled = child.isEnabled(); + + if (slot.hasCaption() && null == caption) { + slot.setCaptionResizeListener(null); + } + + slot.setCaption(caption, icon, styles, error, showError, required, + enabled, child.getState().captionAsHtml); + + AriaHelper.handleInputRequired(child.getWidget(), required); + AriaHelper.handleInputInvalid(child.getWidget(), showError); + AriaHelper.bindCaption(child.getWidget(), slot.getCaptionElement()); + + if (slot.hasCaption()) { + CaptionPosition pos = slot.getCaptionPosition(); + slot.setCaptionResizeListener(slotCaptionResizeListener); + if (child.isRelativeHeight() + && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { + getWidget().updateCaptionOffset(slot.getCaptionElement()); + } else if (child.isRelativeWidth() + && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) { + getWidget().updateCaptionOffset(slot.getCaptionElement()); + } + } + + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ui.AbstractComponentContainerConnector# + * onConnectorHierarchyChange + * (com.vaadin.client.ConnectorHierarchyChangeEvent) + */ + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + Profiler.enter("AOLC.onConnectorHierarchyChange"); + + List previousChildren = event.getOldChildren(); + int currentIndex = 0; + VAbstractOrderedLayout layout = getWidget(); + + // remove spacing as it is exists as separate elements that cannot be + // removed easily after reordering the contents + Profiler.enter("AOLC.onConnectorHierarchyChange temporarily remove spacing"); + layout.setSpacing(false); + Profiler.leave("AOLC.onConnectorHierarchyChange temporarily remove spacing"); + + for (ComponentConnector child : getChildComponents()) { + Profiler.enter("AOLC.onConnectorHierarchyChange add children"); + Slot slot = layout.getSlot(child.getWidget()); + if (slot.getParent() != layout) { + Profiler.enter("AOLC.onConnectorHierarchyChange add state change handler"); + child.addStateChangeHandler(childStateChangeHandler); + Profiler.leave("AOLC.onConnectorHierarchyChange add state change handler"); + } + Profiler.enter("AOLC.onConnectorHierarchyChange addOrMoveSlot"); + layout.addOrMoveSlot(slot, currentIndex++, false); + Profiler.leave("AOLC.onConnectorHierarchyChange addOrMoveSlot"); + + Profiler.leave("AOLC.onConnectorHierarchyChange add children"); + } + + // re-add spacing for the elements that should have it + Profiler.enter("AOLC.onConnectorHierarchyChange setSpacing"); + // spacings were removed above + if (getState().spacing) { + layout.setSpacing(true); + } + Profiler.leave("AOLC.onConnectorHierarchyChange setSpacing"); + + for (ComponentConnector child : previousChildren) { + Profiler.enter("AOLC.onConnectorHierarchyChange remove children"); + if (child.getParent() != this) { + Slot slot = layout.getSlot(child.getWidget()); + slot.setWidgetResizeListener(null); + if (slot.hasCaption()) { + slot.setCaptionResizeListener(null); + } + slot.setSpacingResizeListener(null); + child.removeStateChangeHandler(childStateChangeHandler); + layout.removeWidget(child.getWidget()); + } + Profiler.leave("AOLC.onConnectorHierarchyChange remove children"); + } + Profiler.leave("AOLC.onConnectorHierarchyChange"); + + updateInternalState(); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.client.ui.AbstractComponentConnector#onStateChanged(com.vaadin + * .client.communication.StateChangeEvent) + */ + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + clickEventHandler.handleEventHandlerRegistration(); + getWidget().setMargin(new MarginInfo(getState().marginsBitmask)); + getWidget().setSpacing(getState().spacing); + + updateInternalState(); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.client.ui.AbstractComponentConnector#getTooltipInfo(com.google + * .gwt.dom.client.Element) + */ + @Override + public TooltipInfo getTooltipInfo(com.google.gwt.dom.client.Element element) { + if (element != getWidget().getElement()) { + Slot slot = WidgetUtil.findWidget(element, Slot.class); + if (slot != null && slot.getCaptionElement() != null + && slot.getParent() == getWidget() + && slot.getCaptionElement().isOrHasChild(element)) { + ComponentConnector connector = Util.findConnectorFor(slot + .getWidget()); + if (connector != null) { + return connector.getTooltipInfo(element); + } + } + } + return super.getTooltipInfo(element); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ui.AbstractComponentConnector#hasTooltip() + */ + @Override + public boolean hasTooltip() { + /* + * Tooltips are fetched from child connectors -> there's no quick way of + * checking whether there might a tooltip hiding somewhere + */ + return true; + } + + /** + * Updates DOM properties and listeners based on the current state of this + * layout and its children. + */ + private void updateInternalState() { + // Avoid updating again for the same data + int lastResponseId = getConnection().getLastSeenServerSyncId(); + if (processedResponseId == lastResponseId) { + return; + } + Profiler.enter("AOLC.updateInternalState"); + // Remember that everything is updated for this response + processedResponseId = lastResponseId; + + hasChildrenWithRelativeHeight = false; + hasChildrenWithRelativeWidth = false; + + hasChildrenWithMiddleAlignment = false; + + needsExpand = getWidget().vertical ? !isUndefinedHeight() + : !isUndefinedWidth(); + + boolean onlyZeroExpands = true; + if (needsExpand) { + for (ComponentConnector child : getChildComponents()) { + double expandRatio = getState().childData.get(child).expandRatio; + if (expandRatio != 0) { + onlyZeroExpands = false; + break; + } + } + } + + // First update bookkeeping for all children + for (ComponentConnector child : getChildComponents()) { + Slot slot = getWidget().getSlot(child.getWidget()); + + slot.setRelativeWidth(child.isRelativeWidth()); + slot.setRelativeHeight(child.isRelativeHeight()); + + if (child.delegateCaptionHandling()) { + updateCaptionInternal(child); + } + + // Update slot style names + List childStyles = child.getState().styles; + if (childStyles == null) { + getWidget().setSlotStyleNames(child.getWidget(), + (String[]) null); + } else { + getWidget().setSlotStyleNames(child.getWidget(), + childStyles.toArray(new String[childStyles.size()])); + } + + AlignmentInfo alignment = new AlignmentInfo( + getState().childData.get(child).alignmentBitmask); + slot.setAlignment(alignment); + + if (alignment.isVerticalCenter()) { + hasChildrenWithMiddleAlignment = true; + } + + double expandRatio = onlyZeroExpands ? 1 : getState().childData + .get(child).expandRatio; + + slot.setExpandRatio(expandRatio); + + if (child.isRelativeHeight()) { + hasChildrenWithRelativeHeight = true; + } + if (child.isRelativeWidth()) { + hasChildrenWithRelativeWidth = true; + } + } + + if (needsFixedHeight()) { + // Add resize listener to ensure the widget itself is measured + getLayoutManager().addElementResizeListener( + getWidget().getElement(), childComponentResizeListener); + } else { + getLayoutManager().removeElementResizeListener( + getWidget().getElement(), childComponentResizeListener); + } + + // Then update listeners based on bookkeeping + updateAllSlotListeners(); + + // Update the layout at this point to ensure it's OK even if we get no + // element resize events + updateLayoutHeight(); + if (needsExpand()) { + getWidget().updateExpandedSizes(); + // updateExpandedSizes causes fixed size components to temporarily + // lose their size. updateExpandCompensation must be delayed until + // the browser has a chance to measure them. + Scheduler.get().scheduleFinally(new ScheduledCommand() { + @Override + public void execute() { + getWidget().updateExpandCompensation(); + } + }); + } else { + getWidget().clearExpand(); + } + + Profiler.leave("AOLC.updateInternalState"); + } + + /** + * Does the layout need a fixed height? + */ + private boolean needsFixedHeight() { + boolean isVertical = getWidget().vertical; + + if (isVertical) { + // Doesn't need height fix for vertical layouts + return false; + } + + else if (!isUndefinedHeight()) { + // Fix not needed unless the height is undefined + return false; + } + + else if (!hasChildrenWithRelativeHeight + && !hasChildrenWithMiddleAlignment) { + // Already works if there are no relative heights or middle aligned + // children + return false; + } + + return true; + } + + /** + * Does the layout need to expand? + */ + private boolean needsExpand() { + return needsExpand; + } + + /** + * Add slot listeners + */ + private void updateAllSlotListeners() { + for (ComponentConnector child : getChildComponents()) { + updateSlotListeners(child); + } + } + + /** + * Add/remove necessary ElementResizeListeners for one slot. This should be + * called after each update to the slot's or it's widget. + */ + private void updateSlotListeners(ComponentConnector child) { + Slot slot = getWidget().getSlot(child.getWidget()); + + // Clear all possible listeners first + slot.setWidgetResizeListener(null); + if (slot.hasCaption()) { + slot.setCaptionResizeListener(null); + } + if (slot.hasSpacing()) { + slot.setSpacingResizeListener(null); + } + + // Add all necessary listeners + if (needsFixedHeight()) { + slot.setWidgetResizeListener(childComponentResizeListener); + if (slot.hasCaption()) { + slot.setCaptionResizeListener(slotCaptionResizeListener); + } + } else if ((hasChildrenWithRelativeHeight || hasChildrenWithRelativeWidth) + && slot.hasCaption()) { + /* + * If the slot has caption, we need to listen for its size changes + * in order to update the padding/margin offset for relative sized + * components. + * + * TODO might only be needed if the caption is in the same direction + * as the relative size? + */ + slot.setCaptionResizeListener(slotCaptionResizeListener); + } + + if (needsExpand()) { + // TODO widget resize only be needed for children without expand? + slot.setWidgetResizeListener(childComponentResizeListener); + if (slot.hasSpacing()) { + slot.setSpacingResizeListener(spacingResizeListener); + } + } + } + + /** + * Re-calculate the layout height + */ + private void updateLayoutHeight() { + if (needsFixedHeight()) { + int h = getMaxHeight(); + if (h < 0) { + // Postpone change if there are elements that have not yet been + // measured + return; + } + h += getLayoutManager().getBorderHeight(getWidget().getElement()) + + getLayoutManager().getPaddingHeight( + getWidget().getElement()); + getWidget().getElement().getStyle().setHeight(h, Unit.PX); + getLayoutManager().setNeedsMeasure(this); + } + } + + /** + * Measures the maximum height of the layout in pixels + */ + private int getMaxHeight() { + int highestNonRelative = -1; + int highestRelative = -1; + + LayoutManager layoutManager = getLayoutManager(); + + for (ComponentConnector child : getChildComponents()) { + Widget childWidget = child.getWidget(); + Slot slot = getWidget().getSlot(childWidget); + Element captionElement = slot.getCaptionElement(); + CaptionPosition captionPosition = slot.getCaptionPosition(); + + int pixelHeight = layoutManager.getOuterHeight(childWidget + .getElement()); + if (pixelHeight == -1) { + // Height has not yet been measured -> postpone actions that + // depend on the max height + return -1; + } + + boolean hasRelativeHeight = slot.hasRelativeHeight(); + + boolean captionSizeShouldBeAddedtoComponentHeight = captionPosition == CaptionPosition.TOP + || captionPosition == CaptionPosition.BOTTOM; + boolean includeCaptionHeight = captionElement != null + && captionSizeShouldBeAddedtoComponentHeight; + + if (includeCaptionHeight) { + int captionHeight = layoutManager + .getOuterHeight(captionElement) + - getLayoutManager().getMarginHeight(captionElement); + if (captionHeight == -1) { + // Height has not yet been measured -> postpone actions that + // depend on the max height + return -1; + } + pixelHeight += captionHeight; + } + + if (!hasRelativeHeight) { + if (pixelHeight > highestNonRelative) { + highestNonRelative = pixelHeight; + } + } else { + if (pixelHeight > highestRelative) { + highestRelative = pixelHeight; + } + } + } + return highestNonRelative > -1 ? highestNonRelative : highestRelative; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ui.AbstractComponentConnector#onUnregister() + */ + @Override + public void onUnregister() { + // Cleanup all ElementResizeListeners + for (ComponentConnector child : getChildComponents()) { + Slot slot = getWidget().getSlot(child.getWidget()); + if (slot.hasCaption()) { + slot.setCaptionResizeListener(null); + } + + if (slot.getSpacingElement() != null) { + slot.setSpacingResizeListener(null); + } + + slot.setWidgetResizeListener(null); + } + + super.onUnregister(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/orderedlayout/CaptionPosition.java b/client/src/main/java/com/vaadin/client/ui/orderedlayout/CaptionPosition.java new file mode 100644 index 0000000000..885dc1ecd7 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/orderedlayout/CaptionPosition.java @@ -0,0 +1,24 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.orderedlayout; + +/** + * Defines where the caption should be placed + */ +public enum CaptionPosition { + TOP, RIGHT, BOTTOM, LEFT +} diff --git a/client/src/main/java/com/vaadin/client/ui/orderedlayout/HorizontalLayoutConnector.java b/client/src/main/java/com/vaadin/client/ui/orderedlayout/HorizontalLayoutConnector.java new file mode 100644 index 0000000000..2cd1acd78b --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/orderedlayout/HorizontalLayoutConnector.java @@ -0,0 +1,48 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.orderedlayout; + +import com.vaadin.client.ui.VHorizontalLayout; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.Connect.LoadStyle; +import com.vaadin.shared.ui.orderedlayout.HorizontalLayoutState; +import com.vaadin.ui.HorizontalLayout; + +/** + * Connects the client widget {@link VHorizontalLayout} with the Vaadin server + * side counterpart {@link HorizontalLayout} + */ +@Connect(value = HorizontalLayout.class, loadStyle = LoadStyle.EAGER) +public class HorizontalLayoutConnector extends AbstractOrderedLayoutConnector { + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.client.ui.orderedlayout.AbstractOrderedLayoutConnector#getWidget + * () + */ + @Override + public VHorizontalLayout getWidget() { + return (VHorizontalLayout) super.getWidget(); + } + + @Override + public HorizontalLayoutState getState() { + return (HorizontalLayoutState) super.getState(); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/orderedlayout/Slot.java b/client/src/main/java/com/vaadin/client/ui/orderedlayout/Slot.java new file mode 100644 index 0000000000..564c15472f --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/orderedlayout/Slot.java @@ -0,0 +1,832 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.orderedlayout; + +import java.util.List; + +import com.google.gwt.aria.client.Roles; +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.LayoutManager; +import com.vaadin.client.StyleConstants; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.FontIcon; +import com.vaadin.client.ui.Icon; +import com.vaadin.client.ui.ImageIcon; +import com.vaadin.client.ui.layout.ElementResizeEvent; +import com.vaadin.client.ui.layout.ElementResizeListener; +import com.vaadin.shared.ui.AlignmentInfo; + +/** + * Represents a slot which contains the actual widget in the layout. + */ +public class Slot extends SimplePanel { + + private static final String ALIGN_CLASS_PREFIX = "v-align-"; + + // this must be set at construction time and not changed afterwards + private VAbstractOrderedLayout layout; + + public static final String SLOT_CLASSNAME = "v-slot"; + + private Element spacer; + private Element captionWrap; + private Element caption; + private Element captionText; + private Icon icon; + private Element errorIcon; + private Element requiredIcon; + + private ElementResizeListener captionResizeListener; + + private ElementResizeListener widgetResizeListener; + + private ElementResizeListener spacingResizeListener; + + /* + * This listener is applied only in IE8 to workaround browser issue where + * IE8 forgets to update the error indicator position when the slot gets + * resized by widget resizing itself. #11693 + */ + private ElementResizeListener ie8CaptionElementResizeUpdateListener = new ElementResizeListener() { + + @Override + public void onElementResize(ElementResizeEvent e) { + Element caption = getCaptionElement(); + if (caption != null) { + WidgetUtil.forceIE8Redraw(caption); + } + } + }; + + // Caption is placed after component unless there is some part which + // moves it above. + private CaptionPosition captionPosition = CaptionPosition.RIGHT; + + private AlignmentInfo alignment; + + private double expandRatio = -1; + + /** + * Constructs a slot. + * + * When using this constructor, the layout and widget must be set before any + * other operations are performed on the slot. + * + * @since 7.6 + */ + public Slot() { + setStyleName(SLOT_CLASSNAME); + } + + /** + * Set the layout in which this slot is. This method must be called exactly + * once at slot construction time when using the default constructor. + * + * The method should normally only be called by + * {@link VAbstractOrderedLayout#createSlot(Widget)}. + * + * @since 7.6 + * @param layout + * the layout containing the slot + */ + public void setLayout(VAbstractOrderedLayout layout) { + this.layout = layout; + } + + /** + * Constructs a slot. + * + * @param layout + * The layout to which this slot belongs + * @param widget + * The widget to put in the slot + * @deprecated use {@link GWT#create(Class)}, {@link #setWidget(Widget)} and + * {@link #setLayout(VAbstractOrderedLayout)} instead + */ + @Deprecated + public Slot(VAbstractOrderedLayout layout, Widget widget) { + setLayout(layout); + setStyleName(SLOT_CLASSNAME); + setWidget(widget); + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.SimplePanel#remove(com.google.gwt.user + * .client.ui.Widget) + */ + @Override + public boolean remove(Widget w) { + detachListeners(); + return super.remove(w); + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.SimplePanel#setWidget(com.google.gwt + * .user.client.ui.Widget) + */ + @Override + public void setWidget(Widget w) { + detachListeners(); + super.setWidget(w); + attachListeners(); + } + + /** + * Attaches resize listeners to the widget, caption and spacing elements + */ + private void attachListeners() { + if (getWidget() != null && layout.getLayoutManager() != null) { + LayoutManager lm = layout.getLayoutManager(); + if (getCaptionElement() != null && captionResizeListener != null) { + lm.addElementResizeListener(getCaptionElement(), + captionResizeListener); + } + if (widgetResizeListener != null) { + lm.addElementResizeListener(getWidget().getElement(), + widgetResizeListener); + } + if (getSpacingElement() != null && spacingResizeListener != null) { + lm.addElementResizeListener(getSpacingElement(), + spacingResizeListener); + } + + if (BrowserInfo.get().isIE8()) { + lm.addElementResizeListener(getWidget().getElement(), + ie8CaptionElementResizeUpdateListener); + } + } + } + + /** + * Detaches resize listeners from the widget, caption and spacing elements + */ + private void detachListeners() { + if (getWidget() != null && layout.getLayoutManager() != null) { + LayoutManager lm = layout.getLayoutManager(); + if (getCaptionElement() != null && captionResizeListener != null) { + lm.removeElementResizeListener(getCaptionElement(), + captionResizeListener); + } + if (widgetResizeListener != null) { + lm.removeElementResizeListener(getWidget().getElement(), + widgetResizeListener); + } + // in many cases, the listener has already been removed by + // setSpacing(false) + if (getSpacingElement() != null && spacingResizeListener != null) { + lm.removeElementResizeListener(getSpacingElement(), + spacingResizeListener); + } + + if (BrowserInfo.get().isIE8()) { + lm.removeElementResizeListener(getWidget().getElement(), + ie8CaptionElementResizeUpdateListener); + } + } + } + + public ElementResizeListener getCaptionResizeListener() { + return captionResizeListener; + } + + public void setCaptionResizeListener( + ElementResizeListener captionResizeListener) { + detachListeners(); + this.captionResizeListener = captionResizeListener; + attachListeners(); + } + + public ElementResizeListener getWidgetResizeListener() { + return widgetResizeListener; + } + + public void setWidgetResizeListener( + ElementResizeListener widgetResizeListener) { + detachListeners(); + this.widgetResizeListener = widgetResizeListener; + attachListeners(); + } + + public ElementResizeListener getSpacingResizeListener() { + return spacingResizeListener; + } + + public void setSpacingResizeListener( + ElementResizeListener spacingResizeListener) { + detachListeners(); + this.spacingResizeListener = spacingResizeListener; + attachListeners(); + } + + /** + * Returns the alignment for the slot + * + */ + public AlignmentInfo getAlignment() { + return alignment; + } + + /** + * Sets the style names for the slot containing the widget + * + * @param stylenames + * The style names for the slot + */ + protected void setStyleNames(String... stylenames) { + setStyleName(SLOT_CLASSNAME); + if (stylenames != null) { + for (String stylename : stylenames) { + addStyleDependentName(stylename); + } + } + + // Ensure alignment style names are correct + setAlignment(alignment); + } + + /** + * Sets how the widget is aligned inside the slot + * + * @param alignment + * The alignment inside the slot + */ + public void setAlignment(AlignmentInfo alignment) { + this.alignment = alignment; + + if (alignment != null && alignment.isHorizontalCenter()) { + addStyleName(ALIGN_CLASS_PREFIX + "center"); + removeStyleName(ALIGN_CLASS_PREFIX + "right"); + } else if (alignment != null && alignment.isRight()) { + addStyleName(ALIGN_CLASS_PREFIX + "right"); + removeStyleName(ALIGN_CLASS_PREFIX + "center"); + } else { + removeStyleName(ALIGN_CLASS_PREFIX + "right"); + removeStyleName(ALIGN_CLASS_PREFIX + "center"); + } + + if (alignment != null && alignment.isVerticalCenter()) { + addStyleName(ALIGN_CLASS_PREFIX + "middle"); + removeStyleName(ALIGN_CLASS_PREFIX + "bottom"); + } else if (alignment != null && alignment.isBottom()) { + addStyleName(ALIGN_CLASS_PREFIX + "bottom"); + removeStyleName(ALIGN_CLASS_PREFIX + "middle"); + } else { + removeStyleName(ALIGN_CLASS_PREFIX + "middle"); + removeStyleName(ALIGN_CLASS_PREFIX + "bottom"); + } + } + + /** + * Set how the slot should be expanded relative to the other slots. 0 means + * that the slot should not participate in the division of space based on + * the expand ratios but instead be allocated space based on its natural + * size. Other values causes the slot to get a share of the otherwise + * unallocated space in proportion to the slot's expand ratio value. + * + * @param expandRatio + * The ratio of the space the slot should occupy + * + */ + public void setExpandRatio(double expandRatio) { + this.expandRatio = expandRatio; + } + + /** + * Get the expand ratio for the slot. The expand ratio describes how the + * slot should be resized compared to other slots in the layout + * + * @return the expand ratio of the slot + * + * @see #setExpandRatio(double) + */ + public double getExpandRatio() { + return expandRatio; + } + + /** + * Set the spacing for the slot. The spacing determines if there should be + * empty space around the slot when the slot. + * + * @param spacing + * Should spacing be enabled + */ + public void setSpacing(boolean spacing) { + if (spacing && spacer == null) { + spacer = DOM.createDiv(); + spacer.addClassName("v-spacing"); + + /* + * This has to be done here for the initial render. In other cases + * where the spacer already exists onAttach will handle it. + */ + getElement().getParentElement().insertBefore(spacer, getElement()); + } else if (!spacing && spacer != null) { + // Remove listener before spacer to avoid memory leak + LayoutManager lm = layout.getLayoutManager(); + if (lm != null && spacingResizeListener != null) { + lm.removeElementResizeListener(spacer, spacingResizeListener); + } + + spacer.removeFromParent(); + spacer = null; + } + } + + /** + * Get the element which is added to make the spacing + * + * @return + */ + public com.google.gwt.user.client.Element getSpacingElement() { + return DOM.asOld(spacer); + } + + /** + * Does the slot have spacing + */ + public boolean hasSpacing() { + return getSpacingElement() != null; + } + + /** + * Get the vertical amount in pixels of the spacing + */ + protected int getVerticalSpacing() { + if (spacer == null) { + return 0; + } else if (layout.getLayoutManager() != null) { + return layout.getLayoutManager().getOuterHeight(spacer); + } + return spacer.getOffsetHeight(); + } + + /** + * Get the horizontal amount of pixels of the spacing + * + * @return + */ + protected int getHorizontalSpacing() { + if (spacer == null) { + return 0; + } else if (layout.getLayoutManager() != null) { + return layout.getLayoutManager().getOuterWidth(spacer); + } + return spacer.getOffsetWidth(); + } + + /** + * Set the position of the caption relative to the slot + * + * @param captionPosition + * The position of the caption + */ + public void setCaptionPosition(CaptionPosition captionPosition) { + if (caption == null) { + return; + } + captionWrap.removeClassName("v-caption-on-" + + this.captionPosition.name().toLowerCase()); + + this.captionPosition = captionPosition; + if (captionPosition == CaptionPosition.BOTTOM + || captionPosition == CaptionPosition.RIGHT) { + captionWrap.appendChild(caption); + } else { + captionWrap.insertFirst(caption); + } + + captionWrap.addClassName("v-caption-on-" + + captionPosition.name().toLowerCase()); + } + + /** + * Get the position of the caption relative to the slot + */ + public CaptionPosition getCaptionPosition() { + return captionPosition; + } + + /** + * Set the caption of the slot + * + * @param captionText + * The text of the caption + * @param iconUrl + * The icon URL, must already be run trough translateVaadinUri() + * @param styles + * The style names + * @param error + * The error message + * @param showError + * Should the error message be shown + * @param required + * Is the (field) required + * @param enabled + * Is the component enabled + * + * @deprecated Use + * {@link #setCaption(String, Icon, List, String, boolean, boolean, boolean)} + * instead + */ + @Deprecated + public void setCaption(String captionText, String iconUrl, + List styles, String error, boolean showError, + boolean required, boolean enabled) { + Icon icon; + if (FontIcon.isFontIconUri(iconUrl)) { + icon = GWT.create(FontIcon.class); + } else { + icon = GWT.create(ImageIcon.class); + } + icon.setUri(iconUrl); + + setCaption(captionText, icon, styles, error, showError, required, + enabled); + } + + /** + * Set the caption of the slot as text + * + * @param captionText + * The text of the caption + * @param icon + * The icon + * @param styles + * The style names + * @param error + * The error message + * @param showError + * Should the error message be shown + * @param required + * Is the (field) required + * @param enabled + * Is the component enabled + */ + public void setCaption(String captionText, Icon icon, List styles, + String error, boolean showError, boolean required, boolean enabled) { + setCaption(captionText, icon, styles, error, showError, required, + enabled, false); + } + + /** + * Set the caption of the slot + * + * @param captionText + * The text of the caption + * @param icon + * The icon + * @param styles + * The style names + * @param error + * The error message + * @param showError + * Should the error message be shown + * @param required + * Is the (field) required + * @param enabled + * Is the component enabled + * @param captionAsHtml + * true if the caption should be rendered as HTML, false + * otherwise + */ + public void setCaption(String captionText, Icon icon, List styles, + String error, boolean showError, boolean required, boolean enabled, + boolean captionAsHtml) { + + // TODO place for optimization: check if any of these have changed + // since last time, and only run those changes + + // Caption wrappers + Widget widget = getWidget(); + final Element focusedElement = WidgetUtil.getFocusedElement(); + // By default focus will not be lost + boolean focusLost = false; + if (captionText != null || icon != null || error != null || required) { + if (caption == null) { + caption = DOM.createDiv(); + captionWrap = DOM.createDiv(); + captionWrap.addClassName(StyleConstants.UI_WIDGET); + captionWrap.addClassName("v-has-caption"); + getElement().appendChild(captionWrap); + orphan(widget); + captionWrap.appendChild(widget.getElement()); + adopt(widget); + + // Made changes to DOM. Focus can be lost if it was in the + // widget. + focusLost = (focusedElement == null ? false : widget + .getElement().isOrHasChild(focusedElement)); + } + } else if (caption != null) { + orphan(widget); + getElement().appendChild(widget.getElement()); + adopt(widget); + captionWrap.removeFromParent(); + caption = null; + captionWrap = null; + + // Made changes to DOM. Focus can be lost if it was in the widget. + focusLost = (focusedElement == null ? false : widget.getElement() + .isOrHasChild(focusedElement)); + } + + // Caption text + if (captionText != null) { + if (this.captionText == null) { + this.captionText = DOM.createSpan(); + this.captionText.addClassName("v-captiontext"); + caption.appendChild(this.captionText); + } + if (captionText.trim().equals("")) { + this.captionText.setInnerHTML(" "); + } else { + if (captionAsHtml) { + this.captionText.setInnerHTML(captionText); + } else { + this.captionText.setInnerText(captionText); + } + } + } else if (this.captionText != null) { + this.captionText.removeFromParent(); + this.captionText = null; + } + + // Icon + if (this.icon != null) { + this.icon.getElement().removeFromParent(); + } + if (icon != null) { + caption.insertFirst(icon.getElement()); + } + this.icon = icon; + + // Required + if (required) { + if (requiredIcon == null) { + requiredIcon = DOM.createSpan(); + // TODO decide something better (e.g. use CSS to insert the + // character) + requiredIcon.setInnerHTML("*"); + requiredIcon.setClassName("v-required-field-indicator"); + + // The star should not be read by the screen reader, as it is + // purely visual. Required state is set at the element level for + // the screen reader. + Roles.getTextboxRole().setAriaHiddenState(requiredIcon, true); + } + caption.appendChild(requiredIcon); + } else if (requiredIcon != null) { + requiredIcon.removeFromParent(); + requiredIcon = null; + } + + // Error + if (error != null && showError) { + if (errorIcon == null) { + errorIcon = DOM.createSpan(); + errorIcon.setClassName("v-errorindicator"); + } + caption.appendChild(errorIcon); + } else if (errorIcon != null) { + errorIcon.removeFromParent(); + errorIcon = null; + } + + if (caption != null) { + // Styles + caption.setClassName("v-caption"); + + if (styles != null) { + for (String style : styles) { + caption.addClassName("v-caption-" + style); + } + } + + if (enabled) { + caption.removeClassName("v-disabled"); + } else { + caption.addClassName("v-disabled"); + } + + // Caption position + if (captionText != null || icon != null) { + setCaptionPosition(CaptionPosition.TOP); + } else { + setCaptionPosition(CaptionPosition.RIGHT); + } + } + + if (focusLost) { + // Find out what element is currently focused. + Element currentFocus = WidgetUtil.getFocusedElement(); + if (currentFocus != null + && currentFocus.equals(Document.get().getBody())) { + // Focus has moved to BodyElement and should be moved back to + // original location. This happened because of adding or + // removing the captionWrap + focusedElement.focus(); + } else if (currentFocus != focusedElement) { + // Focus is either moved somewhere else on purpose or IE has + // lost it. Investigate further. + Timer focusTimer = new Timer() { + + @Override + public void run() { + if (WidgetUtil.getFocusedElement() == null) { + // This should never become an infinite loop and + // even if it does it will be stopped once something + // is done with the browser. + schedule(25); + } else if (WidgetUtil.getFocusedElement().equals( + Document.get().getBody())) { + // Focus found it's way to BodyElement. Now it can + // be restored + focusedElement.focus(); + } + } + }; + if (BrowserInfo.get().isIE8()) { + // IE8 can't fix the focus immediately. It will fail. + focusTimer.schedule(25); + } else { + // Newer IE versions can handle things immediately. + focusTimer.run(); + } + } + } + } + + /** + * Does the slot have a caption + */ + public boolean hasCaption() { + return caption != null; + } + + /** + * Get the slots caption element + */ + public com.google.gwt.user.client.Element getCaptionElement() { + return DOM.asOld(caption); + } + + private boolean relativeWidth = false; + + /** + * Set if the slot has a relative width + * + * @param relativeWidth + * True if slot uses relative width, false if the slot has a + * static width + */ + public void setRelativeWidth(boolean relativeWidth) { + this.relativeWidth = relativeWidth; + updateRelativeSize(relativeWidth, "width"); + } + + public boolean hasRelativeWidth() { + return relativeWidth; + } + + private boolean relativeHeight = false; + + /** + * Set if the slot has a relative height + * + * @param relativeHeight + * True if the slot uses a relative height, false if the slot has + * a static height + */ + public void setRelativeHeight(boolean relativeHeight) { + this.relativeHeight = relativeHeight; + updateRelativeSize(relativeHeight, "height"); + } + + public boolean hasRelativeHeight() { + return relativeHeight; + } + + /** + * Updates the captions size if the slot is relative + * + * @param isRelativeSize + * Is the slot relatively sized + * @param direction + * The direction of the caption + */ + private void updateRelativeSize(boolean isRelativeSize, String direction) { + if (isRelativeSize && hasCaption()) { + captionWrap.getStyle().setProperty(direction, + getWidget().getElement().getStyle().getProperty(direction)); + captionWrap.addClassName("v-has-" + direction); + } else if (hasCaption()) { + if (direction.equals("height")) { + captionWrap.getStyle().clearHeight(); + } else { + captionWrap.getStyle().clearWidth(); + } + captionWrap.removeClassName("v-has-" + direction); + captionWrap.getStyle().clearPaddingTop(); + captionWrap.getStyle().clearPaddingRight(); + captionWrap.getStyle().clearPaddingBottom(); + captionWrap.getStyle().clearPaddingLeft(); + caption.getStyle().clearMarginTop(); + caption.getStyle().clearMarginRight(); + caption.getStyle().clearMarginBottom(); + caption.getStyle().clearMarginLeft(); + } + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt + * .user.client.Event) + */ + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (DOM.eventGetType(event) == Event.ONLOAD && icon != null + && icon.getElement() == DOM.eventGetTarget(event)) { + if (layout.getLayoutManager() != null) { + layout.getLayoutManager().layoutLater(); + } else { + layout.updateCaptionOffset(caption); + } + } + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.SimplePanel#getContainerElement() + */ + @Override + protected com.google.gwt.user.client.Element getContainerElement() { + if (captionWrap == null) { + return getElement(); + } else { + return DOM.asOld(captionWrap); + } + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.Widget#onDetach() + */ + @Override + protected void onDetach() { + if (spacer != null) { + spacer.removeFromParent(); + } + super.onDetach(); + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.Widget#onAttach() + */ + @Override + protected void onAttach() { + super.onAttach(); + if (spacer != null) { + getElement().getParentElement().insertBefore(spacer, getElement()); + } + } + + public boolean isRelativeInDirection(boolean vertical) { + if (vertical) { + return hasRelativeHeight(); + } else { + return hasRelativeWidth(); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/orderedlayout/VAbstractOrderedLayout.java b/client/src/main/java/com/vaadin/client/ui/orderedlayout/VAbstractOrderedLayout.java new file mode 100644 index 0000000000..1a36ba9c93 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/orderedlayout/VAbstractOrderedLayout.java @@ -0,0 +1,742 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.orderedlayout; + +import java.util.HashMap; +import java.util.Map; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.regexp.shared.MatchResult; +import com.google.gwt.regexp.shared.RegExp; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.RequiresResize; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.LayoutManager; +import com.vaadin.client.Profiler; +import com.vaadin.client.Util; +import com.vaadin.client.WidgetUtil; +import com.vaadin.shared.ui.MarginInfo; + +/** + * Base class for ordered layouts + */ +public class VAbstractOrderedLayout extends FlowPanel { + + protected boolean spacing = false; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean vertical = true; + + protected boolean definedHeight = false; + + private Map widgetToSlot = new HashMap(); + + private Element expandWrapper; + + private LayoutManager layoutManager; + + /** + * Keep track of the last allocated expand size to help detecting when it + * changes. + */ + private int lastExpandSize = -1; + + public VAbstractOrderedLayout(boolean vertical) { + this.vertical = vertical; + } + + /** + * See the method {@link #addOrMoveSlot(Slot, int, boolean)}. + * + *

+ * This method always adjusts spacings for the whole layout. + * + * @param slot + * The slot to move or add + * @param index + * The index where the slot should be placed. + * @deprecated since 7.1.4, use {@link #addOrMoveSlot(Slot, int, boolean)} + */ + @Deprecated + public void addOrMoveSlot(Slot slot, int index) { + addOrMoveSlot(slot, index, true); + } + + /** + * Add or move a slot to another index. + *

+ * For internal use only. May be removed or replaced in the future. + *

+ * You should note that the index does not refer to the DOM index if + * spacings are used. If spacings are used then the index will be adjusted + * to include the spacings when inserted. + *

+ * For instance when using spacing the index converts to DOM index in the + * following way: + * + *

+     * index : 0 -> DOM index: 0
+     * index : 1 -> DOM index: 1
+     * index : 2 -> DOM index: 3
+     * index : 3 -> DOM index: 5
+     * index : 4 -> DOM index: 7
+     * 
+ * + * When using this method never account for spacings. + *

+ * The caller should remove all spacings before calling this method and + * re-add them (if necessary) after this method. This can be done before and + * after all slots have been added/moved. + *

+ * + * @since 7.1.4 + * + * @param slot + * The slot to move or add + * @param index + * The index where the slot should be placed. + * @param adjustSpacing + * true to recalculate spacings for the whole layout after the + * operation + */ + public void addOrMoveSlot(Slot slot, int index, boolean adjustSpacing) { + Profiler.enter("VAOL.onConnectorHierarchyChange addOrMoveSlot find index"); + if (slot.getParent() == this) { + int currentIndex = getWidgetIndex(slot); + if (index == currentIndex) { + Profiler.leave("VAOL.onConnectorHierarchyChange addOrMoveSlot find index"); + return; + } + } + Profiler.leave("VAOL.onConnectorHierarchyChange addOrMoveSlot find index"); + + Profiler.enter("VAOL.onConnectorHierarchyChange addOrMoveSlot insert"); + insert(slot, index); + Profiler.leave("VAOL.onConnectorHierarchyChange addOrMoveSlot insert"); + + if (adjustSpacing) { + Profiler.enter("VAOL.onConnectorHierarchyChange addOrMoveSlot setSpacing"); + setSpacing(spacing); + Profiler.leave("VAOL.onConnectorHierarchyChange addOrMoveSlot setSpacing"); + } + } + + /** + * {@inheritDoc} + * + * @deprecated As of 7.2, use or override + * {@link #insert(Widget, Element, int, boolean)} instead. + */ + @Override + @Deprecated + protected void insert(Widget child, + com.google.gwt.user.client.Element container, int beforeIndex, + boolean domInsert) { + // Validate index; adjust if the widget is already a child of this + // panel. + beforeIndex = adjustIndex(child, beforeIndex); + + // Detach new child. + child.removeFromParent(); + + // Logical attach. + getChildren().insert(child, beforeIndex); + + // Physical attach. + container = expandWrapper != null ? DOM.asOld(expandWrapper) + : getElement(); + if (domInsert) { + if (spacing) { + if (beforeIndex != 0) { + /* + * Since the spacing elements are located at the same DOM + * level as the slots we need to take them into account when + * calculating the slot position. + * + * The spacing elements are always located before the actual + * slot except for the first slot which do not have a + * spacing element like this + * + * |...| + */ + beforeIndex = beforeIndex * 2 - 1; + } + } + DOM.insertChild(container, child.getElement(), beforeIndex); + } else { + DOM.appendChild(container, child.getElement()); + } + + // Adopt. + adopt(child); + } + + /** + * {@inheritDoc} + * + * @since 7.2 + */ + @Override + protected void insert(Widget child, Element container, int beforeIndex, + boolean domInsert) { + insert(child, DOM.asOld(container), beforeIndex, domInsert); + } + + /** + * Remove a slot from the layout + * + * @param widget + * @return + */ + public void removeWidget(Widget widget) { + Slot slot = widgetToSlot.remove(widget); + if (slot != null) { + removeSlot(slot); + } + } + + /** + * Remove a slot from the layout. + * + * This method is called automatically by {@link #removeWidget(Widget)} and + * should not be called directly by the user. When overridden, the super + * method must be called. + * + * @since 7.6 + * @param Slot + * to remove + */ + protected void removeSlot(Slot slot) { + remove(slot); + } + + /** + * Get the containing slot for a widget. If no slot is found a new slot is + * created and returned. + * + * @param widget + * The widget whose slot you want to get + * + * @return + */ + public Slot getSlot(Widget widget) { + Slot slot = widgetToSlot.get(widget); + if (slot == null) { + slot = createSlot(widget); + widgetToSlot.put(widget, slot); + } + return slot; + } + + /** + * Create a slot to be added to the layout. + * + * This method is called automatically by {@link #getSlot(Widget)} when a + * new slot is needed. It should not be called directly by the user, but can + * be overridden to customize slot creation. + * + * @since 7.6 + * @param widget + * the widget for which a slot is being created + * @return created Slot + */ + protected Slot createSlot(Widget widget) { + Slot slot = GWT.create(Slot.class); + slot.setLayout(this); + slot.setWidget(widget); + return slot; + } + + /** + * Gets a slot based on the widget element. If no slot is found then null is + * returned. + * + * @param widgetElement + * The element of the widget ( Same as getWidget().getElement() ) + * @return + * @deprecated As of 7.2, call or override {@link #getSlot(Element)} instead + */ + @Deprecated + public Slot getSlot(com.google.gwt.user.client.Element widgetElement) { + for (Map.Entry entry : widgetToSlot.entrySet()) { + if (entry.getKey().getElement() == widgetElement) { + return entry.getValue(); + } + } + return null; + } + + /** + * Gets a slot based on the widget element. If no slot is found then null is + * returned. + * + * @param widgetElement + * The element of the widget ( Same as getWidget().getElement() ) + * @return + * + * @since 7.2 + */ + public Slot getSlot(Element widgetElement) { + return getSlot(DOM.asOld(widgetElement)); + } + + /** + * Set the layout manager for the layout + * + * @param manager + * The layout manager to use + */ + public void setLayoutManager(LayoutManager manager) { + layoutManager = manager; + } + + /** + * Get the layout manager used by this layout + * + */ + public LayoutManager getLayoutManager() { + return layoutManager; + } + + /** + * Deducts the caption position by examining the wrapping element. + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param captionWrap + * The wrapping element + * + * @return The caption position + * @deprecated As of 7.2, call or override + * {@link #getCaptionPositionFromElement(Element)} instead + */ + @Deprecated + public CaptionPosition getCaptionPositionFromElement( + com.google.gwt.user.client.Element captionWrap) { + RegExp captionPositionRegexp = RegExp.compile("v-caption-on-(\\S+)"); + + // Get caption position from the classname + MatchResult matcher = captionPositionRegexp.exec(captionWrap + .getClassName()); + if (matcher == null || matcher.getGroupCount() < 2) { + return CaptionPosition.TOP; + } + String captionClass = matcher.getGroup(1); + CaptionPosition captionPosition = CaptionPosition.valueOf( + CaptionPosition.class, captionClass.toUpperCase()); + return captionPosition; + } + + /** + * Deducts the caption position by examining the wrapping element. + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param captionWrap + * The wrapping element + * + * @return The caption position + * @since 7.2 + */ + public CaptionPosition getCaptionPositionFromElement(Element captionWrap) { + return getCaptionPositionFromElement(DOM.asOld(captionWrap)); + } + + /** + * Update the offset off the caption relative to the slot + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param caption + * The caption element + * @deprecated As of 7.2, call or override + * {@link #updateCaptionOffset(Element)} instead + */ + @Deprecated + public void updateCaptionOffset(com.google.gwt.user.client.Element caption) { + + Element captionWrap = caption.getParentElement(); + + Style captionWrapStyle = captionWrap.getStyle(); + captionWrapStyle.clearPaddingTop(); + captionWrapStyle.clearPaddingRight(); + captionWrapStyle.clearPaddingBottom(); + captionWrapStyle.clearPaddingLeft(); + + Style captionStyle = caption.getStyle(); + captionStyle.clearMarginTop(); + captionStyle.clearMarginRight(); + captionStyle.clearMarginBottom(); + captionStyle.clearMarginLeft(); + + // Get caption position from the classname + CaptionPosition captionPosition = getCaptionPositionFromElement(captionWrap); + + if (captionPosition == CaptionPosition.LEFT + || captionPosition == CaptionPosition.RIGHT) { + int captionWidth; + if (layoutManager != null) { + captionWidth = layoutManager.getOuterWidth(caption) + - layoutManager.getMarginWidth(caption); + } else { + captionWidth = caption.getOffsetWidth(); + } + if (captionWidth > 0) { + if (captionPosition == CaptionPosition.LEFT) { + captionWrapStyle.setPaddingLeft(captionWidth, Unit.PX); + captionStyle.setMarginLeft(-captionWidth, Unit.PX); + } else { + captionWrapStyle.setPaddingRight(captionWidth, Unit.PX); + captionStyle.setMarginRight(-captionWidth, Unit.PX); + } + } + } + if (captionPosition == CaptionPosition.TOP + || captionPosition == CaptionPosition.BOTTOM) { + int captionHeight; + if (layoutManager != null) { + captionHeight = layoutManager.getOuterHeight(caption) + - layoutManager.getMarginHeight(caption); + } else { + captionHeight = caption.getOffsetHeight(); + } + if (captionHeight > 0) { + if (captionPosition == CaptionPosition.TOP) { + captionWrapStyle.setPaddingTop(captionHeight, Unit.PX); + captionStyle.setMarginTop(-captionHeight, Unit.PX); + } else { + captionWrapStyle.setPaddingBottom(captionHeight, Unit.PX); + captionStyle.setMarginBottom(-captionHeight, Unit.PX); + } + } + } + } + + /** + * Update the offset off the caption relative to the slot + *

+ * For internal use only. May be removed or replaced in the future. + * + * @param caption + * The caption element + * @since 7.2 + */ + public void updateCaptionOffset(Element caption) { + updateCaptionOffset(DOM.asOld(caption)); + } + + /** + * Set the margin of the layout + * + * @param marginInfo + * The margin information + */ + public void setMargin(MarginInfo marginInfo) { + if (marginInfo != null) { + setStyleName("v-margin-top", marginInfo.hasTop()); + setStyleName("v-margin-right", marginInfo.hasRight()); + setStyleName("v-margin-bottom", marginInfo.hasBottom()); + setStyleName("v-margin-left", marginInfo.hasLeft()); + } + } + + /** + * Turn on or off spacing in the layout + * + * @param spacing + * True if spacing should be used, false if not + */ + public void setSpacing(boolean spacing) { + Profiler.enter("VAOL.onConnectorHierarchyChange setSpacing"); + this.spacing = spacing; + // first widget does not have spacing on + // optimization to avoid looking up widget indices on every iteration + Widget firstSlot = null; + if (getWidgetCount() > 0) { + firstSlot = getWidget(0); + } + for (Slot slot : widgetToSlot.values()) { + slot.setSpacing(spacing && firstSlot != slot); + } + Profiler.leave("VAOL.onConnectorHierarchyChange setSpacing"); + } + + /** + * Assigns relative sizes to the children that should expand based on their + * expand ratios. + */ + public void updateExpandedSizes() { + // Ensure the expand wrapper is in place + if (expandWrapper == null) { + expandWrapper = DOM.createDiv(); + expandWrapper.setClassName("v-expand"); + + // Detach all widgets before modifying DOM + for (Widget widget : getChildren()) { + orphan(widget); + } + + while (getElement().getChildCount() > 0) { + Node el = getElement().getChild(0); + expandWrapper.appendChild(el); + } + getElement().appendChild(expandWrapper); + + // Attach all widgets again + for (Widget widget : getChildren()) { + adopt(widget); + } + } + + // Sum up expand ratios to get the denominator + double total = 0; + for (Slot slot : widgetToSlot.values()) { + // FIXME expandRatio might be <0 + total += slot.getExpandRatio(); + } + + // Give each expanded child its own share + for (Slot slot : widgetToSlot.values()) { + + Element slotElement = slot.getElement(); + slotElement.removeAttribute("aria-hidden"); + + Style slotStyle = slotElement.getStyle(); + slotStyle.clearVisibility(); + slotStyle.clearMarginLeft(); + slotStyle.clearMarginTop(); + + if (slot.getExpandRatio() != 0) { + // FIXME expandRatio might be <0 + double size = 100 * (slot.getExpandRatio() / total); + + if (vertical) { + slot.setHeight(size + "%"); + if (slot.hasRelativeHeight()) { + Util.notifyParentOfSizeChange(this, true); + } + } else { + slot.setWidth(size + "%"); + if (slot.hasRelativeWidth()) { + Util.notifyParentOfSizeChange(this, true); + } + } + + } else if (slot.isRelativeInDirection(vertical)) { + // Relative child without expansion gets no space at all + if (vertical) { + slot.setHeight("0"); + } else { + slot.setWidth("0"); + } + slotStyle.setVisibility(Visibility.HIDDEN); + slotElement.setAttribute("aria-hidden", "true"); + + } else { + // Non-relative child without expansion should be unconstrained + if (BrowserInfo.get().isIE8()) { + // unconstrained in IE8 is auto + if (vertical) { + slot.setHeight("auto"); + } else { + slot.setWidth("auto"); + } + } else { + if (vertical) { + slotStyle.clearHeight(); + } else { + slotStyle.clearWidth(); + } + } + } + } + } + + /** + * Removes elements used to expand a slot. + *

+ * For internal use only. May be removed or replaced in the future. + */ + public void clearExpand() { + if (expandWrapper != null) { + // Detach all widgets before modifying DOM + for (Widget widget : getChildren()) { + orphan(widget); + } + + lastExpandSize = -1; + while (expandWrapper.getChildCount() > 0) { + Element el = expandWrapper.getChild(0).cast(); + getElement().appendChild(el); + if (vertical) { + el.getStyle().clearHeight(); + el.getStyle().clearMarginTop(); + } else { + el.getStyle().clearWidth(); + el.getStyle().clearMarginLeft(); + } + } + expandWrapper.removeFromParent(); + expandWrapper = null; + + // Attach children again + for (Widget widget : getChildren()) { + adopt(widget); + } + } + } + + /** + * Updates the expand compensation based on the measured sizes of children + * without expand. + */ + public void updateExpandCompensation() { + boolean isExpanding = false; + for (Widget slot : getChildren()) { + // FIXME expandRatio might be <0 + if (((Slot) slot).getExpandRatio() != 0) { + isExpanding = true; + break; + } + } + + if (isExpanding) { + /* + * Expanded slots have relative sizes that together add up to 100%. + * To make room for slots without expand, we will add padding that + * is not considered for relative sizes and a corresponding negative + * margin for the unexpanded slots. We calculate the size by summing + * the size of all non-expanded non-relative slots. + * + * Relatively sized slots without expansion are considered to get + * 0px, but we still keep them visible (causing overflows) to help + * the developer see what's happening. Forcing them to only get 0px + * would make them disappear which would avoid overflows but would + * instead cause confusion as they would then just disappear without + * any obvious reason. + */ + int totalSize = 0; + for (Widget w : getChildren()) { + Slot slot = (Slot) w; + if (slot.getExpandRatio() == 0 + && !slot.isRelativeInDirection(vertical)) { + + if (layoutManager != null) { + // TODO check caption position + if (vertical) { + int size = layoutManager.getOuterHeight(slot + .getWidget().getElement()); + if (slot.hasCaption()) { + size += layoutManager.getOuterHeight(slot + .getCaptionElement()); + } + if (size > 0) { + totalSize += size; + } + } else { + int max = -1; + max = layoutManager.getOuterWidth(slot.getWidget() + .getElement()); + if (slot.hasCaption()) { + int max2 = layoutManager.getOuterWidth(slot + .getCaptionElement()); + max = Math.max(max, max2); + } + if (max > 0) { + totalSize += max; + } + } + } else { + // FIXME expandRatio might be <0 + totalSize += vertical ? slot.getOffsetHeight() : slot + .getOffsetWidth(); + } + } + // TODO fails in Opera, always returns 0 + int spacingSize = vertical ? slot.getVerticalSpacing() : slot + .getHorizontalSpacing(); + if (spacingSize > 0) { + totalSize += spacingSize; + } + } + + // When we set the margin to the first child, we don't need + // overflow:hidden in the layout root element, since the wrapper + // would otherwise be placed outside of the layout root element + // and block events on elements below it. + if (vertical) { + expandWrapper.getStyle().setPaddingTop(totalSize, Unit.PX); + expandWrapper.getFirstChildElement().getStyle() + .setMarginTop(-totalSize, Unit.PX); + } else { + expandWrapper.getStyle().setPaddingLeft(totalSize, Unit.PX); + expandWrapper.getFirstChildElement().getStyle() + .setMarginLeft(-totalSize, Unit.PX); + } + + // Measure expanded children again if their size might have changed + if (totalSize != lastExpandSize) { + lastExpandSize = totalSize; + for (Widget w : getChildren()) { + Slot slot = (Slot) w; + // FIXME expandRatio might be <0 + if (slot.getExpandRatio() != 0) { + if (layoutManager != null) { + layoutManager.setNeedsMeasure(Util + .findConnectorFor(slot.getWidget())); + } else if (slot.getWidget() instanceof RequiresResize) { + ((RequiresResize) slot.getWidget()).onResize(); + } + } + } + } + } + WidgetUtil.forceIE8Redraw(getElement()); + } + + /** + * {@inheritDoc} + */ + @Override + public void setHeight(String height) { + super.setHeight(height); + definedHeight = (height != null && !"".equals(height)); + } + + /** + * Sets the slots style names. The style names will be prefixed with the + * v-slot prefix. + * + * @param stylenames + * The style names of the slot. + */ + public void setSlotStyleNames(Widget widget, String... stylenames) { + Slot slot = getSlot(widget); + if (slot == null) { + throw new IllegalArgumentException( + "A slot for the widget could not be found. Has the widget been added to the layout?"); + } + slot.setStyleNames(stylenames); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/orderedlayout/VerticalLayoutConnector.java b/client/src/main/java/com/vaadin/client/ui/orderedlayout/VerticalLayoutConnector.java new file mode 100644 index 0000000000..33ff020e89 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/orderedlayout/VerticalLayoutConnector.java @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.orderedlayout; + +import com.vaadin.client.ui.VVerticalLayout; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.Connect.LoadStyle; +import com.vaadin.shared.ui.orderedlayout.VerticalLayoutState; +import com.vaadin.ui.VerticalLayout; + +/** + * Connects the client widget {@link VVerticalLayout} with the Vaadin server + * side counterpart {@link VerticalLayout} + */ +@Connect(value = VerticalLayout.class, loadStyle = LoadStyle.EAGER) +public class VerticalLayoutConnector extends AbstractOrderedLayoutConnector { + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.client.ui.orderedlayout.AbstractOrderedLayoutConnector#getWidget + * () + */ + @Override + public VVerticalLayout getWidget() { + return (VVerticalLayout) super.getWidget(); + } + + @Override + public VerticalLayoutState getState() { + return (VerticalLayoutState) super.getState(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/panel/PanelConnector.java b/client/src/main/java/com/vaadin/client/ui/panel/PanelConnector.java new file mode 100644 index 0000000000..11111df602 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/panel/PanelConnector.java @@ -0,0 +1,256 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.panel; + +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Unit; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorHierarchyChangeEvent; +import com.vaadin.client.LayoutManager; +import com.vaadin.client.Paintable; +import com.vaadin.client.Profiler; +import com.vaadin.client.UIDL; +import com.vaadin.client.ui.AbstractSingleComponentContainerConnector; +import com.vaadin.client.ui.ClickEventHandler; +import com.vaadin.client.ui.PostLayoutListener; +import com.vaadin.client.ui.ShortcutActionHandler; +import com.vaadin.client.ui.SimpleManagedLayout; +import com.vaadin.client.ui.VPanel; +import com.vaadin.client.ui.layout.MayScrollChildren; +import com.vaadin.shared.MouseEventDetails; +import com.vaadin.shared.ui.ComponentStateUtil; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.panel.PanelServerRpc; +import com.vaadin.shared.ui.panel.PanelState; +import com.vaadin.ui.Panel; + +@Connect(Panel.class) +public class PanelConnector extends AbstractSingleComponentContainerConnector + implements Paintable, SimpleManagedLayout, PostLayoutListener, + MayScrollChildren { + + private Integer uidlScrollTop; + + private ClickEventHandler clickEventHandler = new ClickEventHandler(this) { + + @Override + protected void fireClick(NativeEvent event, + MouseEventDetails mouseDetails) { + getRpcProxy(PanelServerRpc.class).click(mouseDetails); + } + }; + + private Integer uidlScrollLeft; + + @Override + public void init() { + super.init(); + VPanel panel = getWidget(); + LayoutManager layoutManager = getLayoutManager(); + + layoutManager.registerDependency(this, panel.captionNode); + layoutManager.registerDependency(this, panel.bottomDecoration); + layoutManager.registerDependency(this, panel.contentNode); + } + + @Override + public void onUnregister() { + VPanel panel = getWidget(); + LayoutManager layoutManager = getLayoutManager(); + + layoutManager.unregisterDependency(this, panel.captionNode); + layoutManager.unregisterDependency(this, panel.bottomDecoration); + layoutManager.unregisterDependency(this, panel.contentNode); + } + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (isRealUpdate(uidl)) { + + // Handle caption displaying and style names, prior generics. + // Affects size calculations + + // Restore default stylenames + getWidget().contentNode.setClassName(VPanel.CLASSNAME + "-content"); + getWidget().bottomDecoration.setClassName(VPanel.CLASSNAME + + "-deco"); + getWidget().captionNode.setClassName(VPanel.CLASSNAME + "-caption"); + boolean hasCaption = false; + if (getState().caption != null && !"".equals(getState().caption)) { + getWidget().setCaption(getState().caption); + hasCaption = true; + } else { + getWidget().setCaption(""); + getWidget().captionNode.setClassName(VPanel.CLASSNAME + + "-nocaption"); + } + + // Add proper stylenames for all elements. This way we can prevent + // unwanted CSS selector inheritance. + final String captionBaseClass = VPanel.CLASSNAME + + (hasCaption ? "-caption" : "-nocaption"); + final String contentBaseClass = VPanel.CLASSNAME + "-content"; + final String decoBaseClass = VPanel.CLASSNAME + "-deco"; + String captionClass = captionBaseClass; + String contentClass = contentBaseClass; + String decoClass = decoBaseClass; + if (ComponentStateUtil.hasStyles(getState())) { + for (String style : getState().styles) { + captionClass += " " + captionBaseClass + "-" + style; + contentClass += " " + contentBaseClass + "-" + style; + decoClass += " " + decoBaseClass + "-" + style; + } + } + getWidget().captionNode.setClassName(captionClass); + getWidget().contentNode.setClassName(contentClass); + getWidget().bottomDecoration.setClassName(decoClass); + + getWidget().makeScrollable(); + } + + if (!isRealUpdate(uidl)) { + return; + } + + clickEventHandler.handleEventHandlerRegistration(); + + getWidget().client = client; + getWidget().id = uidl.getId(); + + if (getIconUri() != null) { + getWidget().setIconUri(getIconUri(), client); + } else { + getWidget().setIconUri(null, client); + } + + getWidget().setErrorIndicatorVisible(null != getState().errorMessage); + + // We may have actions attached to this panel + if (uidl.getChildCount() > 0) { + final int cnt = uidl.getChildCount(); + for (int i = 0; i < cnt; i++) { + UIDL childUidl = uidl.getChildUIDL(i); + if (childUidl.getTag().equals("actions")) { + if (getWidget().shortcutHandler == null) { + getWidget().shortcutHandler = new ShortcutActionHandler( + getConnectorId(), client); + } + getWidget().shortcutHandler.updateActionMap(childUidl); + } + } + } + + if (getState().scrollTop != getWidget().scrollTop) { + // Sizes are not yet up to date, so changing the scroll position + // is deferred to after the layout phase + uidlScrollTop = getState().scrollTop; + } + + if (getState().scrollLeft != getWidget().scrollLeft) { + // Sizes are not yet up to date, so changing the scroll position + // is deferred to after the layout phase + uidlScrollLeft = getState().scrollLeft; + } + + // And apply tab index + getWidget().contentNode.setTabIndex(getState().tabIndex); + } + + @Override + public void updateCaption(ComponentConnector component) { + // NOP: layouts caption, errors etc not rendered in Panel + } + + @Override + public VPanel getWidget() { + return (VPanel) super.getWidget(); + } + + @Override + public void layout() { + updateSizes(); + } + + void updateSizes() { + VPanel panel = getWidget(); + + LayoutManager layoutManager = getLayoutManager(); + Profiler.enter("PanelConnector.layout getHeights"); + int top = layoutManager.getOuterHeight(panel.captionNode); + int bottom = layoutManager.getInnerHeight(panel.bottomDecoration); + Profiler.leave("PanelConnector.layout getHeights"); + + Profiler.enter("PanelConnector.layout modify style"); + Style style = panel.getElement().getStyle(); + panel.captionNode.getParentElement().getStyle() + .setMarginTop(-top, Unit.PX); + panel.bottomDecoration.getStyle().setMarginBottom(-bottom, Unit.PX); + style.setPaddingTop(top, Unit.PX); + style.setPaddingBottom(bottom, Unit.PX); + Profiler.leave("PanelConnector.layout modify style"); + + // Update scroll positions + Profiler.enter("PanelConnector.layout update scroll positions"); + panel.contentNode.setScrollTop(panel.scrollTop); + panel.contentNode.setScrollLeft(panel.scrollLeft); + Profiler.leave("PanelConnector.layout update scroll positions"); + + // Read actual value back to ensure update logic is correct + Profiler.enter("PanelConnector.layout read scroll positions"); + panel.scrollTop = panel.contentNode.getScrollTop(); + panel.scrollLeft = panel.contentNode.getScrollLeft(); + Profiler.leave("PanelConnector.layout read scroll positions"); + } + + @Override + public void postLayout() { + VPanel panel = getWidget(); + if (uidlScrollTop != null) { + panel.contentNode.setScrollTop(uidlScrollTop.intValue()); + // Read actual value back to ensure update logic is correct + // TODO Does this trigger reflows? + panel.scrollTop = panel.contentNode.getScrollTop(); + uidlScrollTop = null; + } + + if (uidlScrollLeft != null) { + panel.contentNode.setScrollLeft(uidlScrollLeft.intValue()); + // Read actual value back to ensure update logic is correct + // TODO Does this trigger reflows? + panel.scrollLeft = panel.contentNode.getScrollLeft(); + uidlScrollLeft = null; + } + } + + @Override + public PanelState getState() { + return (PanelState) super.getState(); + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + // We always have 1 child, unless the child is hidden + getWidget().setWidget(getContentWidget()); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/passwordfield/PasswordFieldConnector.java b/client/src/main/java/com/vaadin/client/ui/passwordfield/PasswordFieldConnector.java new file mode 100644 index 0000000000..61576fac04 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/passwordfield/PasswordFieldConnector.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.passwordfield; + +import com.vaadin.client.ui.VPasswordField; +import com.vaadin.client.ui.textfield.TextFieldConnector; +import com.vaadin.shared.ui.Connect; +import com.vaadin.ui.PasswordField; + +@Connect(PasswordField.class) +public class PasswordFieldConnector extends TextFieldConnector { + + @Override + public VPasswordField getWidget() { + return (VPasswordField) super.getWidget(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/popupview/PopupViewConnector.java b/client/src/main/java/com/vaadin/client/ui/popupview/PopupViewConnector.java new file mode 100644 index 0000000000..6afceb75de --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/popupview/PopupViewConnector.java @@ -0,0 +1,145 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.popupview; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.event.shared.HandlerRegistration; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorHierarchyChangeEvent; +import com.vaadin.client.VCaption; +import com.vaadin.client.VCaptionWrapper; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractHasComponentsConnector; +import com.vaadin.client.ui.PostLayoutListener; +import com.vaadin.client.ui.VOverlay; +import com.vaadin.client.ui.VPopupView; +import com.vaadin.shared.ui.ComponentStateUtil; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.popupview.PopupViewServerRpc; +import com.vaadin.shared.ui.popupview.PopupViewState; +import com.vaadin.ui.PopupView; + +@Connect(PopupView.class) +public class PopupViewConnector extends AbstractHasComponentsConnector + implements PostLayoutListener, VisibilityChangeHandler { + + private boolean centerAfterLayout = false; + + private final List handlerRegistration = new ArrayList(); + + @Override + protected void init() { + super.init(); + + handlerRegistration.add(getWidget().addVisibilityChangeHandler(this)); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().setHTML(getState().html); + getWidget().popup.setHideOnMouseOut(getState().hideOnMouseOut); + } + + @Override + public PopupViewState getState() { + return (PopupViewState) super.getState(); + } + + @Override + public void updateCaption(ComponentConnector component) { + if (VCaption.isNeeded(component.getState())) { + if (getWidget().popup.captionWrapper != null) { + getWidget().popup.captionWrapper.updateCaption(); + } else { + getWidget().popup.captionWrapper = new VCaptionWrapper( + component, getConnection()); + getWidget().popup.setWidget(getWidget().popup.captionWrapper); + getWidget().popup.captionWrapper.updateCaption(); + } + } else { + if (getWidget().popup.captionWrapper != null) { + getWidget().popup + .setWidget(getWidget().popup.popupComponentWidget); + } + } + } + + @Override + public VPopupView getWidget() { + return (VPopupView) super.getWidget(); + } + + @Override + public void postLayout() { + if (centerAfterLayout) { + centerAfterLayout = false; + getWidget().center(); + } + } + + @Override + public void onConnectorHierarchyChange( + ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) { + // Render the popup if visible and show it. + if (!getChildComponents().isEmpty()) { + getWidget().preparePopup(getWidget().popup); + getWidget().popup.setPopupConnector(getChildComponents().get(0)); + + final StringBuffer styleBuf = new StringBuffer(); + final String primaryName = getWidget().popup.getStylePrimaryName(); + styleBuf.append(primaryName); + + // Add "animate-in" class back if already present + boolean isAnimatingIn = getWidget().popup.getStyleName().contains( + VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN); + + if (isAnimatingIn) { + styleBuf.append(" "); + styleBuf.append(primaryName); + styleBuf.append("-"); + styleBuf.append(VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN); + } + + if (ComponentStateUtil.hasStyles(getState())) { + for (String style : getState().styles) { + styleBuf.append(" "); + styleBuf.append(primaryName); + styleBuf.append("-"); + styleBuf.append(style); + } + } + + getWidget().popup.setStyleName(styleBuf.toString()); + getWidget().showPopup(getWidget().popup); + centerAfterLayout = true; + + } else { + // The popup shouldn't be visible, try to hide it. + getWidget().popup.hide(); + } + } + + @Override + public void onVisibilityChange(VisibilityChangeEvent event) { + getRpcProxy(PopupViewServerRpc.class).setPopupVisibility( + event.isVisible()); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/popupview/VisibilityChangeEvent.java b/client/src/main/java/com/vaadin/client/ui/popupview/VisibilityChangeEvent.java new file mode 100644 index 0000000000..8db2d1cb85 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/popupview/VisibilityChangeEvent.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.popupview; + +import com.google.gwt.event.shared.GwtEvent; + +public class VisibilityChangeEvent extends GwtEvent { + + private static Type TYPE; + + private boolean visible; + + public VisibilityChangeEvent(final boolean visible) { + this.visible = visible; + } + + public boolean isVisible() { + return visible; + } + + @Override + public Type getAssociatedType() { + return getType(); + } + + public static Type getType() { + if (TYPE == null) { + TYPE = new Type(); + } + return TYPE; + } + + @Override + protected void dispatch(final VisibilityChangeHandler handler) { + handler.onVisibilityChange(this); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/popupview/VisibilityChangeHandler.java b/client/src/main/java/com/vaadin/client/ui/popupview/VisibilityChangeHandler.java new file mode 100644 index 0000000000..3c5e09d1fc --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/popupview/VisibilityChangeHandler.java @@ -0,0 +1,23 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.popupview; + +import com.google.gwt.event.shared.EventHandler; + +public interface VisibilityChangeHandler extends EventHandler { + + void onVisibilityChange(VisibilityChangeEvent event); +} diff --git a/client/src/main/java/com/vaadin/client/ui/progressindicator/ProgressBarConnector.java b/client/src/main/java/com/vaadin/client/ui/progressindicator/ProgressBarConnector.java new file mode 100644 index 0000000000..3a83430f7a --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/progressindicator/ProgressBarConnector.java @@ -0,0 +1,56 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.progressindicator; + +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractFieldConnector; +import com.vaadin.client.ui.VProgressBar; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.progressindicator.ProgressBarState; +import com.vaadin.ui.ProgressBar; + +/** + * Connector for {@link VProgressBar}. + * + * @since 7.1 + * @author Vaadin Ltd + */ +@Connect(ProgressBar.class) +public class ProgressBarConnector extends AbstractFieldConnector { + + public ProgressBarConnector() { + super(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + getWidget().setIndeterminate(getState().indeterminate); + getWidget().setState(getState().state); + } + + @Override + public ProgressBarState getState() { + return (ProgressBarState) super.getState(); + } + + @Override + public VProgressBar getWidget() { + return (VProgressBar) super.getWidget(); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/progressindicator/ProgressIndicatorConnector.java b/client/src/main/java/com/vaadin/client/ui/progressindicator/ProgressIndicatorConnector.java new file mode 100644 index 0000000000..36bb1dd6b2 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/progressindicator/ProgressIndicatorConnector.java @@ -0,0 +1,75 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.progressindicator; + +import com.google.gwt.user.client.Timer; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.VProgressBar; +import com.vaadin.client.ui.VProgressIndicator; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.progressindicator.ProgressIndicatorServerRpc; +import com.vaadin.shared.ui.progressindicator.ProgressIndicatorState; +import com.vaadin.ui.ProgressIndicator; + +/** + * Connector for {@link VProgressBar} with polling support. + * + * @since 7.0 + * @author Vaadin Ltd + * @deprecated as of 7.1, use {@link ProgressBarConnector} combined with server + * push or UI polling. + */ +@Connect(ProgressIndicator.class) +@Deprecated +public class ProgressIndicatorConnector extends ProgressBarConnector { + + @Override + public ProgressIndicatorState getState() { + return (ProgressIndicatorState) super.getState(); + } + + private Timer poller = new Timer() { + + @Override + public void run() { + getRpcProxy(ProgressIndicatorServerRpc.class).poll(); + } + + }; + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + if (isEnabled()) { + poller.scheduleRepeating(getState().pollingInterval); + } else { + poller.cancel(); + } + } + + @Override + public VProgressIndicator getWidget() { + return (VProgressIndicator) super.getWidget(); + } + + @Override + public void onUnregister() { + super.onUnregister(); + poller.cancel(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java b/client/src/main/java/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java new file mode 100644 index 0000000000..bcf61a9338 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java @@ -0,0 +1,144 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.richtextarea; + +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.user.client.Event; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.Paintable; +import com.vaadin.client.UIDL; +import com.vaadin.client.ui.AbstractFieldConnector; +import com.vaadin.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; +import com.vaadin.client.ui.SimpleManagedLayout; +import com.vaadin.client.ui.VRichTextArea; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.Connect.LoadStyle; +import com.vaadin.shared.ui.textarea.RichTextAreaState; +import com.vaadin.shared.util.SharedUtil; +import com.vaadin.ui.RichTextArea; + +@Connect(value = RichTextArea.class, loadStyle = LoadStyle.LAZY) +public class RichTextAreaConnector extends AbstractFieldConnector implements + Paintable, BeforeShortcutActionListener, SimpleManagedLayout { + + /* + * Last value received from the server + */ + private String cachedValue = ""; + + @Override + protected void init() { + getWidget().addBlurHandler(new BlurHandler() { + + @Override + public void onBlur(BlurEvent event) { + flush(); + } + }); + getLayoutManager().registerDependency(this, + getWidget().formatter.getElement()); + } + + @Override + public void onUnregister() { + super.onUnregister(); + getLayoutManager().unregisterDependency(this, + getWidget().formatter.getElement()); + } + + @Override + public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) { + getWidget().client = client; + getWidget().id = uidl.getId(); + + if (uidl.hasVariable("text")) { + String newValue = uidl.getStringVariable("text"); + if (!SharedUtil.equals(newValue, cachedValue)) { + getWidget().setValue(newValue); + cachedValue = newValue; + } + } + + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().setEnabled(isEnabled()); + getWidget().setReadOnly(isReadOnly()); + getWidget().immediate = getState().immediate; + int newMaxLength = uidl.hasAttribute("maxLength") ? uidl + .getIntAttribute("maxLength") : -1; + if (newMaxLength >= 0) { + if (getWidget().maxLength == -1) { + getWidget().keyPressHandler = getWidget().rta + .addKeyPressHandler(getWidget()); + } + getWidget().maxLength = newMaxLength; + } else if (getWidget().maxLength != -1) { + getWidget().getElement().setAttribute("maxlength", ""); + getWidget().maxLength = -1; + getWidget().keyPressHandler.removeHandler(); + } + + if (uidl.hasAttribute("selectAll")) { + getWidget().selectAll(); + } + + } + + @Override + public void onBeforeShortcutAction(Event e) { + flush(); + } + + @Override + public VRichTextArea getWidget() { + return (VRichTextArea) super.getWidget(); + } + + @Override + public void flush() { + if (getConnection() != null && getConnectorId() != null) { + final String html = getWidget().getSanitizedValue(); + if (!html.equals(cachedValue)) { + cachedValue = html; + getConnection().updateVariable(getConnectorId(), "text", html, + getState().immediate); + } + } + } + + @Override + public void layout() { + if (!isUndefinedHeight()) { + int rootElementInnerHeight = getLayoutManager().getInnerHeight( + getWidget().getElement()); + int formatterHeight = getLayoutManager().getOuterHeight( + getWidget().formatter.getElement()); + int editorHeight = rootElementInnerHeight - formatterHeight; + if (editorHeight < 0) { + editorHeight = 0; + } + getWidget().rta.setHeight(editorHeight + "px"); + } + } + + @Override + public RichTextAreaState getState() { + return (RichTextAreaState) super.getState(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java b/client/src/main/java/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java new file mode 100644 index 0000000000..2e0554c499 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java @@ -0,0 +1,476 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +/* + * Copyright 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.richtextarea; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.KeyUpEvent; +import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.i18n.client.Constants; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.PushButton; +import com.google.gwt.user.client.ui.RichTextArea; +import com.google.gwt.user.client.ui.ToggleButton; + +/** + * A modified version of sample toolbar for use with {@link RichTextArea}. It + * provides a simple UI for all rich text formatting, dynamically displayed only + * for the available functionality. + */ +public class VRichTextToolbar extends Composite { + + /** + * This {@link ClientBundle} is used for all the button icons. Using a + * bundle allows all of these images to be packed into a single image, which + * saves a lot of HTTP requests, drastically improving startup time. + */ + public interface Images extends ClientBundle { + + ImageResource bold(); + + ImageResource createLink(); + + ImageResource hr(); + + ImageResource indent(); + + ImageResource insertImage(); + + ImageResource italic(); + + ImageResource justifyCenter(); + + ImageResource justifyLeft(); + + ImageResource justifyRight(); + + ImageResource ol(); + + ImageResource outdent(); + + ImageResource removeFormat(); + + ImageResource removeLink(); + + ImageResource strikeThrough(); + + ImageResource subscript(); + + ImageResource superscript(); + + ImageResource ul(); + + ImageResource underline(); + } + + /** + * This {@link Constants} interface is used to make the toolbar's strings + * internationalizable. + */ + public interface Strings extends Constants { + + String black(); + + String blue(); + + String bold(); + + String color(); + + String createLink(); + + String font(); + + String green(); + + String hr(); + + String indent(); + + String insertImage(); + + String italic(); + + String justifyCenter(); + + String justifyLeft(); + + String justifyRight(); + + String large(); + + String medium(); + + String normal(); + + String ol(); + + String outdent(); + + String red(); + + String removeFormat(); + + String removeLink(); + + String size(); + + String small(); + + String strikeThrough(); + + String subscript(); + + String superscript(); + + String ul(); + + String underline(); + + String white(); + + String xlarge(); + + String xsmall(); + + String xxlarge(); + + String xxsmall(); + + String yellow(); + } + + /** + * We use an inner EventHandler class to avoid exposing event methods on the + * RichTextToolbar itself. + */ + private class EventHandler implements ClickHandler, ChangeHandler, + KeyUpHandler { + + @Override + @SuppressWarnings("deprecation") + public void onChange(ChangeEvent event) { + Object sender = event.getSource(); + if (sender == backColors) { + basic.setBackColor(backColors.getValue(backColors + .getSelectedIndex())); + backColors.setSelectedIndex(0); + } else if (sender == foreColors) { + basic.setForeColor(foreColors.getValue(foreColors + .getSelectedIndex())); + foreColors.setSelectedIndex(0); + } else if (sender == fonts) { + basic.setFontName(fonts.getValue(fonts.getSelectedIndex())); + fonts.setSelectedIndex(0); + } else if (sender == fontSizes) { + basic.setFontSize(fontSizesConstants[fontSizes + .getSelectedIndex() - 1]); + fontSizes.setSelectedIndex(0); + } + } + + @Override + @SuppressWarnings("deprecation") + public void onClick(ClickEvent event) { + Object sender = event.getSource(); + if (sender == bold) { + basic.toggleBold(); + } else if (sender == italic) { + basic.toggleItalic(); + } else if (sender == underline) { + basic.toggleUnderline(); + } else if (sender == subscript) { + basic.toggleSubscript(); + } else if (sender == superscript) { + basic.toggleSuperscript(); + } else if (sender == strikethrough) { + extended.toggleStrikethrough(); + } else if (sender == indent) { + extended.rightIndent(); + } else if (sender == outdent) { + extended.leftIndent(); + } else if (sender == justifyLeft) { + basic.setJustification(RichTextArea.Justification.LEFT); + } else if (sender == justifyCenter) { + basic.setJustification(RichTextArea.Justification.CENTER); + } else if (sender == justifyRight) { + basic.setJustification(RichTextArea.Justification.RIGHT); + } else if (sender == insertImage) { + final String url = Window.prompt("Enter an image URL:", + "http://"); + if (url != null) { + extended.insertImage(url); + } + } else if (sender == createLink) { + final String url = Window + .prompt("Enter a link URL:", "http://"); + if (url != null) { + extended.createLink(url); + } + } else if (sender == removeLink) { + extended.removeLink(); + } else if (sender == hr) { + extended.insertHorizontalRule(); + } else if (sender == ol) { + extended.insertOrderedList(); + } else if (sender == ul) { + extended.insertUnorderedList(); + } else if (sender == removeFormat) { + extended.removeFormat(); + } else if (sender == richText) { + // We use the RichTextArea's onKeyUp event to update the toolbar + // status. This will catch any cases where the user moves the + // cursur using the keyboard, or uses one of the browser's + // built-in keyboard shortcuts. + updateStatus(); + } + } + + @Override + public void onKeyUp(KeyUpEvent event) { + if (event.getSource() == richText) { + // We use the RichTextArea's onKeyUp event to update the toolbar + // status. This will catch any cases where the user moves the + // cursor using the keyboard, or uses one of the browser's + // built-in keyboard shortcuts. + updateStatus(); + } + } + } + + private static final RichTextArea.FontSize[] fontSizesConstants = new RichTextArea.FontSize[] { + RichTextArea.FontSize.XX_SMALL, RichTextArea.FontSize.X_SMALL, + RichTextArea.FontSize.SMALL, RichTextArea.FontSize.MEDIUM, + RichTextArea.FontSize.LARGE, RichTextArea.FontSize.X_LARGE, + RichTextArea.FontSize.XX_LARGE }; + + private final Images images = (Images) GWT.create(Images.class); + private final Strings strings = (Strings) GWT.create(Strings.class); + private final EventHandler handler = new EventHandler(); + + private final RichTextArea richText; + @SuppressWarnings("deprecation") + private final RichTextArea.BasicFormatter basic; + @SuppressWarnings("deprecation") + private final RichTextArea.ExtendedFormatter extended; + + private final FlowPanel outer = new FlowPanel(); + private final FlowPanel topPanel = new FlowPanel(); + private final FlowPanel bottomPanel = new FlowPanel(); + private ToggleButton bold; + private ToggleButton italic; + private ToggleButton underline; + private ToggleButton subscript; + private ToggleButton superscript; + private ToggleButton strikethrough; + private PushButton indent; + private PushButton outdent; + private PushButton justifyLeft; + private PushButton justifyCenter; + private PushButton justifyRight; + private PushButton hr; + private PushButton ol; + private PushButton ul; + private PushButton insertImage; + private PushButton createLink; + private PushButton removeLink; + private PushButton removeFormat; + + private ListBox backColors; + private ListBox foreColors; + private ListBox fonts; + private ListBox fontSizes; + + /** + * Creates a new toolbar that drives the given rich text area. + * + * @param richText + * the rich text area to be controlled + */ + @SuppressWarnings("deprecation") + public VRichTextToolbar(RichTextArea richText) { + this.richText = richText; + basic = richText.getBasicFormatter(); + extended = richText.getExtendedFormatter(); + + outer.add(topPanel); + outer.add(bottomPanel); + topPanel.setStyleName("gwt-RichTextToolbar-top"); + bottomPanel.setStyleName("gwt-RichTextToolbar-bottom"); + + initWidget(outer); + setStyleName("gwt-RichTextToolbar"); + + if (basic != null) { + topPanel.add(bold = createToggleButton(images.bold(), + strings.bold())); + topPanel.add(italic = createToggleButton(images.italic(), + strings.italic())); + topPanel.add(underline = createToggleButton(images.underline(), + strings.underline())); + topPanel.add(subscript = createToggleButton(images.subscript(), + strings.subscript())); + topPanel.add(superscript = createToggleButton(images.superscript(), + strings.superscript())); + topPanel.add(justifyLeft = createPushButton(images.justifyLeft(), + strings.justifyLeft())); + topPanel.add(justifyCenter = createPushButton( + images.justifyCenter(), strings.justifyCenter())); + topPanel.add(justifyRight = createPushButton(images.justifyRight(), + strings.justifyRight())); + } + + if (extended != null) { + topPanel.add(strikethrough = createToggleButton( + images.strikeThrough(), strings.strikeThrough())); + topPanel.add(indent = createPushButton(images.indent(), + strings.indent())); + topPanel.add(outdent = createPushButton(images.outdent(), + strings.outdent())); + topPanel.add(hr = createPushButton(images.hr(), strings.hr())); + topPanel.add(ol = createPushButton(images.ol(), strings.ol())); + topPanel.add(ul = createPushButton(images.ul(), strings.ul())); + topPanel.add(insertImage = createPushButton(images.insertImage(), + strings.insertImage())); + topPanel.add(createLink = createPushButton(images.createLink(), + strings.createLink())); + topPanel.add(removeLink = createPushButton(images.removeLink(), + strings.removeLink())); + topPanel.add(removeFormat = createPushButton(images.removeFormat(), + strings.removeFormat())); + } + + if (basic != null) { + bottomPanel.add(backColors = createColorList("Background")); + bottomPanel.add(foreColors = createColorList("Foreground")); + bottomPanel.add(fonts = createFontList()); + bottomPanel.add(fontSizes = createFontSizes()); + + // We only use these handlers for updating status, so don't hook + // them up unless at least basic editing is supported. + richText.addKeyUpHandler(handler); + richText.addClickHandler(handler); + } + } + + private ListBox createColorList(String caption) { + final ListBox lb = new ListBox(); + lb.addChangeHandler(handler); + lb.setVisibleItemCount(1); + + lb.addItem(caption); + lb.addItem(strings.white(), "white"); + lb.addItem(strings.black(), "black"); + lb.addItem(strings.red(), "red"); + lb.addItem(strings.green(), "green"); + lb.addItem(strings.yellow(), "yellow"); + lb.addItem(strings.blue(), "blue"); + lb.setTabIndex(-1); + return lb; + } + + private ListBox createFontList() { + final ListBox lb = new ListBox(); + lb.addChangeHandler(handler); + lb.setVisibleItemCount(1); + + lb.addItem(strings.font(), ""); + lb.addItem(strings.normal(), "inherit"); + lb.addItem("Times New Roman", "Times New Roman"); + lb.addItem("Arial", "Arial"); + lb.addItem("Courier New", "Courier New"); + lb.addItem("Georgia", "Georgia"); + lb.addItem("Trebuchet", "Trebuchet"); + lb.addItem("Verdana", "Verdana"); + lb.setTabIndex(-1); + return lb; + } + + private ListBox createFontSizes() { + final ListBox lb = new ListBox(); + lb.addChangeHandler(handler); + lb.setVisibleItemCount(1); + + lb.addItem(strings.size()); + lb.addItem(strings.xxsmall()); + lb.addItem(strings.xsmall()); + lb.addItem(strings.small()); + lb.addItem(strings.medium()); + lb.addItem(strings.large()); + lb.addItem(strings.xlarge()); + lb.addItem(strings.xxlarge()); + lb.setTabIndex(-1); + return lb; + } + + private PushButton createPushButton(ImageResource img, String tip) { + final PushButton pb = new PushButton(new Image(img)); + pb.addClickHandler(handler); + pb.setTitle(tip); + pb.setTabIndex(-1); + return pb; + } + + private ToggleButton createToggleButton(ImageResource img, String tip) { + final ToggleButton tb = new ToggleButton(new Image(img)); + tb.addClickHandler(handler); + tb.setTitle(tip); + tb.setTabIndex(-1); + return tb; + } + + /** + * Updates the status of all the stateful buttons. + */ + @SuppressWarnings("deprecation") + private void updateStatus() { + if (basic != null) { + bold.setDown(basic.isBold()); + italic.setDown(basic.isItalic()); + underline.setDown(basic.isUnderlined()); + subscript.setDown(basic.isSubscript()); + superscript.setDown(basic.isSuperscript()); + } + + if (extended != null) { + strikethrough.setDown(extended.isStrikethrough()); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/slider/SliderConnector.java b/client/src/main/java/com/vaadin/client/ui/slider/SliderConnector.java new file mode 100644 index 0000000000..8c3c0254d3 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/slider/SliderConnector.java @@ -0,0 +1,97 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.slider; + +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.vaadin.client.communication.RpcProxy; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractFieldConnector; +import com.vaadin.client.ui.VSlider; +import com.vaadin.client.ui.layout.ElementResizeEvent; +import com.vaadin.client.ui.layout.ElementResizeListener; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.slider.SliderServerRpc; +import com.vaadin.shared.ui.slider.SliderState; +import com.vaadin.ui.Slider; + +@Connect(Slider.class) +public class SliderConnector extends AbstractFieldConnector implements + ValueChangeHandler { + + protected SliderServerRpc rpc = RpcProxy + .create(SliderServerRpc.class, this); + + private final ElementResizeListener resizeListener = new ElementResizeListener() { + + @Override + public void onElementResize(ElementResizeEvent e) { + getWidget().iLayout(); + } + }; + + @Override + public void init() { + super.init(); + getWidget().setConnection(getConnection()); + getWidget().addValueChangeHandler(this); + + getLayoutManager().addElementResizeListener(getWidget().getElement(), + resizeListener); + } + + @Override + public void onUnregister() { + super.onUnregister(); + getLayoutManager().removeElementResizeListener( + getWidget().getElement(), resizeListener); + } + + @Override + public VSlider getWidget() { + return (VSlider) super.getWidget(); + } + + @Override + public SliderState getState() { + return (SliderState) super.getState(); + } + + @Override + public void onValueChange(ValueChangeEvent event) { + getState().value = event.getValue(); + rpc.valueChanged(event.getValue()); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().setId(getConnectorId()); + getWidget().setImmediate(getState().immediate); + getWidget().setDisabled(!isEnabled()); + getWidget().setReadOnly(isReadOnly()); + getWidget().setOrientation(getState().orientation); + getWidget().setMinValue(getState().minValue); + getWidget().setMaxValue(getState().maxValue); + getWidget().setResolution(getState().resolution); + getWidget().setValue(getState().value, false); + + getWidget().buildBase(); + getWidget().setTabIndex(getState().tabIndex); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/splitpanel/AbstractSplitPanelConnector.java b/client/src/main/java/com/vaadin/client/ui/splitpanel/AbstractSplitPanelConnector.java new file mode 100644 index 0000000000..6bf03ad880 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/splitpanel/AbstractSplitPanelConnector.java @@ -0,0 +1,268 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.splitpanel; + +import java.util.LinkedList; +import java.util.List; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.DomEvent; +import com.google.gwt.event.dom.client.DomEvent.Type; +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorHierarchyChangeEvent; +import com.vaadin.client.LayoutManager; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractComponentContainerConnector; +import com.vaadin.client.ui.ClickEventHandler; +import com.vaadin.client.ui.SimpleManagedLayout; +import com.vaadin.client.ui.VAbstractSplitPanel; +import com.vaadin.client.ui.VAbstractSplitPanel.SplitterMoveHandler; +import com.vaadin.client.ui.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent; +import com.vaadin.shared.MouseEventDetails; +import com.vaadin.shared.ui.ComponentStateUtil; +import com.vaadin.shared.ui.splitpanel.AbstractSplitPanelRpc; +import com.vaadin.shared.ui.splitpanel.AbstractSplitPanelState; +import com.vaadin.shared.ui.splitpanel.AbstractSplitPanelState.SplitterState; + +public abstract class AbstractSplitPanelConnector extends + AbstractComponentContainerConnector implements SimpleManagedLayout { + + @Override + protected void init() { + super.init(); + // TODO Remove + getWidget().client = getConnection(); + + getWidget().addHandler(new SplitterMoveHandler() { + + @Override + public void splitterMoved(SplitterMoveEvent event) { + String position = getWidget().getSplitterPosition(); + float pos = 0; + if (position.indexOf("%") > 0) { + // Send % values as a fraction to avoid that the splitter + // "jumps" when server responds with the integer pct value + // (e.g. dragged 16.6% -> should not jump to 17%) + pos = Float.valueOf(position.substring(0, + position.length() - 1)); + } else { + pos = Integer.parseInt(position.substring(0, + position.length() - 2)); + } + + getRpcProxy(AbstractSplitPanelRpc.class).setSplitterPosition( + pos); + } + + }, SplitterMoveEvent.TYPE); + } + + @Override + public void updateCaption(ComponentConnector component) { + // TODO Implement caption handling + } + + ClickEventHandler clickEventHandler = new ClickEventHandler(this) { + + @Override + protected HandlerRegistration registerHandler( + H handler, Type type) { + if ((Event.getEventsSunk(getWidget().splitter) & Event + .getTypeInt(type.getName())) != 0) { + // If we are already sinking the event for the splitter we do + // not want to additionally sink it for the root element + return getWidget().addHandler(handler, type); + } else { + return getWidget().addDomHandler(handler, type); + } + } + + @Override + protected boolean shouldFireEvent(DomEvent event) { + Element target = event.getNativeEvent().getEventTarget().cast(); + if (!getWidget().splitter.isOrHasChild(target)) { + return false; + } + + return super.shouldFireEvent(event); + } + + @Override + protected com.google.gwt.user.client.Element getRelativeToElement() { + return DOM.asOld(getWidget().splitter); + } + + @Override + protected void fireClick(NativeEvent event, + MouseEventDetails mouseDetails) { + getRpcProxy(AbstractSplitPanelRpc.class) + .splitterClick(mouseDetails); + } + + }; + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().immediate = getState().immediate; + + getWidget().setEnabled(isEnabled()); + + clickEventHandler.handleEventHandlerRegistration(); + + if (ComponentStateUtil.hasStyles(getState())) { + getWidget().componentStyleNames = getState().styles; + } else { + getWidget().componentStyleNames = new LinkedList(); + } + + // Splitter updates + SplitterState splitterState = getState().splitterState; + + getWidget().setStylenames(); + + getWidget().minimumPosition = splitterState.minPosition + + splitterState.minPositionUnit; + + getWidget().maximumPosition = splitterState.maxPosition + + splitterState.maxPositionUnit; + + getWidget().position = splitterState.position + + splitterState.positionUnit; + + getWidget().setPositionReversed(splitterState.positionReversed); + + getWidget().setLocked(splitterState.locked); + + // This is needed at least for cases like #3458 to take + // appearing/disappearing scrollbars into account. + getConnection().runDescendentsLayout(getWidget()); + + getLayoutManager().setNeedsLayout(this); + + getWidget().makeScrollable(); + + handleSingleComponentMove(); + } + + /** + * Handles the case when there is only one child component and that + * component is moved between first <-> second. This does not trigger a + * hierarchy change event as the list of children contains the same + * component in both cases. + */ + private void handleSingleComponentMove() { + if (getChildComponents().size() == 1) { + Widget stateFirstChild = null; + Widget stateSecondChild = null; + if (getState().firstChild != null) { + stateFirstChild = ((ComponentConnector) getState().firstChild) + .getWidget(); + } + if (getState().secondChild != null) { + stateSecondChild = ((ComponentConnector) getState().secondChild) + .getWidget(); + } + + if (stateFirstChild == getWidget().getSecondWidget() + || stateSecondChild == getWidget().getFirstWidget()) { + handleHierarchyChange(); + } + } + + } + + @Override + public void layout() { + VAbstractSplitPanel splitPanel = getWidget(); + splitPanel.setSplitPosition(splitPanel.position); + splitPanel.updateSizes(); + // Report relative sizes in other direction for quicker propagation + List children = getChildComponents(); + for (ComponentConnector child : children) { + reportOtherDimension(child); + } + } + + private void reportOtherDimension(ComponentConnector child) { + LayoutManager layoutManager = getLayoutManager(); + if (this instanceof HorizontalSplitPanelConnector) { + if (child.isRelativeHeight()) { + int height = layoutManager.getInnerHeight(getWidget() + .getElement()); + layoutManager.reportHeightAssignedToRelative(child, height); + } + } else { + if (child.isRelativeWidth()) { + int width = layoutManager.getInnerWidth(getWidget() + .getElement()); + layoutManager.reportWidthAssignedToRelative(child, width); + } + } + } + + @Override + public VAbstractSplitPanel getWidget() { + return (VAbstractSplitPanel) super.getWidget(); + } + + @Override + public AbstractSplitPanelState getState() { + return (AbstractSplitPanelState) super.getState(); + } + + private ComponentConnector getFirstChild() { + return (ComponentConnector) getState().firstChild; + } + + private ComponentConnector getSecondChild() { + return (ComponentConnector) getState().secondChild; + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + handleHierarchyChange(); + } + + private void handleHierarchyChange() { + /* + * When the connector gets detached, the state isn't updated but there's + * still a hierarchy change -> verify that the child from the state is + * still our child before attaching the widget. See #10150. + */ + + Widget newFirstChildWidget = null; + ComponentConnector firstChild = getFirstChild(); + if (firstChild != null && firstChild.getParent() == this) { + newFirstChildWidget = firstChild.getWidget(); + } + getWidget().setFirstWidget(newFirstChildWidget); + + Widget newSecondChildWidget = null; + ComponentConnector secondChild = getSecondChild(); + if (secondChild != null && secondChild.getParent() == this) { + newSecondChildWidget = secondChild.getWidget(); + } + getWidget().setSecondWidget(newSecondChildWidget); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/splitpanel/HorizontalSplitPanelConnector.java b/client/src/main/java/com/vaadin/client/ui/splitpanel/HorizontalSplitPanelConnector.java new file mode 100644 index 0000000000..75389b6cc4 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/splitpanel/HorizontalSplitPanelConnector.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.splitpanel; + +import com.vaadin.client.ui.VSplitPanelHorizontal; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.Connect.LoadStyle; +import com.vaadin.shared.ui.splitpanel.HorizontalSplitPanelState; +import com.vaadin.ui.HorizontalSplitPanel; + +@Connect(value = HorizontalSplitPanel.class, loadStyle = LoadStyle.EAGER) +public class HorizontalSplitPanelConnector extends AbstractSplitPanelConnector { + + @Override + public VSplitPanelHorizontal getWidget() { + return (VSplitPanelHorizontal) super.getWidget(); + } + + @Override + public HorizontalSplitPanelState getState() { + return (HorizontalSplitPanelState) super.getState(); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/splitpanel/VerticalSplitPanelConnector.java b/client/src/main/java/com/vaadin/client/ui/splitpanel/VerticalSplitPanelConnector.java new file mode 100644 index 0000000000..e95f7143ba --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/splitpanel/VerticalSplitPanelConnector.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.splitpanel; + +import com.vaadin.client.ui.VSplitPanelVertical; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.Connect.LoadStyle; +import com.vaadin.shared.ui.splitpanel.VerticalSplitPanelState; +import com.vaadin.ui.VerticalSplitPanel; + +@Connect(value = VerticalSplitPanel.class, loadStyle = LoadStyle.EAGER) +public class VerticalSplitPanelConnector extends AbstractSplitPanelConnector { + + @Override + public VSplitPanelVertical getWidget() { + return (VSplitPanelVertical) super.getWidget(); + } + + @Override + public VerticalSplitPanelState getState() { + return (VerticalSplitPanelState) super.getState(); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/table/TableConnector.java b/client/src/main/java/com/vaadin/client/ui/table/TableConnector.java new file mode 100644 index 0000000000..a554b9335c --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/table/TableConnector.java @@ -0,0 +1,556 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.table; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.EventTarget; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorHierarchyChangeEvent; +import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler; +import com.vaadin.client.DirectionalManagedLayout; +import com.vaadin.client.HasChildMeasurementHintConnector; +import com.vaadin.client.HasComponentsConnector; +import com.vaadin.client.Paintable; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.TooltipInfo; +import com.vaadin.client.UIDL; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.AbstractFieldConnector; +import com.vaadin.client.ui.PostLayoutListener; +import com.vaadin.client.ui.VScrollTable; +import com.vaadin.client.ui.VScrollTable.ContextMenuDetails; +import com.vaadin.client.ui.VScrollTable.FooterCell; +import com.vaadin.client.ui.VScrollTable.HeaderCell; +import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; +import com.vaadin.shared.MouseEventDetails; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.table.TableConstants; +import com.vaadin.shared.ui.table.TableConstants.Section; +import com.vaadin.shared.ui.table.TableServerRpc; +import com.vaadin.shared.ui.table.TableState; + +@Connect(com.vaadin.ui.Table.class) +public class TableConnector extends AbstractFieldConnector implements + HasComponentsConnector, ConnectorHierarchyChangeHandler, Paintable, + DirectionalManagedLayout, PostLayoutListener, + HasChildMeasurementHintConnector { + + private List childComponents; + + public TableConnector() { + addConnectorHierarchyChangeHandler(this); + } + + @Override + protected void init() { + super.init(); + getWidget().init(getConnection()); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ui.AbstractComponentConnector#onUnregister() + */ + @Override + public void onUnregister() { + super.onUnregister(); + getWidget().onUnregister(); + } + + @Override + protected void sendContextClickEvent(MouseEventDetails details, + EventTarget eventTarget) { + + if (!Element.is(eventTarget)) { + return; + } + Element e = Element.as(eventTarget); + + Section section; + String colKey = null; + String rowKey = null; + if (getWidget().tFoot.getElement().isOrHasChild(e)) { + section = Section.FOOTER; + FooterCell w = WidgetUtil.findWidget(e, FooterCell.class); + colKey = w.getColKey(); + } else if (getWidget().tHead.getElement().isOrHasChild(e)) { + section = Section.HEADER; + HeaderCell w = WidgetUtil.findWidget(e, HeaderCell.class); + colKey = w.getColKey(); + } else { + section = Section.BODY; + if (getWidget().scrollBody.getElement().isOrHasChild(e)) { + VScrollTableRow w = getScrollTableRow(e); + /* + * if w is null because we've clicked on an empty area, we will + * let rowKey and colKey be null too, which will then lead to + * the server side returning a null object. + */ + if (w != null) { + rowKey = w.getKey(); + colKey = getWidget().tHead.getHeaderCell( + getElementIndex(e, w.getElement())).getColKey(); + } + } + } + + getRpcProxy(TableServerRpc.class).contextClick(rowKey, colKey, section, + details); + + WidgetUtil.clearTextSelection(); + } + + protected VScrollTableRow getScrollTableRow(Element e) { + return WidgetUtil.findWidget(e, VScrollTableRow.class); + } + + private int getElementIndex(Element e, + com.google.gwt.user.client.Element element) { + int i = 0; + Element current = element.getFirstChildElement(); + while (!current.isOrHasChild(e)) { + current = current.getNextSiblingElement(); + ++i; + } + return i; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.Paintable#updateFromUIDL(com.vaadin.client.UIDL, + * com.vaadin.client.ApplicationConnection) + */ + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().rendering = true; + + // If a row has an open context menu, it will be closed as the row is + // detached. Retain a reference here so we can restore the menu if + // required. + ContextMenuDetails contextMenuBeforeUpdate = getWidget().contextMenu; + + if (uidl.hasAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_FIRST)) { + getWidget().serverCacheFirst = uidl + .getIntAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_FIRST); + getWidget().serverCacheLast = uidl + .getIntAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_LAST); + } else { + getWidget().serverCacheFirst = -1; + getWidget().serverCacheLast = -1; + } + /* + * We need to do this before updateComponent since updateComponent calls + * this.setHeight() which will calculate a new body height depending on + * the space available. + */ + if (uidl.hasAttribute("colfooters")) { + getWidget().showColFooters = uidl.getBooleanAttribute("colfooters"); + } + + getWidget().tFoot.setVisible(getWidget().showColFooters); + + if (!isRealUpdate(uidl)) { + getWidget().rendering = false; + return; + } + + getWidget().enabled = isEnabled(); + + if (BrowserInfo.get().isIE8() && !getWidget().enabled) { + /* + * The disabled shim will not cover the table body if it is relative + * in IE8. See #7324 + */ + getWidget().scrollBodyPanel.getElement().getStyle() + .setPosition(Position.STATIC); + } else if (BrowserInfo.get().isIE8()) { + getWidget().scrollBodyPanel.getElement().getStyle() + .setPosition(Position.RELATIVE); + } + + getWidget().paintableId = uidl.getStringAttribute("id"); + getWidget().immediate = getState().immediate; + + int previousTotalRows = getWidget().totalRows; + getWidget().updateTotalRows(uidl); + boolean totalRowsHaveChanged = (getWidget().totalRows != previousTotalRows); + + getWidget().updateDragMode(uidl); + + // Update child measure hint + int childMeasureHint = uidl.hasAttribute("measurehint") ? uidl + .getIntAttribute("measurehint") : 0; + getWidget().setChildMeasurementHint( + ChildMeasurementHint.values()[childMeasureHint]); + + getWidget().updateSelectionProperties(uidl, getState(), isReadOnly()); + + if (uidl.hasAttribute("alb")) { + getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb"); + } else { + // Need to clear the actions if the action handlers have been + // removed + getWidget().bodyActionKeys = null; + } + + getWidget().setCacheRateFromUIDL(uidl); + + getWidget().recalcWidths = uidl.hasAttribute("recalcWidths"); + if (getWidget().recalcWidths) { + getWidget().tHead.clear(); + getWidget().tFoot.clear(); + } + + getWidget().updatePageLength(uidl); + + getWidget().updateFirstVisibleAndScrollIfNeeded(uidl); + + getWidget().showRowHeaders = uidl.getBooleanAttribute("rowheaders"); + getWidget().showColHeaders = uidl.getBooleanAttribute("colheaders"); + + getWidget().updateSortingProperties(uidl); + + getWidget().updateActionMap(uidl); + + getWidget().updateColumnProperties(uidl); + + UIDL ac = uidl.getChildByTagName("-ac"); + if (ac == null) { + if (getWidget().dropHandler != null) { + // remove dropHandler if not present anymore + getWidget().dropHandler = null; + } + } else { + if (getWidget().dropHandler == null) { + getWidget().dropHandler = getWidget().new VScrollTableDropHandler(); + } + getWidget().dropHandler.updateAcceptRules(ac); + } + + UIDL partialRowAdditions = uidl.getChildByTagName("prows"); + UIDL partialRowUpdates = uidl.getChildByTagName("urows"); + if (partialRowUpdates != null || partialRowAdditions != null) { + getWidget().postponeSanityCheckForLastRendered = true; + // we may have pending cache row fetch, cancel it. See #2136 + getWidget().rowRequestHandler.cancel(); + + getWidget().updateRowsInBody(partialRowUpdates); + getWidget().addAndRemoveRows(partialRowAdditions); + + // sanity check (in case the value has slipped beyond the total + // amount of rows) + getWidget().scrollBody.setLastRendered(getWidget().scrollBody + .getLastRendered()); + getWidget().updateMaxIndent(); + } else { + getWidget().postponeSanityCheckForLastRendered = false; + UIDL rowData = uidl.getChildByTagName("rows"); + if (rowData != null) { + // we may have pending cache row fetch, cancel it. See #2136 + getWidget().rowRequestHandler.cancel(); + + if (!getWidget().recalcWidths + && getWidget().initializedAndAttached) { + getWidget().updateBody(rowData, + uidl.getIntAttribute("firstrow"), + uidl.getIntAttribute("rows")); + if (getWidget().headerChangedDuringUpdate) { + getWidget().triggerLazyColumnAdjustment(true); + } + } else { + getWidget().initializeRows(uidl, rowData); + } + } + } + + boolean keyboardSelectionOverRowFetchInProgress = getWidget() + .selectSelectedRows(uidl); + + // If a row had an open context menu before the update, and after the + // update there's a row with the same key as that row, restore the + // context menu. See #8526. + showSavedContextMenu(contextMenuBeforeUpdate); + + if (!getWidget().isSelectable()) { + getWidget().scrollBody.addStyleName(getWidget() + .getStylePrimaryName() + "-body-noselection"); + } else { + getWidget().scrollBody.removeStyleName(getWidget() + .getStylePrimaryName() + "-body-noselection"); + } + + getWidget().hideScrollPositionAnnotation(); + + // selection is no in sync with server, avoid excessive server visits by + // clearing to flag used during the normal operation + if (!keyboardSelectionOverRowFetchInProgress) { + getWidget().selectionChanged = false; + } + + /* + * This is called when the Home or page up button has been pressed in + * selectable mode and the next selected row was not yet rendered in the + * client + */ + if (getWidget().selectFirstItemInNextRender + || getWidget().focusFirstItemInNextRender) { + getWidget().selectFirstRenderedRowInViewPort( + getWidget().focusFirstItemInNextRender); + getWidget().selectFirstItemInNextRender = getWidget().focusFirstItemInNextRender = false; + } + + /* + * This is called when the page down or end button has been pressed in + * selectable mode and the next selected row was not yet rendered in the + * client + */ + if (getWidget().selectLastItemInNextRender + || getWidget().focusLastItemInNextRender) { + getWidget().selectLastRenderedRowInViewPort( + getWidget().focusLastItemInNextRender); + getWidget().selectLastItemInNextRender = getWidget().focusLastItemInNextRender = false; + } + getWidget().multiselectPending = false; + + if (getWidget().focusedRow != null) { + if (!getWidget().focusedRow.isAttached() + && !getWidget().rowRequestHandler.isRequestHandlerRunning()) { + // focused row has been orphaned, can't focus + if (getWidget().selectedRowKeys.contains(getWidget().focusedRow + .getKey())) { + // if row cache was refreshed, focused row should be + // in selection and exists with same index + getWidget().setRowFocus( + getWidget().getRenderedRowByKey( + getWidget().focusedRow.getKey())); + } else if (getWidget().selectedRowKeys.size() > 0) { + // try to focus any row in selection + getWidget().setRowFocus( + getWidget().getRenderedRowByKey( + getWidget().selectedRowKeys.iterator() + .next())); + } else { + // try to focus any row + getWidget().focusRowFromBody(); + } + } + } + + /* + * If the server has (re)initialized the rows, our selectionRangeStart + * row will point to an index that the server knows nothing about, + * causing problems if doing multi selection with shift. The field will + * be cleared a little later when the row focus has been restored. + * (#8584) + */ + if (uidl.hasAttribute(TableConstants.ATTRIBUTE_KEY_MAPPER_RESET) + && uidl.getBooleanAttribute(TableConstants.ATTRIBUTE_KEY_MAPPER_RESET) + && getWidget().selectionRangeStart != null) { + assert !getWidget().selectionRangeStart.isAttached(); + getWidget().selectionRangeStart = getWidget().focusedRow; + } + + getWidget().tabIndex = getState().tabIndex; + getWidget().setProperTabIndex(); + + Scheduler.get().scheduleFinally(new ScheduledCommand() { + + @Override + public void execute() { + getWidget().resizeSortedColumnForSortIndicator(); + } + }); + + // Remember this to detect situations where overflow hack might be + // needed during scrolling + getWidget().lastRenderedHeight = getWidget().scrollBody + .getOffsetHeight(); + + getWidget().rendering = false; + getWidget().headerChangedDuringUpdate = false; + + getWidget().collapsibleMenuContent = getState().collapseMenuContent; + } + + @Override + public VScrollTable getWidget() { + return (VScrollTable) super.getWidget(); + } + + @Override + public void updateCaption(ComponentConnector component) { + // NOP, not rendered + } + + @Override + public void layoutVertically() { + getWidget().updateHeight(); + } + + @Override + public void layoutHorizontally() { + getWidget().updateWidth(); + } + + @Override + public void postLayout() { + VScrollTable table = getWidget(); + if (table.sizeNeedsInit) { + table.sizeInit(); + Scheduler.get().scheduleFinally(new ScheduledCommand() { + @Override + public void execute() { + // IE8 needs some hacks to measure sizes correctly + WidgetUtil.forceIE8Redraw(getWidget().getElement()); + + getLayoutManager().setNeedsMeasure(TableConnector.this); + ServerConnector parent = getParent(); + if (parent instanceof ComponentConnector) { + getLayoutManager().setNeedsMeasure( + (ComponentConnector) parent); + } + getLayoutManager().setNeedsVerticalLayout( + TableConnector.this); + getLayoutManager().layoutNow(); + } + }); + } + } + + @Override + public boolean isReadOnly() { + return super.isReadOnly() || getState().propertyReadOnly; + } + + @Override + public TableState getState() { + return (TableState) super.getState(); + } + + /** + * Shows a saved row context menu if the row for the context menu is still + * visible. Does nothing if a context menu has not been saved. + * + * @param savedContextMenu + */ + public void showSavedContextMenu(ContextMenuDetails savedContextMenu) { + if (isEnabled() && savedContextMenu != null) { + Iterator iterator = getWidget().scrollBody.iterator(); + while (iterator.hasNext()) { + Widget w = iterator.next(); + VScrollTableRow row = (VScrollTableRow) w; + if (row.getKey().equals(savedContextMenu.rowKey)) { + row.showContextMenu(savedContextMenu.left, + savedContextMenu.top); + } + } + } + } + + @Override + public TooltipInfo getTooltipInfo(Element element) { + + TooltipInfo info = null; + + if (element != getWidget().getElement()) { + Object node = WidgetUtil.findWidget(element, VScrollTableRow.class); + + if (node != null) { + VScrollTableRow row = (VScrollTableRow) node; + info = row.getTooltip(element); + } + } + + if (info == null) { + info = super.getTooltipInfo(element); + } + + return info; + } + + @Override + public boolean hasTooltip() { + /* + * Tooltips for individual rows and cells are not processed until + * updateFromUIDL, so we can't be sure that there are no tooltips during + * onStateChange when this method is used. + */ + return true; + } + + @Override + public void onConnectorHierarchyChange( + ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) { + // TODO Move code from updateFromUIDL to this method + } + + @Override + protected void updateComponentSize(String newWidth, String newHeight) { + super.updateComponentSize(newWidth, newHeight); + + if ("".equals(newWidth)) { + getWidget().updateWidth(); + } + if ("".equals(newHeight)) { + getWidget().updateHeight(); + } + } + + @Override + public List getChildComponents() { + if (childComponents == null) { + return Collections.emptyList(); + } + + return childComponents; + } + + @Override + public void setChildComponents(List childComponents) { + this.childComponents = childComponents; + } + + @Override + public HandlerRegistration addConnectorHierarchyChangeHandler( + ConnectorHierarchyChangeHandler handler) { + return ensureHandlerManager().addHandler( + ConnectorHierarchyChangeEvent.TYPE, handler); + } + + @Override + public void setChildMeasurementHint(ChildMeasurementHint hint) { + getWidget().setChildMeasurementHint(hint); + } + + @Override + public ChildMeasurementHint getChildMeasurementHint() { + return getWidget().getChildMeasurementHint(); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/tabsheet/TabsheetBaseConnector.java b/client/src/main/java/com/vaadin/client/ui/tabsheet/TabsheetBaseConnector.java new file mode 100644 index 0000000000..9de415e74e --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/tabsheet/TabsheetBaseConnector.java @@ -0,0 +1,118 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.tabsheet; + +import java.util.ArrayList; +import java.util.Iterator; + +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractComponentContainerConnector; +import com.vaadin.client.ui.VTabsheetBase; +import com.vaadin.shared.ui.tabsheet.TabState; +import com.vaadin.shared.ui.tabsheet.TabsheetState; + +public abstract class TabsheetBaseConnector extends + AbstractComponentContainerConnector { + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ui.AbstractConnector#init() + */ + @Override + protected void init() { + super.init(); + + getWidget().setClient(getConnection()); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.client.ui.AbstractComponentConnector#onStateChanged(com.vaadin + * .client.communication.StateChangeEvent) + */ + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + // Update member references + getWidget().setEnabled(isEnabled()); + + // Widgets in the TabSheet before update + ArrayList oldWidgets = new ArrayList(); + for (Iterator iterator = getWidget().getWidgetIterator(); iterator + .hasNext();) { + oldWidgets.add(iterator.next()); + } + + // Clear previous values + getWidget().clearTabKeys(); + + int index = 0; + for (TabState tab : getState().tabs) { + final String key = tab.key; + final boolean selected = key.equals(getState().selected); + + getWidget().addTabKey(key, !tab.enabled && tab.visible); + + if (selected) { + getWidget().setActiveTabIndex(index); + } + getWidget().renderTab(tab, index); + if (selected) { + getWidget().selectTab(index); + } + index++; + } + + int tabCount = getWidget().getTabCount(); + while (tabCount-- > index) { + getWidget().removeTab(index); + } + + for (int i = 0; i < getWidget().getTabCount(); i++) { + ComponentConnector p = getWidget().getTab(i); + // null for PlaceHolder widgets + if (p != null) { + oldWidgets.remove(p.getWidget()); + } + } + + // Detach any old tab widget, should be max 1 + for (Iterator iterator = oldWidgets.iterator(); iterator + .hasNext();) { + Widget oldWidget = iterator.next(); + if (oldWidget.isAttached()) { + oldWidget.removeFromParent(); + } + } + } + + @Override + public VTabsheetBase getWidget() { + return (VTabsheetBase) super.getWidget(); + } + + @Override + public TabsheetState getState() { + return (TabsheetState) super.getState(); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/tabsheet/TabsheetConnector.java b/client/src/main/java/com/vaadin/client/ui/tabsheet/TabsheetConnector.java new file mode 100644 index 0000000000..1b043c8a51 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/tabsheet/TabsheetConnector.java @@ -0,0 +1,192 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.tabsheet; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Overflow; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorHierarchyChangeEvent; +import com.vaadin.client.TooltipInfo; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.SimpleManagedLayout; +import com.vaadin.client.ui.VTabsheet; +import com.vaadin.client.ui.layout.MayScrollChildren; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.tabsheet.TabsheetClientRpc; +import com.vaadin.ui.TabSheet; + +@Connect(TabSheet.class) +public class TabsheetConnector extends TabsheetBaseConnector implements + SimpleManagedLayout, MayScrollChildren { + + public TabsheetConnector() { + registerRpc(TabsheetClientRpc.class, new TabsheetClientRpc() { + @Override + public void revertToSharedStateSelection() { + for (int i = 0; i < getState().tabs.size(); ++i) { + final String key = getState().tabs.get(i).key; + final boolean selected = key.equals(getState().selected); + if (selected) { + getWidget().waitingForResponse = false; + getWidget().setActiveTabIndex(i); + getWidget().selectTab(i); + break; + } + } + renderContent(); + } + }); + } + + @Override + protected void init() { + super.init(); + getWidget().setConnector(this); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.client.ui.AbstractComponentConnector#onStateChanged(com.vaadin + * .client.communication.StateChangeEvent) + */ + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().handleStyleNames(getState()); + + if (getState().tabsVisible) { + getWidget().showTabs(); + } else { + getWidget().hideTabs(); + } + + // tabs; push or not + if (!isUndefinedWidth()) { + getWidget().tabs.getStyle().setOverflow(Overflow.HIDDEN); + } else { + getWidget().showAllTabs(); + getWidget().tabs.getStyle().clearWidth(); + getWidget().tabs.getStyle().setOverflow(Overflow.VISIBLE); + getWidget().updateDynamicWidth(); + } + + if (!isUndefinedHeight()) { + // Must update height after the styles have been set + getWidget().updateContentNodeHeight(); + getWidget().updateOpenTabSize(); + } + + getWidget().iLayout(); + } + + @Override + public VTabsheet getWidget() { + return (VTabsheet) super.getWidget(); + } + + @Override + public void updateCaption(ComponentConnector component) { + /* Tabsheet does not render its children's captions */ + } + + @Override + public void layout() { + VTabsheet tabsheet = getWidget(); + + tabsheet.updateContentNodeHeight(); + + if (isUndefinedWidth()) { + tabsheet.contentNode.getStyle().setProperty("width", ""); + } else { + int contentWidth = tabsheet.getOffsetWidth() + - tabsheet.getContentAreaBorderWidth(); + if (contentWidth < 0) { + contentWidth = 0; + } + tabsheet.contentNode.getStyle().setProperty("width", + contentWidth + "px"); + } + + tabsheet.updateOpenTabSize(); + if (isUndefinedWidth()) { + tabsheet.updateDynamicWidth(); + } + + tabsheet.iLayout(); + + } + + @Override + public TooltipInfo getTooltipInfo(Element element) { + + TooltipInfo info = null; + + // Find a tooltip for the tab, if the element is a tab + if (element != getWidget().getElement()) { + Object node = WidgetUtil.findWidget(element, + VTabsheet.TabCaption.class); + + if (node != null) { + VTabsheet.TabCaption caption = (VTabsheet.TabCaption) node; + info = caption.getTooltipInfo(); + } + } + + // If not tab tooltip was found, use the default + if (info == null) { + info = super.getTooltipInfo(element); + } + + return info; + } + + @Override + public boolean hasTooltip() { + /* + * Tab tooltips are not processed until updateFromUIDL, so we can't be + * sure that there are no tooltips during onStateChange when this method + * is used. + */ + return true; + } + + @Override + public void onConnectorHierarchyChange( + ConnectorHierarchyChangeEvent connector) { + renderContent(); + } + + /** + * (Re-)render the content of the active tab. + */ + protected void renderContent() { + ComponentConnector contentConnector = null; + if (!getChildComponents().isEmpty()) { + contentConnector = getChildComponents().get(0); + } + + if (null != contentConnector) { + getWidget().renderContent(contentConnector.getWidget()); + } else { + getWidget().renderContent(null); + } + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/textarea/TextAreaConnector.java b/client/src/main/java/com/vaadin/client/ui/textarea/TextAreaConnector.java new file mode 100644 index 0000000000..3bc0a86df4 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/textarea/TextAreaConnector.java @@ -0,0 +1,91 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.textarea; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.dom.client.MouseUpEvent; +import com.google.gwt.event.dom.client.MouseUpHandler; +import com.vaadin.client.WidgetUtil.CssSize; +import com.vaadin.client.ui.VTextArea; +import com.vaadin.client.ui.textfield.TextFieldConnector; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.textarea.TextAreaState; +import com.vaadin.ui.TextArea; + +@Connect(TextArea.class) +public class TextAreaConnector extends TextFieldConnector { + + @Override + public TextAreaState getState() { + return (TextAreaState) super.getState(); + } + + @Override + public VTextArea getWidget() { + return (VTextArea) super.getWidget(); + } + + @Override + protected void init() { + super.init(); + + getWidget().addMouseUpHandler(new ResizeMouseUpHandler()); + } + + /* + * Workaround to handle the resize on the mouse up. + */ + private class ResizeMouseUpHandler implements MouseUpHandler { + + @Override + public void onMouseUp(MouseUpEvent event) { + Element element = getWidget().getElement(); + + updateSize(element.getStyle().getHeight(), getState().height, + "height"); + updateSize(element.getStyle().getWidth(), getState().width, "width"); + } + + /* + * Update the specified size on the server. + */ + private void updateSize(String sizeText, String stateSizeText, + String sizeType) { + + CssSize stateSize = CssSize.fromString(stateSizeText); + CssSize newSize = CssSize.fromString(sizeText); + + if (stateSize == null && newSize == null) { + return; + + } else if (newSize == null) { + sizeText = ""; + + // Else, if the current stateSize is null, just go ahead and set + // the newSize, so no check on stateSize is needed. + + } else if (stateSize != null && stateSize.equals(newSize)) { + return; + } + + getConnection().updateVariable(getConnectorId(), sizeType, + sizeText, false); + } + + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/textfield/TextFieldConnector.java b/client/src/main/java/com/vaadin/client/ui/textfield/TextFieldConnector.java new file mode 100644 index 0000000000..0d85e98ee3 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/textfield/TextFieldConnector.java @@ -0,0 +1,129 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.textfield; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Event; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.Paintable; +import com.vaadin.client.UIDL; +import com.vaadin.client.Util; +import com.vaadin.client.ui.AbstractFieldConnector; +import com.vaadin.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; +import com.vaadin.client.ui.VTextField; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.Connect.LoadStyle; +import com.vaadin.shared.ui.textfield.AbstractTextFieldState; +import com.vaadin.shared.ui.textfield.TextFieldConstants; +import com.vaadin.ui.TextField; + +@Connect(value = TextField.class, loadStyle = LoadStyle.EAGER) +public class TextFieldConnector extends AbstractFieldConnector implements + Paintable, BeforeShortcutActionListener { + + @Override + public AbstractTextFieldState getState() { + return (AbstractTextFieldState) super.getState(); + } + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Save details + getWidget().client = client; + getWidget().paintableId = uidl.getId(); + + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().setReadOnly(isReadOnly()); + + getWidget().setInputPrompt(getState().inputPrompt); + getWidget().setMaxLength(getState().maxLength); + getWidget().setImmediate(getState().immediate); + + getWidget().listenTextChangeEvents = hasEventListener("ie"); + if (getWidget().listenTextChangeEvents) { + getWidget().textChangeEventMode = uidl + .getStringAttribute(TextFieldConstants.ATTR_TEXTCHANGE_EVENTMODE); + if (getWidget().textChangeEventMode + .equals(TextFieldConstants.TEXTCHANGE_MODE_EAGER)) { + getWidget().textChangeEventTimeout = 1; + } else { + getWidget().textChangeEventTimeout = uidl + .getIntAttribute(TextFieldConstants.ATTR_TEXTCHANGE_TIMEOUT); + if (getWidget().textChangeEventTimeout < 1) { + // Sanitize and allow lazy/timeout with timeout set to 0 to + // work as eager + getWidget().textChangeEventTimeout = 1; + } + } + getWidget().sinkEvents(VTextField.TEXTCHANGE_EVENTS); + getWidget().attachCutEventListener(getWidget().getElement()); + } + getWidget().setColumns(getState().columns); + + String text = getState().text; + if (text == null) { + text = ""; + } + /* + * We skip the text content update if field has been repainted, but text + * has not been changed (#6588). Additional sanity check verifies there + * is no change in the queue (in which case we count more on the server + * side value). is updated only when it looses focus, so we + * force updating if not focused. Lost focus issue appeared in (#15144) + */ + if (!(Util.getFocusedElement() == getWidget().getElement()) + || !uidl.getBooleanAttribute(TextFieldConstants.ATTR_NO_VALUE_CHANGE_BETWEEN_PAINTS) + || getWidget().valueBeforeEdit == null + || !text.equals(getWidget().valueBeforeEdit)) { + getWidget().updateFieldContent(text); + } + + if (uidl.hasAttribute("selpos")) { + final int pos = uidl.getIntAttribute("selpos"); + final int length = uidl.getIntAttribute("sellen"); + /* + * Gecko defers setting the text so we need to defer the selection. + */ + Scheduler.get().scheduleDeferred(new Command() { + @Override + public void execute() { + getWidget().setSelectionRange(pos, length); + } + }); + } + } + + @Override + public VTextField getWidget() { + return (VTextField) super.getWidget(); + } + + @Override + public void onBeforeShortcutAction(Event e) { + flush(); + } + + @Override + public void flush() { + getWidget().valueChange(false); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/tree/TreeConnector.java b/client/src/main/java/com/vaadin/client/ui/tree/TreeConnector.java new file mode 100644 index 0000000000..f49f44e802 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/tree/TreeConnector.java @@ -0,0 +1,400 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.tree; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.aria.client.Roles; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.EventTarget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.Paintable; +import com.vaadin.client.TooltipInfo; +import com.vaadin.client.UIDL; +import com.vaadin.client.VConsole; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.VTree; +import com.vaadin.client.ui.VTree.TreeNode; +import com.vaadin.shared.MouseEventDetails; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.MultiSelectMode; +import com.vaadin.shared.ui.tree.TreeConstants; +import com.vaadin.shared.ui.tree.TreeServerRpc; +import com.vaadin.shared.ui.tree.TreeState; +import com.vaadin.ui.Tree; + +@Connect(Tree.class) +public class TreeConnector extends AbstractComponentConnector implements + Paintable { + + protected final Map tooltipMap = new HashMap(); + + @Override + protected void init() { + getWidget().connector = this; + } + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().rendering = true; + + getWidget().client = client; + + if (uidl.hasAttribute("partialUpdate")) { + handleUpdate(uidl); + + // IE8 needs a hack to measure the tree again after update + WidgetUtil.forceIE8Redraw(getWidget().getElement()); + + getWidget().rendering = false; + return; + } + + getWidget().paintableId = uidl.getId(); + + getWidget().immediate = getState().immediate; + + getWidget().disabled = !isEnabled(); + getWidget().readonly = isReadOnly(); + + getWidget().dragMode = uidl.hasAttribute("dragMode") ? uidl + .getIntAttribute("dragMode") : 0; + + getWidget().isNullSelectionAllowed = uidl + .getBooleanAttribute("nullselect"); + getWidget().isHtmlContentAllowed = uidl + .getBooleanAttribute(TreeConstants.ATTRIBUTE_HTML_ALLOWED); + + if (uidl.hasAttribute("alb")) { + getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb"); + } + + getWidget().body.clear(); + // clear out any references to nodes that no longer are attached + getWidget().clearNodeToKeyMap(); + tooltipMap.clear(); + + TreeNode childTree = null; + UIDL childUidl = null; + for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { + childUidl = (UIDL) i.next(); + if ("actions".equals(childUidl.getTag())) { + updateActionMap(childUidl); + continue; + } else if ("-ac".equals(childUidl.getTag())) { + getWidget().updateDropHandler(childUidl); + continue; + } + childTree = getWidget().new TreeNode(); + getConnection().getVTooltip().connectHandlersToWidget(childTree); + updateNodeFromUIDL(childTree, childUidl, 1); + getWidget().body.add(childTree); + childTree.addStyleDependentName("root"); + childTree.childNodeContainer.addStyleDependentName("root"); + } + if (childTree != null && childUidl != null) { + boolean leaf = !childUidl.getTag().equals("node"); + childTree.addStyleDependentName(leaf ? "leaf-last" : "last"); + childTree.childNodeContainer.addStyleDependentName("last"); + } + final String selectMode = uidl.getStringAttribute("selectmode"); + getWidget().selectable = !"none".equals(selectMode); + getWidget().isMultiselect = "multi".equals(selectMode); + + if (getWidget().isMultiselect) { + Roles.getTreeRole().setAriaMultiselectableProperty( + getWidget().getElement(), true); + + if (BrowserInfo.get().isTouchDevice()) { + // Always use the simple mode for touch devices that do not have + // shift/ctrl keys (#8595) + getWidget().multiSelectMode = MultiSelectMode.SIMPLE; + } else { + getWidget().multiSelectMode = MultiSelectMode.valueOf(uidl + .getStringAttribute("multiselectmode")); + } + } else { + Roles.getTreeRole().setAriaMultiselectableProperty( + getWidget().getElement(), false); + } + + getWidget().selectedIds = uidl.getStringArrayVariableAsSet("selected"); + + // Update lastSelection and focusedNode to point to *actual* nodes again + // after the old ones have been cleared from the body. This fixes focus + // and keyboard navigation issues as described in #7057 and other + // tickets. + if (getWidget().lastSelection != null) { + getWidget().lastSelection = getWidget().getNodeByKey( + getWidget().lastSelection.key); + } + + if (getWidget().focusedNode != null) { + + Set selectedIds = getWidget().selectedIds; + + // If the focused node is not between the selected nodes, we need to + // refresh the focused node to prevent an undesired scroll. #12618. + if (!selectedIds.isEmpty() + && !selectedIds.contains(getWidget().focusedNode.key)) { + String keySelectedId = selectedIds.iterator().next(); + + TreeNode nodeToSelect = getWidget().getNodeByKey(keySelectedId); + + getWidget().setFocusedNode(nodeToSelect); + } else { + getWidget().setFocusedNode( + getWidget().getNodeByKey(getWidget().focusedNode.key)); + } + } + + if (getWidget().lastSelection == null + && getWidget().focusedNode == null + && !getWidget().selectedIds.isEmpty()) { + getWidget().setFocusedNode( + getWidget().getNodeByKey( + getWidget().selectedIds.iterator().next())); + getWidget().focusedNode.setFocused(false); + } + + // IE8 needs a hack to measure the tree again after update + WidgetUtil.forceIE8Redraw(getWidget().getElement()); + + getWidget().rendering = false; + + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + // VTree does not implement Focusable + getWidget().setTabIndex(getState().tabIndex); + } + + @Override + public VTree getWidget() { + return (VTree) super.getWidget(); + } + + private void handleUpdate(UIDL uidl) { + final TreeNode rootNode = getWidget().getNodeByKey( + uidl.getStringAttribute("rootKey")); + if (rootNode != null) { + if (!rootNode.getState()) { + // expanding node happened server side + rootNode.setState(true, false); + } + String levelPropertyString = Roles.getTreeitemRole() + .getAriaLevelProperty(rootNode.getElement()); + int levelProperty; + try { + levelProperty = Integer.valueOf(levelPropertyString); + } catch (NumberFormatException e) { + levelProperty = 1; + VConsole.error(e); + } + + renderChildNodes(rootNode, (Iterator) uidl.getChildIterator(), + levelProperty + 1); + } + } + + /** + * Registers action for the root and also for individual nodes + * + * @param uidl + */ + private void updateActionMap(UIDL uidl) { + final Iterator it = uidl.getChildIterator(); + while (it.hasNext()) { + final UIDL action = (UIDL) it.next(); + final String key = action.getStringAttribute("key"); + final String caption = action + .getStringAttribute(TreeConstants.ATTRIBUTE_ACTION_CAPTION); + String iconUrl = null; + if (action.hasAttribute(TreeConstants.ATTRIBUTE_ACTION_ICON)) { + iconUrl = getConnection() + .translateVaadinUri( + action.getStringAttribute(TreeConstants.ATTRIBUTE_ACTION_ICON)); + } + getWidget().registerAction(key, caption, iconUrl); + } + + } + + public void updateNodeFromUIDL(TreeNode treeNode, UIDL uidl, int level) { + Roles.getTreeitemRole().setAriaLevelProperty(treeNode.getElement(), + level); + + String nodeKey = uidl.getStringAttribute("key"); + String caption = uidl + .getStringAttribute(TreeConstants.ATTRIBUTE_NODE_CAPTION); + if (getWidget().isHtmlContentAllowed) { + treeNode.setHtml(caption); + } else { + treeNode.setText(caption); + } + treeNode.key = nodeKey; + + getWidget().registerNode(treeNode); + + if (uidl.hasAttribute("al")) { + treeNode.actionKeys = uidl.getStringArrayAttribute("al"); + } + + if (uidl.getTag().equals("node")) { + if (uidl.getChildCount() == 0) { + treeNode.childNodeContainer.setVisible(false); + } else { + renderChildNodes(treeNode, (Iterator) uidl.getChildIterator(), + level + 1); + treeNode.childrenLoaded = true; + } + } else { + treeNode.addStyleName(TreeNode.CLASSNAME + "-leaf"); + } + if (uidl.hasAttribute(TreeConstants.ATTRIBUTE_NODE_STYLE)) { + treeNode.setNodeStyleName(uidl + .getStringAttribute(TreeConstants.ATTRIBUTE_NODE_STYLE)); + } + + String description = uidl.getStringAttribute("descr"); + if (description != null) { + tooltipMap.put(treeNode, new TooltipInfo(description, null, + treeNode)); + } + + if (uidl.getBooleanAttribute("expanded") && !treeNode.getState()) { + treeNode.setState(true, false); + } + + if (uidl.getBooleanAttribute("selected")) { + treeNode.setSelected(true); + // ensure that identifier is in selectedIds array (this may be a + // partial update) + getWidget().selectedIds.add(nodeKey); + } + + String iconUrl = uidl + .getStringAttribute(TreeConstants.ATTRIBUTE_NODE_ICON); + String iconAltText = uidl + .getStringAttribute(TreeConstants.ATTRIBUTE_NODE_ICON_ALT); + treeNode.setIcon(iconUrl, iconAltText); + } + + void renderChildNodes(TreeNode containerNode, Iterator i, int level) { + containerNode.childNodeContainer.clear(); + containerNode.childNodeContainer.setVisible(true); + while (i.hasNext()) { + final UIDL childUidl = i.next(); + // actions are in bit weird place, don't mix them with children, + // but current node's actions + if ("actions".equals(childUidl.getTag())) { + updateActionMap(childUidl); + continue; + } + final TreeNode childTree = getWidget().new TreeNode(); + getConnection().getVTooltip().connectHandlersToWidget(childTree); + updateNodeFromUIDL(childTree, childUidl, level); + containerNode.childNodeContainer.add(childTree); + if (!i.hasNext()) { + childTree + .addStyleDependentName(childTree.isLeaf() ? "leaf-last" + : "last"); + childTree.childNodeContainer.addStyleDependentName("last"); + } + } + containerNode.childrenLoaded = true; + } + + @Override + public boolean isReadOnly() { + return super.isReadOnly() || getState().propertyReadOnly; + } + + @Override + public TreeState getState() { + return (TreeState) super.getState(); + } + + @Override + public TooltipInfo getTooltipInfo(Element element) { + + TooltipInfo info = null; + + // Try to find a tooltip for a node + if (element != getWidget().getElement()) { + Object node = WidgetUtil.findWidget(element, TreeNode.class); + + if (node != null) { + TreeNode tnode = (TreeNode) node; + if (tnode.isCaptionElement(element)) { + info = tooltipMap.get(tnode); + } + } + } + + // If no tooltip found for the node or if the target was not a node, use + // the default tooltip + if (info == null) { + info = super.getTooltipInfo(element); + } + + return info; + } + + @Override + public boolean hasTooltip() { + /* + * Item tooltips are not processed until updateFromUIDL, so we can't be + * sure that there are no tooltips during onStateChange when this method + * is used. + */ + return true; + } + + @Override + protected void sendContextClickEvent(MouseEventDetails details, + EventTarget eventTarget) { + if (!Element.is(eventTarget)) { + return; + } + + Element e = Element.as(eventTarget); + String key = null; + + if (getWidget().body.getElement().isOrHasChild(e)) { + TreeNode t = WidgetUtil.findWidget(e, TreeNode.class); + if (t != null) { + key = t.key; + } + } + + getRpcProxy(TreeServerRpc.class).contextClick(key, details); + + WidgetUtil.clearTextSelection(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/treetable/TreeTableConnector.java b/client/src/main/java/com/vaadin/client/ui/treetable/TreeTableConnector.java new file mode 100644 index 0000000000..0e0c190c11 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/treetable/TreeTableConnector.java @@ -0,0 +1,151 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.treetable; + +import com.google.gwt.dom.client.Element; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.TooltipInfo; +import com.vaadin.client.UIDL; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.FocusableScrollPanel; +import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; +import com.vaadin.client.ui.VTreeTable; +import com.vaadin.client.ui.VTreeTable.PendingNavigationEvent; +import com.vaadin.client.ui.VTreeTable.VTreeTableScrollBody.VTreeTableRow; +import com.vaadin.client.ui.table.TableConnector; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.treetable.TreeTableConstants; +import com.vaadin.shared.ui.treetable.TreeTableState; +import com.vaadin.ui.TreeTable; + +@Connect(TreeTable.class) +public class TreeTableConnector extends TableConnector { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + FocusableScrollPanel widget = null; + int scrollPosition = 0; + if (getWidget().collapseRequest) { + widget = (FocusableScrollPanel) getWidget().getWidget(1); + scrollPosition = widget.getScrollPosition(); + } + getWidget().animationsEnabled = uidl.getBooleanAttribute("animate"); + getWidget().colIndexOfHierarchy = uidl + .hasAttribute(TreeTableConstants.ATTRIBUTE_HIERARCHY_COLUMN_INDEX) ? uidl + .getIntAttribute(TreeTableConstants.ATTRIBUTE_HIERARCHY_COLUMN_INDEX) + : 0; + int oldTotalRows = getWidget().getTotalRows(); + + super.updateFromUIDL(uidl, client); + // super.updateFromUIDL set rendering to false, even though we continue + // rendering here. Set it back to true. + getWidget().rendering = true; + + if (getWidget().collapseRequest) { + if (getWidget().collapsedRowKey != null + && getWidget().scrollBody != null) { + VScrollTableRow row = getWidget().getRenderedRowByKey( + getWidget().collapsedRowKey); + if (row != null) { + getWidget().setRowFocus(row); + getWidget().focus(); + } + } + + int scrollPosition2 = widget.getScrollPosition(); + if (scrollPosition != scrollPosition2) { + widget.setScrollPosition(scrollPosition); + } + + // check which rows are needed from the server and initiate a + // deferred fetch + getWidget().onScroll(null); + } + // Recalculate table size if collapse request, or if page length is zero + // (not sent by server) and row count changes (#7908). + if (getWidget().collapseRequest + || (!uidl.hasAttribute("pagelength") && getWidget() + .getTotalRows() != oldTotalRows)) { + /* + * Ensure that possibly removed/added scrollbars are considered. + * Triggers row calculations, removes cached rows etc. Basically + * cleans up state. Be careful if touching this, you will break + * pageLength=0 if you remove this. + */ + getWidget().triggerLazyColumnAdjustment(true); + + getWidget().collapseRequest = false; + } + if (uidl.hasAttribute("focusedRow")) { + String key = uidl.getStringAttribute("focusedRow"); + getWidget().setRowFocus(getWidget().getRenderedRowByKey(key)); + getWidget().focusParentResponsePending = false; + } else if (uidl.hasAttribute("clearFocusPending")) { + // Special case to detect a response to a focusParent request that + // does not return any focusedRow because the selected node has no + // parent + getWidget().focusParentResponsePending = false; + } + + while (!getWidget().collapseRequest + && !getWidget().focusParentResponsePending + && !getWidget().pendingNavigationEvents.isEmpty()) { + // Keep replaying any queued events as long as we don't have any + // potential content changes pending + PendingNavigationEvent event = getWidget().pendingNavigationEvents + .removeFirst(); + getWidget() + .handleNavigation(event.keycode, event.ctrl, event.shift); + } + getWidget().rendering = false; + } + + @Override + public VTreeTable getWidget() { + return (VTreeTable) super.getWidget(); + } + + @Override + public TreeTableState getState() { + return (TreeTableState) super.getState(); + } + + @Override + public TooltipInfo getTooltipInfo(Element element) { + + TooltipInfo info = null; + + if (element != getWidget().getElement()) { + Object node = WidgetUtil.findWidget(element, VTreeTableRow.class); + + if (node != null) { + VTreeTableRow row = (VTreeTableRow) node; + info = row.getTooltip(element); + } + } + + if (info == null) { + info = super.getTooltipInfo(element); + } + + return info; + } + + @Override + protected VScrollTableRow getScrollTableRow(Element e) { + return WidgetUtil.findWidget(e, VTreeTableRow.class); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/twincolselect/TwinColSelectConnector.java b/client/src/main/java/com/vaadin/client/ui/twincolselect/TwinColSelectConnector.java new file mode 100644 index 0000000000..0994ac15c3 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/twincolselect/TwinColSelectConnector.java @@ -0,0 +1,84 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.twincolselect; + +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.DirectionalManagedLayout; +import com.vaadin.client.UIDL; +import com.vaadin.client.ui.VTwinColSelect; +import com.vaadin.client.ui.optiongroup.OptionGroupBaseConnector; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.twincolselect.TwinColSelectState; +import com.vaadin.ui.TwinColSelect; + +@Connect(TwinColSelect.class) +public class TwinColSelectConnector extends OptionGroupBaseConnector implements + DirectionalManagedLayout { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Captions are updated before super call to ensure the widths are set + // correctly + if (isRealUpdate(uidl)) { + getWidget().updateCaptions(uidl); + getLayoutManager().setNeedsHorizontalLayout(this); + } + + super.updateFromUIDL(uidl, client); + } + + @Override + protected void init() { + super.init(); + getLayoutManager().registerDependency(this, + getWidget().captionWrapper.getElement()); + } + + @Override + public void onUnregister() { + getLayoutManager().unregisterDependency(this, + getWidget().captionWrapper.getElement()); + } + + @Override + public VTwinColSelect getWidget() { + return (VTwinColSelect) super.getWidget(); + } + + @Override + public TwinColSelectState getState() { + return (TwinColSelectState) super.getState(); + } + + @Override + public void layoutVertically() { + if (isUndefinedHeight()) { + getWidget().clearInternalHeights(); + } else { + getWidget().setInternalHeights(); + } + } + + @Override + public void layoutHorizontally() { + if (isUndefinedWidth()) { + getWidget().clearInternalWidths(); + } else { + getWidget().setInternalWidths(); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/ui/UIConnector.java b/client/src/main/java/com/vaadin/client/ui/ui/UIConnector.java new file mode 100644 index 0000000000..9ffb9cfba9 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/ui/UIConnector.java @@ -0,0 +1,1126 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.ui; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Logger; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.HeadElement; +import com.google.gwt.dom.client.LinkElement; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.NodeList; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.StyleElement; +import com.google.gwt.dom.client.StyleInjector; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.ScrollEvent; +import com.google.gwt.event.dom.client.ScrollHandler; +import com.google.gwt.event.logical.shared.ResizeEvent; +import com.google.gwt.event.logical.shared.ResizeHandler; +import com.google.gwt.http.client.URL; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.History; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.Window.Location; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.Widget; +import com.google.web.bindery.event.shared.HandlerRegistration; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorHierarchyChangeEvent; +import com.vaadin.client.Focusable; +import com.vaadin.client.Paintable; +import com.vaadin.client.ResourceLoader; +import com.vaadin.client.ResourceLoader.ResourceLoadEvent; +import com.vaadin.client.ResourceLoader.ResourceLoadListener; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.UIDL; +import com.vaadin.client.Util; +import com.vaadin.client.VConsole; +import com.vaadin.client.ValueMap; +import com.vaadin.client.annotations.OnStateChange; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; +import com.vaadin.client.ui.AbstractConnector; +import com.vaadin.client.ui.AbstractSingleComponentContainerConnector; +import com.vaadin.client.ui.ClickEventHandler; +import com.vaadin.client.ui.ShortcutActionHandler; +import com.vaadin.client.ui.VNotification; +import com.vaadin.client.ui.VOverlay; +import com.vaadin.client.ui.VUI; +import com.vaadin.client.ui.VWindow; +import com.vaadin.client.ui.layout.MayScrollChildren; +import com.vaadin.client.ui.window.WindowConnector; +import com.vaadin.server.Page.Styles; +import com.vaadin.shared.ApplicationConstants; +import com.vaadin.shared.MouseEventDetails; +import com.vaadin.shared.Version; +import com.vaadin.shared.communication.MethodInvocation; +import com.vaadin.shared.ui.ComponentStateUtil; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.Connect.LoadStyle; +import com.vaadin.shared.ui.ui.DebugWindowClientRpc; +import com.vaadin.shared.ui.ui.DebugWindowServerRpc; +import com.vaadin.shared.ui.ui.PageClientRpc; +import com.vaadin.shared.ui.ui.PageState; +import com.vaadin.shared.ui.ui.ScrollClientRpc; +import com.vaadin.shared.ui.ui.UIClientRpc; +import com.vaadin.shared.ui.ui.UIConstants; +import com.vaadin.shared.ui.ui.UIServerRpc; +import com.vaadin.shared.ui.ui.UIState; +import com.vaadin.shared.util.SharedUtil; +import com.vaadin.ui.UI; + +@Connect(value = UI.class, loadStyle = LoadStyle.EAGER) +public class UIConnector extends AbstractSingleComponentContainerConnector + implements Paintable, MayScrollChildren { + + private HandlerRegistration childStateChangeHandlerRegistration; + + private String activeTheme = null; + + private final StateChangeHandler childStateChangeHandler = new StateChangeHandler() { + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + // TODO Should use a more specific handler that only reacts to + // size changes + onChildSizeChange(); + } + }; + + @Override + protected void init() { + super.init(); + registerRpc(PageClientRpc.class, new PageClientRpc() { + + @Override + public void reload() { + Window.Location.reload(); + + } + }); + registerRpc(ScrollClientRpc.class, new ScrollClientRpc() { + @Override + public void setScrollTop(int scrollTop) { + getWidget().getElement().setScrollTop(scrollTop); + } + + @Override + public void setScrollLeft(int scrollLeft) { + getWidget().getElement().setScrollLeft(scrollLeft); + } + }); + registerRpc(UIClientRpc.class, new UIClientRpc() { + @Override + public void uiClosed(final boolean sessionExpired) { + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + // Only notify user if we're still running and not eg. + // navigating away (#12298) + if (getConnection().isApplicationRunning()) { + if (sessionExpired) { + getConnection().showSessionExpiredError(null); + } else { + getState().enabled = false; + updateEnabledState(getState().enabled); + } + getConnection().setApplicationRunning(false); + } + } + }); + } + }); + registerRpc(DebugWindowClientRpc.class, new DebugWindowClientRpc() { + + @Override + public void reportLayoutProblems(String json) { + VConsole.printLayoutProblems(getValueMap(json), getConnection()); + } + + private native ValueMap getValueMap(String json) + /*-{ + return JSON.parse(json); + }-*/; + }); + + getWidget().addResizeHandler(new ResizeHandler() { + @Override + public void onResize(ResizeEvent event) { + getRpcProxy(UIServerRpc.class).resize(event.getHeight(), + event.getWidth(), Window.getClientWidth(), + Window.getClientHeight()); + if (getState().immediate || getPageState().hasResizeListeners) { + getConnection().getServerRpcQueue().flush(); + } + } + }); + getWidget().addScrollHandler(new ScrollHandler() { + private int lastSentScrollTop = Integer.MAX_VALUE; + private int lastSentScrollLeft = Integer.MAX_VALUE; + + @Override + public void onScroll(ScrollEvent event) { + Element element = getWidget().getElement(); + int newScrollTop = element.getScrollTop(); + int newScrollLeft = element.getScrollLeft(); + if (newScrollTop != lastSentScrollTop + || newScrollLeft != lastSentScrollLeft) { + lastSentScrollTop = newScrollTop; + lastSentScrollLeft = newScrollLeft; + getRpcProxy(UIServerRpc.class).scroll(newScrollTop, + newScrollLeft); + } + } + }); + } + + private native void open(String url, String name) + /*-{ + $wnd.open(url, name); + }-*/; + + @Override + public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) { + getWidget().id = getConnectorId(); + boolean firstPaint = getWidget().connection == null; + getWidget().connection = client; + + getWidget().immediate = getState().immediate; + getWidget().resizeLazy = uidl.hasAttribute(UIConstants.RESIZE_LAZY); + // this also implicitly removes old styles + String styles = ""; + styles += getWidget().getStylePrimaryName() + " "; + if (ComponentStateUtil.hasStyles(getState())) { + for (String style : getState().styles) { + styles += style + " "; + } + } + if (!client.getConfiguration().isStandalone()) { + styles += getWidget().getStylePrimaryName() + "-embedded"; + } + getWidget().setStyleName(styles.trim()); + + getWidget().makeScrollable(); + + clickEventHandler.handleEventHandlerRegistration(); + + // Process children + int childIndex = 0; + + // Open URL:s + boolean isClosed = false; // was this window closed? + while (childIndex < uidl.getChildCount() + && "open".equals(uidl.getChildUIDL(childIndex).getTag())) { + final UIDL open = uidl.getChildUIDL(childIndex); + final String url = client.translateVaadinUri(open + .getStringAttribute("src")); + final String target = open.getStringAttribute("name"); + if (target == null) { + // source will be opened to this browser window, but we may have + // to finish rendering this window in case this is a download + // (and window stays open). + Scheduler.get().scheduleDeferred(new Command() { + @Override + public void execute() { + VUI.goTo(url); + } + }); + } else if ("_self".equals(target)) { + // This window is closing (for sure). Only other opens are + // relevant in this change. See #3558, #2144 + isClosed = true; + VUI.goTo(url); + } else { + String options; + boolean alwaysAsPopup = true; + if (open.hasAttribute("popup")) { + alwaysAsPopup = open.getBooleanAttribute("popup"); + } + if (alwaysAsPopup) { + if (open.hasAttribute("border")) { + if (open.getStringAttribute("border").equals("minimal")) { + options = "menubar=yes,location=no,status=no"; + } else { + options = "menubar=no,location=no,status=no"; + } + + } else { + options = "resizable=yes,menubar=yes,toolbar=yes,directories=yes,location=yes,scrollbars=yes,status=yes"; + } + + if (open.hasAttribute("width")) { + int w = open.getIntAttribute("width"); + options += ",width=" + w; + } + if (open.hasAttribute("height")) { + int h = open.getIntAttribute("height"); + options += ",height=" + h; + } + + Window.open(url, target, options); + } else { + open(url, target); + } + } + childIndex++; + } + if (isClosed) { + // We're navigating away, so stop the application. + client.setApplicationRunning(false); + return; + } + + // Handle other UIDL children + UIDL childUidl; + while ((childUidl = uidl.getChildUIDL(childIndex++)) != null) { + String tag = childUidl.getTag().intern(); + if (tag == "actions") { + if (getWidget().actionHandler == null) { + getWidget().actionHandler = new ShortcutActionHandler( + getWidget().id, client); + } + getWidget().actionHandler.updateActionMap(childUidl); + } else if (tag == "notifications") { + for (final Iterator it = childUidl.getChildIterator(); it + .hasNext();) { + final UIDL notification = (UIDL) it.next(); + VNotification.showNotification(client, notification); + } + } else if (tag == "css-injections") { + injectCSS(childUidl); + } + } + + if (uidl.hasAttribute("focused")) { + // set focused component when render phase is finished + Scheduler.get().scheduleDeferred(new Command() { + @Override + public void execute() { + ComponentConnector connector = (ComponentConnector) uidl + .getPaintableAttribute("focused", getConnection()); + + if (connector == null) { + // Do not try to focus invisible components which not + // present in UIDL + return; + } + + final Widget toBeFocused = connector.getWidget(); + /* + * Two types of Widgets can be focused, either implementing + * GWT Focusable of a thinner Vaadin specific Focusable + * interface. + */ + if (toBeFocused instanceof com.google.gwt.user.client.ui.Focusable) { + final com.google.gwt.user.client.ui.Focusable toBeFocusedWidget = (com.google.gwt.user.client.ui.Focusable) toBeFocused; + toBeFocusedWidget.setFocus(true); + } else if (toBeFocused instanceof Focusable) { + ((Focusable) toBeFocused).focus(); + } else { + getLogger() + .severe("Server is trying to set focus to the widget of connector " + + Util.getConnectorString(connector) + + " but it is not focusable. The widget should implement either " + + com.google.gwt.user.client.ui.Focusable.class + .getName() + + " or " + + Focusable.class.getName()); + } + } + }); + } + + // Add window listeners on first paint, to prevent premature + // variablechanges + if (firstPaint) { + Window.addWindowClosingHandler(getWidget()); + Window.addResizeHandler(getWidget()); + } + + if (uidl.hasAttribute("scrollTo")) { + final ComponentConnector connector = (ComponentConnector) uidl + .getPaintableAttribute("scrollTo", getConnection()); + scrollIntoView(connector); + } + + if (uidl.hasAttribute(UIConstants.LOCATION_VARIABLE)) { + String location = uidl + .getStringAttribute(UIConstants.LOCATION_VARIABLE); + String newFragment; + + int fragmentIndex = location.indexOf('#'); + if (fragmentIndex >= 0) { + // Decode fragment to avoid double encoding (#10769) + newFragment = URL.decodePathSegment(location + .substring(fragmentIndex + 1)); + + if (newFragment.isEmpty() + && Location.getHref().indexOf('#') == -1) { + // Ensure there is a trailing # even though History and + // Location.getHash() treat null and "" the same way. + Location.assign(Location.getHref() + "#"); + } + } else { + // No fragment in server-side location, but can't completely + // remove the browser fragment since that would reload the page + newFragment = ""; + } + + getWidget().currentFragment = newFragment; + + if (!newFragment.equals(History.getToken())) { + History.newItem(newFragment, true); + } + } + + if (firstPaint) { + // Queue the initial window size to be sent with the following + // request. + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + getWidget().sendClientResized(); + } + }); + } + } + + /** + * Reads CSS strings and resources injected by {@link Styles#inject} from + * the UIDL stream. + * + * @param uidl + * The uidl which contains "css-resource" and "css-string" tags + */ + private void injectCSS(UIDL uidl) { + + /* + * Search the UIDL stream for CSS resources and strings to be injected. + */ + for (Iterator it = uidl.getChildIterator(); it.hasNext();) { + UIDL cssInjectionsUidl = (UIDL) it.next(); + + // Check if we have resources to inject + if (cssInjectionsUidl.getTag().equals("css-resource")) { + String url = getWidget().connection + .translateVaadinUri(cssInjectionsUidl + .getStringAttribute("url")); + LinkElement link = LinkElement.as(DOM + .createElement(LinkElement.TAG)); + link.setRel("stylesheet"); + link.setHref(url); + link.setType("text/css"); + getHead().appendChild(link); + // Check if we have CSS string to inject + } else if (cssInjectionsUidl.getTag().equals("css-string")) { + for (Iterator it2 = cssInjectionsUidl.getChildIterator(); it2 + .hasNext();) { + StyleInjector.injectAtEnd((String) it2.next()); + StyleInjector.flush(); + } + } + } + } + + /** + * Internal helper to get the tag of the page + * + * @since 7.3 + * @return the head element + */ + private HeadElement getHead() { + return HeadElement.as(Document.get() + .getElementsByTagName(HeadElement.TAG).getItem(0)); + } + + /** + * Internal helper for removing any stylesheet with the given URL + * + * @since 7.3 + * @param url + * the url to match with existing stylesheets + */ + private void removeStylesheet(String url) { + NodeList linkTags = getHead().getElementsByTagName( + LinkElement.TAG); + for (int i = 0; i < linkTags.getLength(); i++) { + LinkElement link = LinkElement.as(linkTags.getItem(i)); + if (!"stylesheet".equals(link.getRel())) { + continue; + } + if (!"text/css".equals(link.getType())) { + continue; + } + if (url.equals(link.getHref())) { + getHead().removeChild(link); + } + } + } + + public void init(String rootPanelId, + ApplicationConnection applicationConnection) { + // Create a style tag for style injections so they don't end up in + // the theme tag in IE8-IE10 (we don't want to wipe them out if we + // change theme). + // StyleInjectorImplIE always injects to the last style tag on the page. + if (BrowserInfo.get().isIE() + && BrowserInfo.get().getBrowserMajorVersion() < 11) { + StyleElement style = Document.get().createStyleElement(); + style.setType("text/css"); + getHead().appendChild(style); + } + + Widget shortcutContextWidget = getWidget(); + if (applicationConnection.getConfiguration().isStandalone()) { + // Listen to body for standalone apps (#19392) + shortcutContextWidget = RootPanel.get(); // document body + } + + shortcutContextWidget.addDomHandler(new KeyDownHandler() { + @Override + public void onKeyDown(KeyDownEvent event) { + if (getWidget().actionHandler != null) { + getWidget().actionHandler.handleKeyboardEvent((Event) event + .getNativeEvent().cast()); + } + } + }, KeyDownEvent.getType()); + + DOM.sinkEvents(getWidget().getElement(), Event.ONSCROLL); + + RootPanel root = RootPanel.get(rootPanelId); + + // Remove the v-app-loading or any splash screen added inside the div by + // the user + root.getElement().setInnerHTML(""); + + // Activate the initial theme by only adding the class name. Not calling + // activateTheme here as it will also cause a full layout and updates to + // the overlay container which has not yet been created at this point + activeTheme = applicationConnection.getConfiguration().getThemeName(); + root.addStyleName(activeTheme); + + root.add(getWidget()); + + // Set default tab index before focus call. State change handler + // will update this later if needed. + getWidget().setTabIndex(1); + + if (applicationConnection.getConfiguration().isStandalone()) { + // set focus to iview element by default to listen possible keyboard + // shortcuts. For embedded applications this is unacceptable as we + // don't want to steal focus from the main page nor we don't want + // side-effects from focusing (scrollIntoView). + getWidget().getElement().focus(); + } + + applicationConnection.addHandler( + ApplicationConnection.ApplicationStoppedEvent.TYPE, + new ApplicationConnection.ApplicationStoppedHandler() { + + @Override + public void onApplicationStopped( + ApplicationStoppedEvent event) { + // Stop any polling + if (pollTimer != null) { + pollTimer.cancel(); + pollTimer = null; + } + } + }); + } + + private ClickEventHandler clickEventHandler = new ClickEventHandler(this) { + + @Override + protected void fireClick(NativeEvent event, + MouseEventDetails mouseDetails) { + getRpcProxy(UIServerRpc.class).click(mouseDetails); + } + + }; + + private Timer pollTimer = null; + + @Override + public void updateCaption(ComponentConnector component) { + // NOP The main view never draws caption for its layout + } + + @Override + public VUI getWidget() { + return (VUI) super.getWidget(); + } + + @Override + protected ComponentConnector getContent() { + ComponentConnector connector = super.getContent(); + // VWindow (WindowConnector is its connector)is also a child component + // but it's never a content widget + if (connector instanceof WindowConnector) { + return null; + } else { + return connector; + } + } + + protected void onChildSizeChange() { + ComponentConnector child = getContent(); + if (child == null) { + return; + } + Style childStyle = child.getWidget().getElement().getStyle(); + /* + * Must set absolute position if the child has relative height and + * there's a chance of horizontal scrolling as some browsers will + * otherwise not take the scrollbar into account when calculating the + * height. Assuming v-ui does not have an undefined width for now, see + * #8460. + */ + if (child.isRelativeHeight() && !BrowserInfo.get().isIE9()) { + childStyle.setPosition(Position.ABSOLUTE); + } else { + childStyle.clearPosition(); + } + } + + /** + * Checks if the given sub window is a child of this UI Connector + * + * @deprecated Should be replaced by a more generic mechanism for getting + * non-ComponentConnector children + * @param wc + * @return + */ + @Deprecated + public boolean hasSubWindow(WindowConnector wc) { + return getChildComponents().contains(wc); + } + + /** + * Return an iterator for current subwindows. This method is meant for + * testing purposes only. + * + * @return + */ + public List getSubWindows() { + ArrayList windows = new ArrayList(); + for (ComponentConnector child : getChildComponents()) { + if (child instanceof WindowConnector) { + windows.add((WindowConnector) child); + } + } + return windows; + } + + @Override + public UIState getState() { + return (UIState) super.getState(); + } + + /** + * Returns the state of the Page associated with the UI. + *

+ * Note that state is considered an internal part of the connector. You + * should not rely on the state object outside of the connector who owns it. + * If you depend on the state of other connectors you should use their + * public API instead of their state object directly. The page state might + * not be an independent state object but can be embedded in UI state. + *

+ * + * @since 7.1 + * @return state object of the page + */ + public PageState getPageState() { + return getState().pageState; + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + ComponentConnector oldChild = null; + ComponentConnector newChild = getContent(); + + for (ComponentConnector c : event.getOldChildren()) { + if (!(c instanceof WindowConnector)) { + oldChild = c; + break; + } + } + + if (oldChild != newChild) { + if (childStateChangeHandlerRegistration != null) { + childStateChangeHandlerRegistration.removeHandler(); + childStateChangeHandlerRegistration = null; + } + if (newChild != null) { + getWidget().setWidget(newChild.getWidget()); + childStateChangeHandlerRegistration = newChild + .addStateChangeHandler(childStateChangeHandler); + // Must handle new child here as state change events are already + // fired + onChildSizeChange(); + } else { + getWidget().setWidget(null); + } + } + + for (ComponentConnector c : getChildComponents()) { + if (c instanceof WindowConnector) { + WindowConnector wc = (WindowConnector) c; + wc.setWindowOrderAndPosition(); + VWindow window = wc.getWidget(); + if (!window.isAttached()) { + + // Attach so that all widgets inside the Window are attached + // when their onStateChange is run + + // Made invisible here for legacy reasons and made visible + // at the end of stateChange. This dance could probably be + // removed + window.setVisible(false); + window.show(); + } + + } + } + + // Close removed sub windows + for (ComponentConnector c : event.getOldChildren()) { + if (c.getParent() != this && c instanceof WindowConnector) { + ((WindowConnector) c).getWidget().hide(); + } + } + } + + @Override + public boolean hasTooltip() { + /* + * Always return true so there's always top level tooltip handler that + * takes care of hiding tooltips whenever the mouse is moved somewhere + * else. + */ + return true; + } + + /** + * Tries to scroll the viewport so that the given connector is in view. + * + * @param componentConnector + * The connector which should be visible + * + */ + public void scrollIntoView(final ComponentConnector componentConnector) { + if (componentConnector == null) { + return; + } + + Scheduler.get().scheduleDeferred(new Command() { + @Override + public void execute() { + componentConnector.getWidget().getElement().scrollIntoView(); + } + }); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + if (stateChangeEvent.hasPropertyChanged("tooltipConfiguration")) { + getConnection().getVTooltip().setCloseTimeout( + getState().tooltipConfiguration.closeTimeout); + getConnection().getVTooltip().setOpenDelay( + getState().tooltipConfiguration.openDelay); + getConnection().getVTooltip().setQuickOpenDelay( + getState().tooltipConfiguration.quickOpenDelay); + getConnection().getVTooltip().setQuickOpenTimeout( + getState().tooltipConfiguration.quickOpenTimeout); + getConnection().getVTooltip().setMaxWidth( + getState().tooltipConfiguration.maxWidth); + } + + if (stateChangeEvent + .hasPropertyChanged("loadingIndicatorConfiguration")) { + getConnection().getLoadingIndicator().setFirstDelay( + getState().loadingIndicatorConfiguration.firstDelay); + getConnection().getLoadingIndicator().setSecondDelay( + getState().loadingIndicatorConfiguration.secondDelay); + getConnection().getLoadingIndicator().setThirdDelay( + getState().loadingIndicatorConfiguration.thirdDelay); + } + + if (stateChangeEvent.hasPropertyChanged("pollInterval")) { + configurePolling(); + } + + if (stateChangeEvent.hasPropertyChanged("pageState.title")) { + String title = getState().pageState.title; + if (title != null) { + com.google.gwt.user.client.Window.setTitle(title); + } + } + + if (stateChangeEvent.hasPropertyChanged("pushConfiguration")) { + getConnection().getMessageSender().setPushEnabled( + getState().pushConfiguration.mode.isEnabled()); + } + if (stateChangeEvent.hasPropertyChanged("reconnectDialogConfiguration")) { + getConnection().getConnectionStateHandler().configurationUpdated(); + } + + if (stateChangeEvent.hasPropertyChanged("overlayContainerLabel")) { + VOverlay.setOverlayContainerLabel(getConnection(), + getState().overlayContainerLabel); + } + } + + private void configurePolling() { + if (pollTimer != null) { + pollTimer.cancel(); + pollTimer = null; + } + if (getState().pollInterval >= 0) { + pollTimer = new Timer() { + @Override + public void run() { + if (getState().pollInterval < 0) { + // Polling has been cancelled server side + pollTimer.cancel(); + pollTimer = null; + return; + } + getRpcProxy(UIServerRpc.class).poll(); + // Send changes even though poll is @Delayed + getConnection().getServerRpcQueue().flush(); + } + }; + pollTimer.scheduleRepeating(getState().pollInterval); + } else { + // Ensure no more polls are sent as polling has been disabled + getConnection().getServerRpcQueue().removeMatching( + new MethodInvocation(getConnectorId(), UIServerRpc.class + .getName(), "poll")); + } + } + + /** + * Invokes the layout analyzer on the server + * + * @since 7.1 + */ + public void analyzeLayouts() { + getRpcProxy(DebugWindowServerRpc.class).analyzeLayouts(); + } + + /** + * Sends a request to the server to print details to console that will help + * the developer to locate the corresponding server-side connector in the + * source code. + * + * @since 7.1 + * @param serverConnector + * the connector to locate + */ + public void showServerDebugInfo(ServerConnector serverConnector) { + getRpcProxy(DebugWindowServerRpc.class).showServerDebugInfo( + serverConnector); + } + + /** + * Sends a request to the server to print a design to the console for the + * given component. + * + * @since 7.5 + * @param connector + * the component connector to output a declarative design for + */ + public void showServerDesign(ServerConnector connector) { + getRpcProxy(DebugWindowServerRpc.class).showServerDesign(connector); + } + + @OnStateChange("theme") + void onThemeChange() { + final String oldTheme = activeTheme; + final String newTheme = getState().theme; + final String oldThemeUrl = getThemeUrl(oldTheme); + final String newThemeUrl = getThemeUrl(newTheme); + + if (SharedUtil.equals(oldTheme, newTheme)) { + // This should only happen on the initial load when activeTheme has + // been updated in init. + + if (newTheme == null) { + return; + } + + // For the embedded case we cannot be 100% sure that the theme has + // been loaded and that the style names have been set. + + if (findStylesheetTag(oldThemeUrl) == null) { + // If there is no style tag, load it the normal way (the class + // name will be added when theme has been loaded) + replaceTheme(null, newTheme, null, newThemeUrl); + } else if (!getWidget().getParent().getElement() + .hasClassName(newTheme)) { + // If only the class name is missing, add that + activateTheme(newTheme); + } + return; + } + + getLogger().info("Changing theme from " + oldTheme + " to " + newTheme); + replaceTheme(oldTheme, newTheme, oldThemeUrl, newThemeUrl); + } + + /** + * Loads the new theme and removes references to the old theme + * + * @since 7.4.3 + * @param oldTheme + * The name of the old theme + * @param newTheme + * The name of the new theme + * @param oldThemeUrl + * The url of the old theme + * @param newThemeUrl + * The url of the new theme + */ + protected void replaceTheme(final String oldTheme, final String newTheme, + String oldThemeUrl, final String newThemeUrl) { + + LinkElement tagToReplace = null; + + if (oldTheme != null) { + tagToReplace = findStylesheetTag(oldThemeUrl); + + if (tagToReplace == null) { + getLogger() + .warning( + "Did not find the link tag for the old theme (" + + oldThemeUrl + + "), adding a new stylesheet for the new theme (" + + newThemeUrl + ")"); + } + } + + if (newTheme != null) { + loadTheme(newTheme, newThemeUrl, tagToReplace); + } else { + if (tagToReplace != null) { + tagToReplace.getParentElement().removeChild(tagToReplace); + } + + activateTheme(null); + } + + } + + private void updateVaadinFavicon(String newTheme) { + NodeList iconElements = querySelectorAll("link[rel~=\"icon\"]"); + for (int i = 0; i < iconElements.getLength(); i++) { + Element iconElement = iconElements.getItem(i); + + String href = iconElement.getAttribute("href"); + if (href != null && href.contains("VAADIN/themes") + && href.endsWith("/favicon.ico")) { + href = href.replaceFirst("VAADIN/themes/.+?/favicon.ico", + "VAADIN/themes/" + newTheme + "/favicon.ico"); + iconElement.setAttribute("href", href); + } + } + } + + private static native NodeList querySelectorAll(String selector) + /*-{ + return $doc.querySelectorAll(selector); + }-*/; + + /** + * Finds a link tag for a style sheet with the given URL + * + * @since 7.3 + * @param url + * the URL of the style sheet + * @return the link tag or null if no matching link tag was found + */ + private LinkElement findStylesheetTag(String url) { + NodeList linkTags = getHead().getElementsByTagName( + LinkElement.TAG); + for (int i = 0; i < linkTags.getLength(); i++) { + final LinkElement link = LinkElement.as(linkTags.getItem(i)); + if ("stylesheet".equals(link.getRel()) + && "text/css".equals(link.getType()) + && url.equals(link.getHref())) { + return link; + } + } + return null; + } + + /** + * Loads the given theme and replaces the given link element with the new + * theme link element. + * + * @param newTheme + * The name of the new theme + * @param newThemeUrl + * The url of the new theme + * @param tagToReplace + * The link element to replace. If null, then the new link + * element is added at the end. + */ + private void loadTheme(final String newTheme, final String newThemeUrl, + final LinkElement tagToReplace) { + LinkElement newThemeLinkElement = Document.get().createLinkElement(); + newThemeLinkElement.setRel("stylesheet"); + newThemeLinkElement.setType("text/css"); + newThemeLinkElement.setHref(newThemeUrl); + ResourceLoader.addOnloadHandler(newThemeLinkElement, + new ResourceLoadListener() { + + @Override + public void onLoad(ResourceLoadEvent event) { + getLogger().info( + "Loading of " + newTheme + " from " + + newThemeUrl + " completed"); + + if (tagToReplace != null) { + tagToReplace.getParentElement().removeChild( + tagToReplace); + } + activateTheme(newTheme); + } + + @Override + public void onError(ResourceLoadEvent event) { + getLogger().warning( + "Could not load theme from " + + getThemeUrl(newTheme)); + } + }, null); + + if (tagToReplace != null) { + getHead().insertBefore(newThemeLinkElement, tagToReplace); + } else { + getHead().appendChild(newThemeLinkElement); + } + } + + /** + * Activates the new theme. Assumes the theme has been loaded and taken into + * use in the browser. + * + * @since 7.4.3 + * @param newTheme + * The name of the new theme + */ + protected void activateTheme(String newTheme) { + if (activeTheme != null) { + getWidget().getParent().removeStyleName(activeTheme); + VOverlay.getOverlayContainer(getConnection()).removeClassName( + activeTheme); + } + + String oldThemeBase = getConnection().translateVaadinUri("theme://"); + + activeTheme = newTheme; + + if (newTheme != null) { + getWidget().getParent().addStyleName(newTheme); + VOverlay.getOverlayContainer(getConnection()).addClassName( + activeTheme); + + updateVaadinFavicon(newTheme); + + } + + // Request a full resynchronization from the server to deal with legacy + // components + getConnection().getMessageSender().resynchronize(); + + // Immediately update state and do layout while waiting for the resync + forceStateChangeRecursively(UIConnector.this); + getLayoutManager().forceLayout(); + } + + /** + * Force a full recursive recheck of every connector's state variables. + * + * @see #forceStateChange() + * + * @since 7.3 + */ + protected static void forceStateChangeRecursively( + AbstractConnector connector) { + connector.forceStateChange(); + + for (ServerConnector child : connector.getChildren()) { + if (child instanceof AbstractConnector) { + forceStateChangeRecursively((AbstractConnector) child); + } else { + getLogger().warning( + "Could not force state change for unknown connector type: " + + child.getClass().getName()); + } + } + + } + + /** + * Internal helper to get the theme URL for a given theme + * + * @since 7.3 + * @param theme + * the name of the theme + * @return The URL the theme can be loaded from + */ + private String getThemeUrl(String theme) { + String themeUrl = getConnection().translateVaadinUri( + ApplicationConstants.VAADIN_PROTOCOL_PREFIX + "themes/" + theme + + "/styles" + ".css"); + // Parameter appended to bypass caches after version upgrade. + themeUrl += "?v=" + Version.getFullVersion(); + return themeUrl; + + } + + /** + * Returns the name of the theme currently in used by the UI + * + * @since 7.3 + * @return the theme name used by this UI + */ + public String getActiveTheme() { + return activeTheme; + } + + private static Logger getLogger() { + return Logger.getLogger(UIConnector.class.getName()); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/upload/UploadConnector.java b/client/src/main/java/com/vaadin/client/ui/upload/UploadConnector.java new file mode 100644 index 0000000000..9e25770a17 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/upload/UploadConnector.java @@ -0,0 +1,112 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.upload; + +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.Paintable; +import com.vaadin.client.UIDL; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.VUpload; +import com.vaadin.shared.EventId; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.upload.UploadClientRpc; +import com.vaadin.shared.ui.upload.UploadServerRpc; +import com.vaadin.shared.ui.upload.UploadState; +import com.vaadin.ui.Upload; + +@Connect(Upload.class) +public class UploadConnector extends AbstractComponentConnector implements + Paintable { + + public UploadConnector() { + registerRpc(UploadClientRpc.class, new UploadClientRpc() { + @Override + public void submitUpload() { + getWidget().submit(); + } + }); + } + + @Override + protected void init() { + super.init(); + + getWidget().fu.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + if (hasEventListener(EventId.CHANGE)) { + getRpcProxy(UploadServerRpc.class).change( + getWidget().fu.getFilename()); + } + } + }); + } + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + if (uidl.hasAttribute("notStarted")) { + getWidget().t.schedule(400); + return; + } + getWidget().setImmediate(getState().immediate); + getWidget().client = client; + getWidget().paintableId = uidl.getId(); + getWidget().nextUploadId = uidl.getIntAttribute("nextid"); + final String action = client.translateVaadinUri(uidl + .getStringVariable("action")); + getWidget().element.setAction(action); + if (uidl.hasAttribute("buttoncaption")) { + getWidget().submitButton.setText(uidl + .getStringAttribute("buttoncaption")); + getWidget().submitButton.setVisible(true); + } else { + getWidget().submitButton.setVisible(false); + } + getWidget().fu.setName(getWidget().paintableId + "_file"); + + if (!isEnabled() || isReadOnly()) { + getWidget().disableUpload(); + } else if (!uidl.getBooleanAttribute("state")) { + // Enable the button only if an upload is not in progress + getWidget().enableUpload(); + getWidget().ensureTargetFrame(); + } + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().disableTitle(hasTooltip()); + } + + @Override + public VUpload getWidget() { + return (VUpload) super.getWidget(); + } + + @Override + public UploadState getState() { + return (UploadState) super.getState(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategy.java b/client/src/main/java/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategy.java new file mode 100644 index 0000000000..2c2e10594d --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategy.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.upload; + +import com.vaadin.client.ui.VUpload; + +public class UploadIFrameOnloadStrategy { + + public native void hookEvents(com.google.gwt.dom.client.Element iframe, + VUpload upload) + /*-{ + iframe.onload = $entry(function() { + upload.@com.vaadin.client.ui.VUpload::onSubmitComplete()(); + }); + }-*/; + + /** + * @param iframe + * the iframe whose onLoad event is to be cleaned + */ + public native void unHookEvents(com.google.gwt.dom.client.Element iframe) + /*-{ + iframe.onload = null; + }-*/; + +} diff --git a/client/src/main/java/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategyIE.java b/client/src/main/java/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategyIE.java new file mode 100644 index 0000000000..0c114e2ee7 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategyIE.java @@ -0,0 +1,42 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.upload; + +import com.google.gwt.dom.client.Element; +import com.vaadin.client.ui.VUpload; + +/** + * IE does not have onload, detect onload via readystatechange + * + */ +public class UploadIFrameOnloadStrategyIE extends UploadIFrameOnloadStrategy { + @Override + public native void hookEvents(Element iframe, VUpload upload) + /*-{ + iframe.onreadystatechange = $entry(function() { + if (iframe.readyState == 'complete') { + upload.@com.vaadin.client.ui.VUpload::onSubmitComplete()(); + } + }); + }-*/; + + @Override + public native void unHookEvents(Element iframe) + /*-{ + iframe.onreadystatechange = null; + }-*/; + +} diff --git a/client/src/main/java/com/vaadin/client/ui/video/VideoConnector.java b/client/src/main/java/com/vaadin/client/ui/video/VideoConnector.java new file mode 100644 index 0000000000..de53368d6a --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/video/VideoConnector.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.video; + +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.MediaBaseConnector; +import com.vaadin.client.ui.VVideo; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.video.VideoConstants; +import com.vaadin.shared.ui.video.VideoState; +import com.vaadin.ui.Video; + +@Connect(Video.class) +public class VideoConnector extends MediaBaseConnector { + + @Override + public VideoState getState() { + return (VideoState) super.getState(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + getWidget().setPoster(getResourceUrl(VideoConstants.POSTER_RESOURCE)); + } + + @Override + public VVideo getWidget() { + return (VVideo) super.getWidget(); + } + + @Override + protected String getDefaultAltHtml() { + return "Your browser does not support the video element."; + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/window/WindowConnector.java b/client/src/main/java/com/vaadin/client/ui/window/WindowConnector.java new file mode 100644 index 0000000000..9ea3c8bb68 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/window/WindowConnector.java @@ -0,0 +1,512 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.window; + +import java.util.logging.Logger; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.NodeList; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.DoubleClickEvent; +import com.google.gwt.event.dom.client.DoubleClickHandler; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Window; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorHierarchyChangeEvent; +import com.vaadin.client.LayoutManager; +import com.vaadin.client.Paintable; +import com.vaadin.client.UIDL; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.communication.RpcProxy; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractSingleComponentContainerConnector; +import com.vaadin.client.ui.ClickEventHandler; +import com.vaadin.client.ui.PostLayoutListener; +import com.vaadin.client.ui.ShortcutActionHandler; +import com.vaadin.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; +import com.vaadin.client.ui.SimpleManagedLayout; +import com.vaadin.client.ui.VWindow; +import com.vaadin.client.ui.layout.MayScrollChildren; +import com.vaadin.shared.MouseEventDetails; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.window.WindowMode; +import com.vaadin.shared.ui.window.WindowServerRpc; +import com.vaadin.shared.ui.window.WindowState; + +@Connect(value = com.vaadin.ui.Window.class) +public class WindowConnector extends AbstractSingleComponentContainerConnector + implements Paintable, BeforeShortcutActionListener, + SimpleManagedLayout, PostLayoutListener, MayScrollChildren, + WindowMoveHandler { + + private Node windowClone; + + private ClickEventHandler clickEventHandler = new ClickEventHandler(this) { + @Override + protected void fireClick(NativeEvent event, + MouseEventDetails mouseDetails) { + getRpcProxy(WindowServerRpc.class).click(mouseDetails); + } + }; + + abstract class WindowEventHandler implements ClickHandler, + DoubleClickHandler { + } + + private WindowEventHandler maximizeRestoreClickHandler = new WindowEventHandler() { + + @Override + public void onClick(ClickEvent event) { + final Element target = event.getNativeEvent().getEventTarget() + .cast(); + if (target == getWidget().maximizeRestoreBox) { + // Click on maximize/restore box + onMaximizeRestore(); + } + } + + @Override + public void onDoubleClick(DoubleClickEvent event) { + final Element target = event.getNativeEvent().getEventTarget() + .cast(); + if (getWidget().header.isOrHasChild(target)) { + // Double click on header + onMaximizeRestore(); + } + } + }; + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + @Override + protected void init() { + super.init(); + + VWindow window = getWidget(); + window.id = getConnectorId(); + window.client = getConnection(); + + getLayoutManager().registerDependency(this, + window.contentPanel.getElement()); + getLayoutManager().registerDependency(this, window.header); + getLayoutManager().registerDependency(this, window.footer); + + window.addHandler(maximizeRestoreClickHandler, ClickEvent.getType()); + window.addHandler(maximizeRestoreClickHandler, + DoubleClickEvent.getType()); + + window.setOwner(getConnection().getUIConnector().getWidget()); + + window.addMoveHandler(this); + } + + @Override + public void onUnregister() { + LayoutManager lm = getLayoutManager(); + VWindow window = getWidget(); + lm.unregisterDependency(this, window.contentPanel.getElement()); + lm.unregisterDependency(this, window.header); + lm.unregisterDependency(this, window.footer); + } + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + VWindow window = getWidget(); + String connectorId = getConnectorId(); + + // Workaround needed for Testing Tools (GWT generates window DOM + // slightly different in different browsers). + window.closeBox.setId(connectorId + "_window_close"); + window.maximizeRestoreBox + .setId(connectorId + "_window_maximizerestore"); + + window.visibilityChangesDisabled = true; + if (!isRealUpdate(uidl)) { + return; + } + window.visibilityChangesDisabled = false; + + // we may have actions + for (int i = 0; i < uidl.getChildCount(); i++) { + UIDL childUidl = uidl.getChildUIDL(i); + if (childUidl.getTag().equals("actions")) { + if (window.shortcutHandler == null) { + window.shortcutHandler = new ShortcutActionHandler( + connectorId, client); + } + window.shortcutHandler.updateActionMap(childUidl); + } + + } + + if (uidl.hasAttribute("bringToFront")) { + /* + * Focus as a side-effect. Will be overridden by + * ApplicationConnection if another component was focused by the + * server side. + */ + window.contentPanel.focus(); + window.bringToFrontSequence = uidl.getIntAttribute("bringToFront"); + VWindow.deferOrdering(); + } + } + + @Override + public void updateCaption(ComponentConnector component) { + // NOP, window has own caption, layout caption not rendered + } + + @Override + public void onBeforeShortcutAction(Event e) { + // NOP, nothing to update just avoid workaround ( causes excess + // blur/focus ) + } + + @Override + public VWindow getWidget() { + return (VWindow) super.getWidget(); + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + // We always have 1 child, unless the child is hidden + getWidget().contentPanel.setWidget(getContentWidget()); + + if (getParent() == null && windowClone != null) { + // If the window is removed from the UI, add the copy of the + // contents to the window (in case of an 'out-animation') + getWidget().getElement().removeAllChildren(); + getWidget().getElement().appendChild(windowClone); + + // Clean reference + windowClone = null; + } + + } + + @Override + public void layout() { + LayoutManager lm = getLayoutManager(); + VWindow window = getWidget(); + ComponentConnector content = getContent(); + boolean hasContent = (content != null); + Element contentElement = window.contentPanel.getElement(); + + Style contentStyle = window.contents.getStyle(); + + int headerHeight = lm.getOuterHeight(window.header); + contentStyle.setPaddingTop(headerHeight, Unit.PX); + contentStyle.setMarginTop(-headerHeight, Unit.PX); + + int footerHeight = lm.getOuterHeight(window.footer); + contentStyle.setPaddingBottom(footerHeight, Unit.PX); + contentStyle.setMarginBottom(-footerHeight, Unit.PX); + + int minWidth = lm.getOuterWidth(window.header) + - lm.getInnerWidth(window.header); + int minHeight = footerHeight + headerHeight; + + getWidget().getElement().getStyle().setPropertyPx("minWidth", minWidth); + getWidget().getElement().getStyle() + .setPropertyPx("minHeight", minHeight); + + /* + * Must set absolute position if the child has relative height and + * there's a chance of horizontal scrolling as some browsers will + * otherwise not take the scrollbar into account when calculating the + * height. + */ + if (hasContent) { + Element layoutElement = content.getWidget().getElement(); + Style childStyle = layoutElement.getStyle(); + + // IE8 needs some hackery to measure its content correctly + WidgetUtil.forceIE8Redraw(layoutElement); + + if (content.isRelativeHeight() && !BrowserInfo.get().isIE9()) { + childStyle.setPosition(Position.ABSOLUTE); + + Style wrapperStyle = contentElement.getStyle(); + if (window.getElement().getStyle().getWidth().length() == 0 + && !content.isRelativeWidth()) { + /* + * Need to lock width to make undefined width work even with + * absolute positioning + */ + int contentWidth = lm.getOuterWidth(layoutElement); + wrapperStyle.setWidth(contentWidth, Unit.PX); + } else { + wrapperStyle.clearWidth(); + } + } else { + childStyle.clearPosition(); + } + } + + } + + @Override + public void postLayout() { + VWindow window = getWidget(); + + if (!window.isAttached()) { + Logger.getLogger(WindowConnector.class.getName()).warning( + "Called postLayout to detached Window."); + return; + } + if (window.centered && getState().windowMode != WindowMode.MAXIMIZED) { + window.center(); + } + window.positionOrSizeUpdated(); + + if (getParent() != null) { + // Take a copy of the contents, since the server will detach all + // children of this window when it's closed, and the window will be + // emptied during the following hierarchy update (we need to keep + // the contents visible for the duration of a possible + // 'out-animation') + + // Fix for #14645 and #14785 - as soon as we clone audio and video + // tags, they start fetching data, and playing immediately in + // background, in case autoplay attribute is present. Therefore we + // have to replace them with stubs in the clone. And we can't just + // erase them, because there are corresponding player widgets to + // animate + windowClone = cloneNodeFilteringMedia(getWidget().getElement() + .getFirstChild()); + } + } + + private Node cloneNodeFilteringMedia(Node node) { + if (node instanceof Element) { + Element old = (Element) node; + if ("audio".equalsIgnoreCase(old.getTagName()) + || "video".equalsIgnoreCase(old.getTagName())) { + if (!old.hasAttribute("controls") + && "audio".equalsIgnoreCase(old.getTagName())) { + return null; // nothing to animate, so we won't add this to + // the clone + } + Element newEl = DOM.createElement(old.getTagName()); + if (old.hasAttribute("controls")) { + newEl.setAttribute("controls", old.getAttribute("controls")); + } + if (old.hasAttribute("style")) { + newEl.setAttribute("style", old.getAttribute("style")); + } + if (old.hasAttribute("class")) { + newEl.setAttribute("class", old.getAttribute("class")); + } + return newEl; + } + } + Node res = node.cloneNode(false); + if (node.hasChildNodes()) { + NodeList nl = node.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node clone = cloneNodeFilteringMedia(nl.getItem(i)); + if (clone != null) { + res.appendChild(clone); + } + } + } + return res; + } + + @Override + public WindowState getState() { + return (WindowState) super.getState(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + VWindow window = getWidget(); + WindowState state = getState(); + + if (state.modal != window.vaadinModality) { + window.setVaadinModality(!window.vaadinModality); + } + boolean resizeable = state.resizable + && state.windowMode == WindowMode.NORMAL; + window.setResizable(resizeable); + + window.resizeLazy = state.resizeLazy; + + window.setDraggable(state.draggable + && state.windowMode == WindowMode.NORMAL); + + window.updateMaximizeRestoreClassName(state.resizable, state.windowMode); + + // Caption must be set before required header size is measured. If + // the caption attribute is missing the caption should be cleared. + String iconURL = null; + if (getIconUri() != null) { + iconURL = getIconUri(); + } + + window.setAssistivePrefix(state.assistivePrefix); + window.setAssistivePostfix(state.assistivePostfix); + window.setCaption(state.caption, iconURL, getState().captionAsHtml); + + window.setWaiAriaRole(getState().role); + window.setAssistiveDescription(state.contentDescription); + + window.setTabStopEnabled(getState().assistiveTabStop); + window.setTabStopTopAssistiveText(getState().assistiveTabStopTopText); + window.setTabStopBottomAssistiveText(getState().assistiveTabStopBottomText); + + clickEventHandler.handleEventHandlerRegistration(); + + window.immediate = state.immediate; + + window.setClosable(!isReadOnly()); + // initialize position from state + updateWindowPosition(); + + // setting scrollposition must happen after children is rendered + window.contentPanel.setScrollPosition(state.scrollTop); + window.contentPanel.setHorizontalScrollPosition(state.scrollLeft); + + // Center this window on screen if requested + // This had to be here because we might not know the content size before + // everything is painted into the window + + // centered is this is unset on move/resize + window.centered = state.centered; + // Ensure centering before setting visible (#16486) + if (window.centered && getState().windowMode != WindowMode.MAXIMIZED) { + Scheduler.get().scheduleFinally(new ScheduledCommand() { + + @Override + public void execute() { + getWidget().center(); + } + }); + } + window.setVisible(true); + + // ensure window is not larger than browser window + if (window.getOffsetWidth() > Window.getClientWidth()) { + window.setWidth(Window.getClientWidth() + "px"); + } + if (window.getOffsetHeight() > Window.getClientHeight()) { + window.setHeight(Window.getClientHeight() + "px"); + } + } + + // Need to override default because of window mode + @Override + protected void updateComponentSize() { + if (getState().windowMode == WindowMode.NORMAL) { + super.updateComponentSize(); + } else if (getState().windowMode == WindowMode.MAXIMIZED) { + super.updateComponentSize("100%", "100%"); + } + } + + protected void updateWindowPosition() { + VWindow window = getWidget(); + WindowState state = getState(); + if (state.windowMode == WindowMode.NORMAL) { + // if centered, position handled in postLayout() + if (!state.centered + && (state.positionX >= 0 || state.positionY >= 0)) { + // If both positions are negative, then + // setWindowOrderAndPosition has already taken care of + // positioning the window so it stacks with other windows + window.setPopupPosition(state.positionX, state.positionY); + } + } else if (state.windowMode == WindowMode.MAXIMIZED) { + window.setPopupPositionNoUpdate(0, 0); + } + } + + protected void updateWindowMode() { + VWindow window = getWidget(); + WindowState state = getState(); + + // update draggable on widget + window.setDraggable(state.draggable + && state.windowMode == WindowMode.NORMAL); + // update resizable on widget + window.setResizable(state.resizable + && state.windowMode == WindowMode.NORMAL); + updateComponentSize(); + updateWindowPosition(); + window.updateMaximizeRestoreClassName(state.resizable, state.windowMode); + window.updateContentsSize(); + } + + protected void onMaximizeRestore() { + WindowState state = getState(); + if (state.resizable) { + if (state.windowMode == WindowMode.MAXIMIZED) { + state.windowMode = WindowMode.NORMAL; + } else { + state.windowMode = WindowMode.MAXIMIZED; + } + updateWindowMode(); + + VWindow window = getWidget(); + window.bringToFront(); + + getRpcProxy(WindowServerRpc.class).windowModeChanged( + state.windowMode); + } + } + + /** + * Gives the WindowConnector an order number. As a side effect, moves the + * window according to its order number so the windows are stacked. This + * method should be called for each window in the order they should appear. + */ + public void setWindowOrderAndPosition() { + getWidget().setWindowOrderAndPosition(); + } + + @Override + public boolean hasTooltip() { + /* + * Tooltip event handler always needed on the window widget to make sure + * tooltips are properly hidden. (#11448) + */ + return true; + } + + @Override + public void onWindowMove(WindowMoveEvent event) { + RpcProxy.create(WindowServerRpc.class, this).windowMoved( + event.getNewX(), event.getNewY()); + + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/window/WindowMoveEvent.java b/client/src/main/java/com/vaadin/client/ui/window/WindowMoveEvent.java new file mode 100644 index 0000000000..add7ee740f --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/window/WindowMoveEvent.java @@ -0,0 +1,82 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.window; + +import com.google.gwt.event.shared.GwtEvent; + +/** + * Event for window position updates + * + * @since 7.1.9 + * @author Vaadin Ltd + */ +public class WindowMoveEvent extends GwtEvent { + + private static final Type TYPE = new Type(); + + private final int newX; + private final int newY; + + /** + * Creates a new event with the given parameters + * + * @param x + * The new x-position for the VWindow + * @param y + * The new y-position for the VWindow + */ + public WindowMoveEvent(int x, int y) { + newX = x; + newY = y; + } + + /** + * Gets the new x position of the window + * + * @return the new X position of the VWindow + */ + public int getNewX() { + return newX; + } + + /** + * Gets the new y position of the window + * + * @return the new Y position of the VWindow + */ + public int getNewY() { + return newY; + } + + /** + * Gets the type of the event + * + * @return the type of the event + */ + public static Type getType() { + return TYPE; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(WindowMoveHandler handler) { + handler.onWindowMove(this); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/window/WindowMoveHandler.java b/client/src/main/java/com/vaadin/client/ui/window/WindowMoveHandler.java new file mode 100644 index 0000000000..0ac348af68 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/window/WindowMoveHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.window; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler for {@link WindowMoveEvent}s + * + * @since 7.1.9 + * @author Vaadin Ltd + */ +public interface WindowMoveHandler extends EventHandler { + + /** + * Called when the VWindow was moved by the user. + * + * @param event + * Contains new coordinates for the VWindow + */ + public void onWindowMove(WindowMoveEvent event); +} diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/Cell.java b/client/src/main/java/com/vaadin/client/widget/escalator/Cell.java new file mode 100644 index 0000000000..08dbcf6955 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/escalator/Cell.java @@ -0,0 +1,85 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.escalator; + +import com.google.gwt.dom.client.TableCellElement; + +/** + * Describes a cell + *

+ * It's a representation of the element in a grid cell, and its row and column + * indices. + *

+ * Unlike the {@link FlyweightRow}, an instance of {@link Cell} can be stored in + * a field. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class Cell { + + private final int row; + + private final int column; + + private final TableCellElement element; + + /** + * Constructs a new {@link Cell}. + * + * @param row + * The index of the row + * @param column + * The index of the column + * @param element + * The cell element + */ + public Cell(int row, int column, TableCellElement element) { + super(); + this.row = row; + this.column = column; + this.element = element; + } + + /** + * Returns the index of the row the cell resides in. + * + * @return the row index + * + */ + public int getRow() { + return row; + } + + /** + * Returns the index of the column the cell resides in. + * + * @return the column index + */ + public int getColumn() { + return column; + } + + /** + * Returns the element of the cell. + * + * @return the cell element + */ + public TableCellElement getElement() { + return element; + } + +} diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/ColumnConfiguration.java b/client/src/main/java/com/vaadin/client/widget/escalator/ColumnConfiguration.java new file mode 100644 index 0000000000..76f6a55b8a --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/escalator/ColumnConfiguration.java @@ -0,0 +1,196 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.widget.escalator; + +import java.util.Map; + +/** + * A representation of the columns in an instance of {@link Escalator}. + * + * @since 7.4 + * @author Vaadin Ltd + * @see Escalator#getColumnConfiguration() + */ +public interface ColumnConfiguration { + + /** + * Removes columns at certain indices. + *

+ * If any of the removed columns were frozen, the number of frozen columns + * will be reduced by the number of the removed columns that were frozen. + *

+ * Note: This method simply removes the given columns, and does not + * do much of anything else. Especially if you have column spans, you + * probably need to run {@link #refreshColumns(int, int)} or + * {@link RowContainer#refreshRows(int, int)} + * + * @param index + * the index of the first column to be removed + * @param numberOfColumns + * the number of rows to remove, starting from {@code index} + * @throws IndexOutOfBoundsException + * if the entire range of removed columns is not currently + * present in the escalator + * @throws IllegalArgumentException + * if numberOfColumns is less than 1. + */ + public void removeColumns(int index, int numberOfColumns) + throws IndexOutOfBoundsException, IllegalArgumentException; + + /** + * Adds columns at a certain index. + *

+ * The new columns will be inserted between the column at the index, and the + * column before (an index of 0 means that the columns are inserted at the + * beginning). Therefore, the columns at the index and afterwards will be + * moved to the right. + *

+ * The contents of the inserted columns will be queried from the respective + * cell renderers in the header, body and footer. + *

+ * If there are frozen columns and the first added column is to the left of + * the last frozen column, the number of frozen columns will be increased by + * the number of inserted columns. + *

+ * Note: Only the contents of the inserted columns will be + * rendered. If inserting new columns affects the contents of existing + * columns (e.g. you have column spans), + * {@link RowContainer#refreshRows(int, int)} or + * {@link #refreshColumns(int, int)} needs to be called as appropriate. + * + * @param index + * the index of the column before which new columns are inserted, + * or {@link #getColumnCount()} to add new columns at the end + * @param numberOfColumns + * the number of columns to insert after the index + * @throws IndexOutOfBoundsException + * if index is not an integer in the range + * [0..{@link #getColumnCount()}] + * @throws IllegalArgumentException + * if {@code numberOfColumns} is less than 1. + */ + public void insertColumns(int index, int numberOfColumns) + throws IndexOutOfBoundsException, IllegalArgumentException; + + /** + * Returns the number of columns in the escalator. + * + * @return the number of columns in the escalator + */ + public int getColumnCount(); + + /** + * Sets the number of leftmost columns that are not affected by horizontal + * scrolling. + * + * @param count + * the number of columns to freeze + * + * @throws IllegalArgumentException + * if the column count is < 0 or > the number of columns + * + */ + public void setFrozenColumnCount(int count) throws IllegalArgumentException; + + /** + * Get the number of leftmost columns that are not affected by horizontal + * scrolling. + * + * @return the number of frozen columns + */ + public int getFrozenColumnCount(); + + /** + * Sets (or unsets) an explicit width for a column. + * + * @param index + * the index of the column for which to set a width + * @param px + * the number of pixels the indicated column should be, or a + * negative number to let the escalator decide + * @throws IllegalArgumentException + * if index is not a valid column index + */ + public void setColumnWidth(int index, double px) + throws IllegalArgumentException; + + /** + * Returns the user-defined width of a column. + * + * @param index + * the index of the column for which to retrieve the width + * @return the column's width in pixels, or a negative number if the width + * is implicitly decided by the escalator + * @throws IllegalArgumentException + * if index is not a valid column index + */ + public double getColumnWidth(int index) throws IllegalArgumentException; + + /** + * Sets widths for a set of columns. + * + * @param indexWidthMap + * a map from column index to its respective width to be set. If + * the given width for a column index is negative, the column is + * resized-to-fit. + * @throws IllegalArgumentException + * if {@code indexWidthMap} is {@code null} + * @throws IllegalArgumentException + * if any column index in {@code indexWidthMap} is invalid + * @throws NullPointerException + * If any value in the map is null + */ + public void setColumnWidths(Map indexWidthMap) + throws IllegalArgumentException; + + /** + * Returns the actual width of a column. + * + * @param index + * the index of the column for which to retrieve the width + * @return the column's actual width in pixels + * @throws IllegalArgumentException + * if index is not a valid column index + */ + public double getColumnWidthActual(int index) + throws IllegalArgumentException; + + /** + * Refreshes a range of rows in the current row containers in each Escalator + * section. + *

+ * The data for the refreshed columns is queried from the current cell + * renderer. + * + * @param index + * the index of the first row that will be updated + * @param numberOfRows + * the number of rows to update, starting from the index + * @throws IndexOutOfBoundsException + * if any integer number in the range + * [index..(index+numberOfColumns)] is not an + * existing column index. + * @throws IllegalArgumentException + * if {@code numberOfColumns} is less than 1. + * @see RowContainer#setEscalatorUpdater(EscalatorUpdater) + * @see Escalator#getHeader() + * @see Escalator#getBody() + * @see Escalator#getFooter() + */ + public void refreshColumns(int index, int numberOfColumns) + throws IndexOutOfBoundsException, IllegalArgumentException; +} diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/EscalatorUpdater.java b/client/src/main/java/com/vaadin/client/widget/escalator/EscalatorUpdater.java new file mode 100644 index 0000000000..54507a7650 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/escalator/EscalatorUpdater.java @@ -0,0 +1,155 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.widget.escalator; + +/** + * An interface that allows client code to define how a certain row in Escalator + * will be displayed. The contents of an escalator's header, body and footer are + * rendered by their respective updaters. + *

+ * The updater is responsible for internally handling all remote communication, + * should the displayed data need to be fetched remotely. + *

+ * This has a similar function to {@link Grid Grid's} {@link Renderer Renderers} + * , although they operate on different abstraction levels. + * + * @since 7.4 + * @author Vaadin Ltd + * @see RowContainer#setEscalatorUpdater(EscalatorUpdater) + * @see Escalator#getHeader() + * @see Escalator#getBody() + * @see Escalator#getFooter() + * @see Renderer + */ +public interface EscalatorUpdater { + + /** + * An {@link EscalatorUpdater} that doesn't render anything. + */ + public static final EscalatorUpdater NULL = new EscalatorUpdater() { + @Override + public void update(final Row row, + final Iterable cellsToUpdate) { + // NOOP + } + + @Override + public void preAttach(final Row row, + final Iterable cellsToAttach) { + // NOOP + + } + + @Override + public void postAttach(final Row row, + final Iterable attachedCells) { + // NOOP + } + + @Override + public void preDetach(final Row row, + final Iterable cellsToDetach) { + // NOOP + } + + @Override + public void postDetach(final Row row, + final Iterable detachedCells) { + // NOOP + } + }; + + /** + * Renders a row contained in a row container. + *

+ * Note: If rendering of cells is deferred (e.g. because + * asynchronous data retrieval), this method is responsible for explicitly + * displaying some placeholder data (empty content is valid). Because the + * cells (and rows) in an escalator are recycled, failing to reset a cell's + * presentation will lead to wrong data being displayed in the escalator. + *

+ * For performance reasons, the escalator will never autonomously clear any + * data in a cell. + * + * @param row + * Information about the row that is being updated. + * Note: You should not store nor reuse this reference. + * @param cellsToUpdate + * A collection of cells that need to be updated. Note: + * You should neither store nor reuse the reference to the + * iterable, nor to the individual cells. + */ + public void update(Row row, Iterable cellsToUpdate); + + /** + * Called before attaching new cells to the escalator. + * + * @param row + * Information about the row to which the cells will be added. + * Note: You should not store nor reuse this reference. + * @param cellsToAttach + * A collection of cells that are about to be attached. + * Note: You should neither store nor reuse the + * reference to the iterable, nor to the individual cells. + * + */ + public void preAttach(Row row, Iterable cellsToAttach); + + /** + * Called after attaching new cells to the escalator. + * + * @param row + * Information about the row to which the cells were added. + * Note: You should not store nor reuse this reference. + * @param attachedCells + * A collection of cells that were attached. Note: You + * should neither store nor reuse the reference to the iterable, + * nor to the individual cells. + * + */ + public void postAttach(Row row, Iterable attachedCells); + + /** + * Called before detaching cells from the escalator. + * + * @param row + * Information about the row from which the cells will be + * removed. Note: You should not store nor reuse this + * reference. + * @param cellsToAttach + * A collection of cells that are about to be detached. + * Note: You should neither store nor reuse the + * reference to the iterable, nor to the individual cells. + * + */ + public void preDetach(Row row, Iterable cellsToDetach); + + /** + * Called after detaching cells from the escalator. + * + * @param row + * Information about the row from which the cells were removed. + * Note: You should not store nor reuse this reference. + * @param attachedCells + * A collection of cells that were detached. Note: You + * should neither store nor reuse the reference to the iterable, + * nor to the individual cells. + * + */ + public void postDetach(Row row, Iterable detachedCells); + +} diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/FlyweightCell.java b/client/src/main/java/com/vaadin/client/widget/escalator/FlyweightCell.java new file mode 100644 index 0000000000..b77b752327 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/escalator/FlyweightCell.java @@ -0,0 +1,201 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.escalator; + +import java.util.List; + +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.TableCellElement; +import com.vaadin.client.widget.escalator.FlyweightRow.CellIterator; +import com.vaadin.client.widgets.Escalator; + +/** + * A {@link FlyweightCell} represents a cell in the {@link Grid} or + * {@link Escalator} at a certain point in time. + * + *

+ * Since the {@link FlyweightCell} follows the Flyweight-pattern + * any instance of this object is subject to change without the user knowing it + * and so should not be stored anywhere outside of the method providing these + * instances. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class FlyweightCell { + public static final String COLSPAN_ATTR = "colSpan"; + + private final int column; + private final FlyweightRow row; + + private TableCellElement element = null; + private CellIterator currentIterator = null; + + public FlyweightCell(final FlyweightRow row, final int column) { + this.row = row; + this.column = column; + } + + /** + * Returns the row index of the cell + */ + public int getRow() { + assertSetup(); + return row.getRow(); + } + + /** + * Returns the column index of the cell + */ + public int getColumn() { + assertSetup(); + return column; + } + + /** + * Returns the element of the cell. Can be either a TD element + * or a TH element. + */ + public TableCellElement getElement() { + assertSetup(); + return element; + } + + /** + * Return the colspan attribute of the element of the cell. + */ + public int getColSpan() { + assertSetup(); + return element.getPropertyInt(COLSPAN_ATTR); + } + + /** + * Sets the DOM element for this FlyweightCell, either a TD or + * a TH. It is the caller's responsibility to actually insert + * the given element to the document when needed. + * + * @param element + * the element corresponding to this cell, cannot be null + */ + public void setElement(TableCellElement element) { + assert element != null; + assertSetup(); + this.element = element; + } + + void setup(final CellIterator iterator) { + currentIterator = iterator; + + if (iterator.areCellsAttached()) { + final TableCellElement e = row.getElement().getCells() + .getItem(column); + + assert e != null : "Cell " + column + " for logical row " + + row.getRow() + " doesn't exist in the DOM!"; + + e.setPropertyInt(COLSPAN_ATTR, 1); + if (row.getColumnWidth(column) >= 0) { + e.getStyle().setWidth(row.getColumnWidth(column), Unit.PX); + } + e.getStyle().clearDisplay(); + setElement(e); + } + } + + /** + * Tear down the state of the Cell. + *

+ * This is an internal check method, to prevent retrieving uninitialized + * data by calling {@link #getRow()}, {@link #getColumn()} or + * {@link #getElement()} at an improper time. + *

+ * This should only be used with asserts (" + * assert flyweightCell.teardown() ") so that the code is never + * run when asserts aren't enabled. + * + * @return always true + * @see FlyweightRow#teardown() + */ + boolean teardown() { + currentIterator = null; + element = null; + return true; + } + + /** + * Asserts that the flyweight cell has properly been set up before trying to + * access any of its data. + */ + private void assertSetup() { + assert currentIterator != null : "FlyweightCell was not properly " + + "initialized. This is either a bug in Grid/Escalator " + + "or a Cell reference has been stored and reused " + + "inappropriately."; + } + + public void setColSpan(final int numberOfCells) { + if (numberOfCells < 1) { + throw new IllegalArgumentException( + "Number of cells should be more than 0"); + } + + /*- + * This will default to 1 if unset, as per DOM specifications: + * http://www.w3.org/TR/html5/tabular-data.html#attributes-common-to-td-and-th-elements + */ + final int prevColSpan = getElement().getPropertyInt(COLSPAN_ATTR); + if (numberOfCells == 1 && prevColSpan == 1) { + return; + } + + getElement().setPropertyInt(COLSPAN_ATTR, numberOfCells); + adjustCellWidthForSpan(numberOfCells); + hideOrRevealAdjacentCellElements(numberOfCells, prevColSpan); + currentIterator.setSkipNext(numberOfCells - 1); + } + + private void adjustCellWidthForSpan(final int numberOfCells) { + final int cellsToTheRight = currentIterator.rawPeekNext( + numberOfCells - 1).size(); + + final double selfWidth = row.getColumnWidth(column); + double widthsOfColumnsToTheRight = 0; + for (int i = 0; i < cellsToTheRight; i++) { + widthsOfColumnsToTheRight += row.getColumnWidth(column + i + 1); + } + getElement().getStyle().setWidth(selfWidth + widthsOfColumnsToTheRight, + Unit.PX); + } + + private void hideOrRevealAdjacentCellElements(final int numberOfCells, + final int prevColSpan) { + final int affectedCellsNumber = Math.max(prevColSpan, numberOfCells); + final List affectedCells = currentIterator + .rawPeekNext(affectedCellsNumber - 1); + if (prevColSpan < numberOfCells) { + for (int i = 0; i < affectedCells.size(); i++) { + affectedCells.get(prevColSpan + i - 1).getElement().getStyle() + .setDisplay(Display.NONE); + } + } else if (prevColSpan > numberOfCells) { + for (int i = 0; i < affectedCells.size(); i++) { + affectedCells.get(numberOfCells + i - 1).getElement() + .getStyle().clearDisplay(); + } + } + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/FlyweightRow.java b/client/src/main/java/com/vaadin/client/widget/escalator/FlyweightRow.java new file mode 100644 index 0000000000..8628adb05f --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/escalator/FlyweightRow.java @@ -0,0 +1,294 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.escalator; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import com.google.gwt.dom.client.TableRowElement; + +/** + * An internal implementation of the {@link Row} interface. + *

+ * There is only one instance per Escalator. This is designed to be re-used when + * rendering rows. + * + * @since 7.4 + * @author Vaadin Ltd + * @see Escalator.AbstractRowContainer#refreshRow(Node, int) + */ +public class FlyweightRow implements Row { + + static class CellIterator implements Iterator { + /** A defensive copy of the cells in the current row. */ + private final ArrayList cells; + private final boolean cellsAttached; + private int cursor = 0; + private int skipNext = 0; + + /** + * Creates a new iterator of attached flyweight cells. A cell is + * attached if it has a corresponding {@link FlyweightCell#getElement() + * DOM element} attached to the row element. + * + * @param cells + * the collection of cells to iterate + */ + public static CellIterator attached( + final Collection cells) { + return new CellIterator(cells, true); + } + + /** + * Creates a new iterator of unattached flyweight cells. A cell is + * unattached if it does not have a corresponding + * {@link FlyweightCell#getElement() DOM element} attached to the row + * element. + * + * @param cells + * the collection of cells to iterate + */ + public static CellIterator unattached( + final Collection cells) { + return new CellIterator(cells, false); + } + + private CellIterator(final Collection cells, + final boolean attached) { + this.cells = new ArrayList(cells); + cellsAttached = attached; + } + + @Override + public boolean hasNext() { + return cursor + skipNext < cells.size(); + } + + @Override + public FlyweightCell next() { + // if we needed to skip some cells since the last invocation. + for (int i = 0; i < skipNext; i++) { + cells.remove(cursor); + } + skipNext = 0; + + final FlyweightCell cell = cells.get(cursor++); + cell.setup(this); + return cell; + } + + @Override + public void remove() { + throw new UnsupportedOperationException( + "Cannot remove cells via iterator"); + } + + /** + * Sets the number of cells to skip when {@link #next()} is called the + * next time. Cell hiding is also handled eagerly in this method. + * + * @param colspan + * the number of cells to skip on next invocation of + * {@link #next()} + */ + public void setSkipNext(final int colspan) { + assert colspan > 0 : "Number of cells didn't make sense: " + + colspan; + skipNext = colspan; + } + + /** + * Gets the next n cells in the iterator, ignoring any + * possibly spanned cells. + * + * @param n + * the number of next cells to retrieve + * @return A list of next n cells, or less if there aren't + * enough cells to retrieve + */ + public List rawPeekNext(final int n) { + final int from = Math.min(cursor, cells.size()); + final int to = Math.min(cursor + n, cells.size()); + List nextCells = cells.subList(from, to); + for (FlyweightCell cell : nextCells) { + cell.setup(this); + } + return nextCells; + } + + public boolean areCellsAttached() { + return cellsAttached; + } + } + + private static final int BLANK = Integer.MIN_VALUE; + + private int row; + private TableRowElement element; + private double[] columnWidths = null; + private final List cells = new ArrayList(); + + public void setup(final TableRowElement e, final int row, + double[] columnWidths) { + element = e; + this.row = row; + this.columnWidths = columnWidths; + } + + /** + * Tear down the state of the Row. + *

+ * This is an internal check method, to prevent retrieving uninitialized + * data by calling {@link #getRow()}, {@link #getElement()} or + * {@link #getCells()} at an improper time. + *

+ * This should only be used with asserts (" + * assert flyweightRow.teardown() ") so that the code is never + * run when asserts aren't enabled. + * + * @return always true + */ + public boolean teardown() { + element = null; + row = BLANK; + columnWidths = null; + for (final FlyweightCell cell : cells) { + assert cell.teardown(); + } + return true; + } + + @Override + public int getRow() { + assertSetup(); + return row; + } + + @Override + public TableRowElement getElement() { + assertSetup(); + return element; + } + + public void addCells(final int index, final int numberOfColumns) { + for (int i = 0; i < numberOfColumns; i++) { + final int col = index + i; + cells.add(col, new FlyweightCell(this, col)); + } + updateRestOfCells(index + numberOfColumns); + } + + public void removeCells(final int index, final int numberOfColumns) { + cells.subList(index, index + numberOfColumns).clear(); + updateRestOfCells(index); + } + + private void updateRestOfCells(final int startPos) { + // update the column number for the cells to the right + for (int col = startPos; col < cells.size(); col++) { + cells.set(col, new FlyweightCell(this, col)); + } + } + + /** + * Returns flyweight cells for the client code to render. The cells get + * their associated {@link FlyweightCell#getElement() elements} from the row + * element. + *

+ * Precondition: each cell has a corresponding element in the row + * + * @return an iterable of flyweight cells + * + * @see #setup(Element, int, int[]) + * @see #teardown() + */ + public Iterable getCells() { + return getCells(0, cells.size()); + } + + /** + * Returns a subrange of flyweight cells for the client code to render. The + * cells get their associated {@link FlyweightCell#getElement() elements} + * from the row element. + *

+ * Precondition: each cell has a corresponding element in the row + * + * @param offset + * the index of the first cell to return + * @param numberOfCells + * the number of cells to return + * @return an iterable of flyweight cells + */ + public Iterable getCells(final int offset, + final int numberOfCells) { + assertSetup(); + assert offset >= 0 && offset + numberOfCells <= cells.size() : "Invalid range of cells"; + return new Iterable() { + @Override + public Iterator iterator() { + return CellIterator.attached(cells.subList(offset, offset + + numberOfCells)); + } + }; + } + + /** + * Returns a subrange of unattached flyweight cells. Unattached cells do not + * have {@link FlyweightCell#getElement() elements} associated. Note that + * FlyweightRow does not keep track of whether cells in actuality have + * corresponding DOM elements or not; it is the caller's responsibility to + * invoke this method with correct parameters. + *

+ * Precondition: the range [offset, offset + numberOfCells) must be valid + * + * @param offset + * the index of the first cell to return + * @param numberOfCells + * the number of cells to return + * @return an iterable of flyweight cells + */ + public Iterable getUnattachedCells(final int offset, + final int numberOfCells) { + assertSetup(); + assert offset >= 0 && offset + numberOfCells <= cells.size() : "Invalid range of cells"; + return new Iterable() { + @Override + public Iterator iterator() { + return CellIterator.unattached(cells.subList(offset, offset + + numberOfCells)); + } + }; + } + + /** + * Asserts that the flyweight row has properly been set up before trying to + * access any of its data. + */ + private void assertSetup() { + assert element != null && row != BLANK && columnWidths != null : "Flyweight row was not " + + "properly initialized. Make sure the setup-method is " + + "called before retrieving data. This is either a bug " + + "in Escalator, or the instance of the flyweight row " + + "has been stored and accessed."; + } + + double getColumnWidth(int column) { + assertSetup(); + return columnWidths[column]; + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/PositionFunction.java b/client/src/main/java/com/vaadin/client/widget/escalator/PositionFunction.java new file mode 100644 index 0000000000..929f27df37 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/escalator/PositionFunction.java @@ -0,0 +1,118 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.widget.escalator; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Unit; + +/** + * A functional interface that can be used for positioning elements in the DOM. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface PositionFunction { + /** + * A position function using "transform: translate3d(x,y,z)" to position + * elements in the DOM. + */ + public static class Translate3DPosition implements PositionFunction { + @Override + public void set(Element e, double x, double y) { + e.getStyle().setProperty("transform", + "translate3d(" + x + "px, " + y + "px, 0)"); + } + + @Override + public void reset(Element e) { + e.getStyle().clearProperty("transform"); + } + } + + /** + * A position function using "transform: translate(x,y)" to position + * elements in the DOM. + */ + public static class TranslatePosition implements PositionFunction { + @Override + public void set(Element e, double x, double y) { + e.getStyle().setProperty("transform", + "translate(" + x + "px," + y + "px)"); + } + + @Override + public void reset(Element e) { + e.getStyle().clearProperty("transform"); + } + } + + /** + * A position function using "-webkit-transform: translate3d(x,y,z)" to + * position elements in the DOM. + */ + public static class WebkitTranslate3DPosition implements PositionFunction { + @Override + public void set(Element e, double x, double y) { + e.getStyle().setProperty("webkitTransform", + "translate3d(" + x + "px," + y + "px,0)"); + } + + @Override + public void reset(Element e) { + e.getStyle().clearProperty("webkitTransform"); + } + } + + /** + * A position function using "left: x" and "top: y" to position elements in + * the DOM. + */ + public static class AbsolutePosition implements PositionFunction { + @Override + public void set(Element e, double x, double y) { + e.getStyle().setLeft(x, Unit.PX); + e.getStyle().setTop(y, Unit.PX); + } + + @Override + public void reset(Element e) { + e.getStyle().clearLeft(); + e.getStyle().clearTop(); + } + } + + /** + * Position an element in an (x,y) coordinate system in the DOM. + * + * @param e + * the element to position. Never null. + * @param x + * the x coordinate, in pixels + * @param y + * the y coordinate, in pixels + */ + void set(Element e, double x, double y); + + /** + * Resets any previously applied positioning, clearing the used style + * attributes. + * + * @param e + * the element for which to reset the positioning + */ + void reset(Element e); +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/Row.java b/client/src/main/java/com/vaadin/client/widget/escalator/Row.java new file mode 100644 index 0000000000..fa89853120 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/escalator/Row.java @@ -0,0 +1,48 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.widget.escalator; + +import com.google.gwt.dom.client.TableRowElement; + +/** + * A representation of a row in an {@link Escalator}. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface Row { + /** + * Gets the row index. + * + * @return the row index + */ + public int getRow(); + + /** + * Gets the root element for this row. + *

+ * The {@link EscalatorUpdater} may update the class names of the element + * and add inline styles, but may not modify the contained DOM structure. + *

+ * If you wish to modify the cells within this row element, access them via + * the List<{@link Cell}> objects passed in to + * {@code EscalatorUpdater.updateCells(Row, List)} + * + * @return the root element of the row + */ + public TableRowElement getElement(); +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/RowContainer.java b/client/src/main/java/com/vaadin/client/widget/escalator/RowContainer.java new file mode 100644 index 0000000000..abab25046c --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/escalator/RowContainer.java @@ -0,0 +1,281 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.widget.escalator; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.TableRowElement; +import com.google.gwt.dom.client.TableSectionElement; + +/** + * A representation of the rows in each of the sections (header, body and + * footer) in an {@link com.vaadin.client.widgets.Escalator}. + * + * @since 7.4 + * @author Vaadin Ltd + * @see com.vaadin.client.widgets.Escalator#getHeader() + * @see com.vaadin.client.widgets.Escalator#getBody() + * @see com.vaadin.client.widgets.Escalator#getFooter() + * @see SpacerContainer + */ +public interface RowContainer { + + /** + * The row container for the body section in an + * {@link com.vaadin.client.widgets.Escalator}. + *

+ * The body section can contain both rows and spacers. + * + * @since 7.5.0 + * @author Vaadin Ltd + * @see com.vaadin.client.widgets.Escalator#getBody() + */ + public interface BodyRowContainer extends RowContainer { + + /** + * Marks a spacer and its height. + *

+ * If a spacer is already registered with the given row index, that + * spacer will be updated with the given height. + *

+ * Note: The row index for a spacer will change if rows are + * inserted or removed above the current position. Spacers will also be + * removed alongside their associated rows + * + * @param rowIndex + * the row index for the spacer to modify. The affected + * spacer is underneath the given index. Use -1 to insert a + * spacer before the first row + * @param height + * the pixel height of the spacer. If {@code height} is + * negative, the affected spacer (if exists) will be removed + * @throws IllegalArgumentException + * if {@code rowIndex} is not a valid row index + * @see #insertRows(int, int) + * @see #removeRows(int, int) + */ + void setSpacer(int rowIndex, double height) + throws IllegalArgumentException; + + /** + * Sets a new spacer updater. + *

+ * Spacers that are currently visible will be updated, i.e. + * {@link SpacerUpdater#destroy(Spacer) destroyed} with the previous + * one, and {@link SpacerUpdater#init(Spacer) initialized} with the new + * one. + * + * @param spacerUpdater + * the new spacer updater + * @throws IllegalArgumentException + * if {@code spacerUpdater} is {@code null} + */ + void setSpacerUpdater(SpacerUpdater spacerUpdater) + throws IllegalArgumentException; + + /** + * Gets the spacer updater currently in use. + *

+ * {@link SpacerUpdater#NULL} is the default. + * + * @return the spacer updater currently in use. Never null + */ + SpacerUpdater getSpacerUpdater(); + + /** + * {@inheritDoc} + *

+ * Any spacers underneath {@code index} will be offset and "pushed" + * down. This also modifies the row index they are associated with. + */ + @Override + public void insertRows(int index, int numberOfRows) + throws IndexOutOfBoundsException, IllegalArgumentException; + + /** + * {@inheritDoc} + *

+ * Any spacers underneath {@code index} will be offset and "pulled" up. + * This also modifies the row index they are associated with. Any + * spacers in the removed range will also be closed and removed. + */ + @Override + public void removeRows(int index, int numberOfRows) + throws IndexOutOfBoundsException, IllegalArgumentException; + } + + /** + * An arbitrary pixel height of a row, before any autodetection for the row + * height has been made. + * */ + public static final double INITIAL_DEFAULT_ROW_HEIGHT = 20; + + /** + * Returns the current {@link EscalatorUpdater} used to render cells. + * + * @return the current escalator updater + */ + public EscalatorUpdater getEscalatorUpdater(); + + /** + * Sets the {@link EscalatorUpdater} to use when displaying data in the + * escalator. + * + * @param escalatorUpdater + * the escalator updater to use to render cells. May not be + * null + * @throws IllegalArgumentException + * if {@code cellRenderer} is null + * @see EscalatorUpdater#NULL + */ + public void setEscalatorUpdater(EscalatorUpdater escalatorUpdater) + throws IllegalArgumentException; + + /** + * Removes rows at a certain index in the current row container. + * + * @param index + * the index of the first row to be removed + * @param numberOfRows + * the number of rows to remove, starting from the index + * @throws IndexOutOfBoundsException + * if any integer number in the range + * [index..(index+numberOfRows)] is not an existing + * row index + * @throws IllegalArgumentException + * if {@code numberOfRows} is less than 1. + */ + public void removeRows(int index, int numberOfRows) + throws IndexOutOfBoundsException, IllegalArgumentException; + + /** + * Adds rows at a certain index in this row container. + *

+ * The new rows will be inserted between the row at the index, and the row + * before (an index of 0 means that the rows are inserted at the beginning). + * Therefore, the rows currently at the index and afterwards will be moved + * downwards. + *

+ * The contents of the inserted rows will subsequently be queried from the + * escalator updater. + *

+ * Note: Only the contents of the inserted rows will be rendered. + * If inserting new rows affects the contents of existing rows, + * {@link #refreshRows(int, int)} needs to be called for those rows + * separately. + * + * @param index + * the index of the row before which new rows are inserted, or + * {@link #getRowCount()} to add rows at the end + * @param numberOfRows + * the number of rows to insert after the index + * @see #setEscalatorUpdater(EscalatorUpdater) + * @throws IndexOutOfBoundsException + * if index is not an integer in the range + * [0..{@link #getRowCount()}] + * @throws IllegalArgumentException + * if {@code numberOfRows} is less than 1. + */ + public void insertRows(int index, int numberOfRows) + throws IndexOutOfBoundsException, IllegalArgumentException; + + /** + * Refreshes a range of rows in the current row container. + *

+ * The data for the refreshed rows is queried from the current cell + * renderer. + * + * @param index + * the index of the first row that will be updated + * @param numberOfRows + * the number of rows to update, starting from the index + * @see #setEscalatorUpdater(EscalatorUpdater) + * @throws IndexOutOfBoundsException + * if any integer number in the range + * [index..(index+numberOfColumns)] is not an + * existing column index. + * @throws IllegalArgumentException + * if {@code numberOfRows} is less than 1. + */ + public void refreshRows(int index, int numberOfRows) + throws IndexOutOfBoundsException, IllegalArgumentException; + + /** + * Gets the number of rows in the current row container. + * + * @return the number of rows in the current row container + */ + public int getRowCount(); + + /** + * The default height of the rows in this RowContainer. + * + * @param px + * the default height in pixels of the rows in this RowContainer + * @throws IllegalArgumentException + * if px < 1 + * @see #getDefaultRowHeight() + */ + public void setDefaultRowHeight(double px) throws IllegalArgumentException; + + /** + * Returns the default height of the rows in this RowContainer. + *

+ * This value will be equal to {@link #INITIAL_DEFAULT_ROW_HEIGHT} if the + * {@link Escalator} has not yet had a chance to autodetect the row height, + * or no explicit value has yet given via {@link #setDefaultRowHeight(int)} + * + * @return the default height of the rows in this RowContainer, in pixels + * @see #setDefaultRowHeight(int) + */ + public double getDefaultRowHeight(); + + /** + * Returns the cell object which contains information about the cell the + * element is in. + * + * @param element + * The element to get the cell for. If element is not present in + * row container then null is returned. + * + * @return the cell of the element, or null if element is not + * present in the {@link RowContainer}. + */ + public Cell getCell(Element element); + + /** + * Gets the row element with given logical index. For lazy loaded containers + * such as Escalators BodyRowContainer visibility should be checked before + * calling this function. See {@link Escalator#getVisibleRowRange()}. + * + * @param index + * the logical index of the element to retrieve + * @return the element at position {@code index} + * @throws IndexOutOfBoundsException + * if {@code index} is not valid within container + * @throws IllegalStateException + * if {@code index} is currently not available in the DOM + */ + public TableRowElement getRowElement(int index) + throws IndexOutOfBoundsException, IllegalStateException; + + /** + * Returns the root element of RowContainer + * + * @return RowContainer root element + */ + public TableSectionElement getElement(); +} diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/RowVisibilityChangeEvent.java b/client/src/main/java/com/vaadin/client/widget/escalator/RowVisibilityChangeEvent.java new file mode 100644 index 0000000000..5f1531e378 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/escalator/RowVisibilityChangeEvent.java @@ -0,0 +1,99 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.widget.escalator; + +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.shared.ui.grid.Range; + +/** + * Event fired when the range of visible rows changes e.g. because of scrolling. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class RowVisibilityChangeEvent extends + GwtEvent { + /** + * The type of this event. + */ + public static final Type TYPE = new Type(); + + private final Range visibleRows; + + /** + * Creates a new row visibility change event + * + * @param firstVisibleRow + * the index of the first visible row + * @param visibleRowCount + * the number of visible rows + */ + public RowVisibilityChangeEvent(int firstVisibleRow, int visibleRowCount) { + visibleRows = Range.withLength(firstVisibleRow, visibleRowCount); + } + + /** + * Gets the index of the first row that is at least partially visible. + * + * @return the index of the first visible row + */ + public int getFirstVisibleRow() { + return visibleRows.getStart(); + } + + /** + * Gets the number of at least partially visible rows. + * + * @return the number of visible rows + */ + public int getVisibleRowCount() { + return visibleRows.length(); + } + + /** + * Gets the range of visible rows. + * + * @since 7.6 + * @return the visible rows + */ + public Range getVisibleRowRange() { + return visibleRows; + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.event.shared.GwtEvent#getAssociatedType() + */ + @Override + public Type getAssociatedType() { + return TYPE; + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.shared.GwtEvent#dispatch(com.google.gwt.event.shared + * .EventHandler) + */ + @Override + protected void dispatch(RowVisibilityChangeHandler handler) { + handler.onRowVisibilityChange(this); + } + +} diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/RowVisibilityChangeHandler.java b/client/src/main/java/com/vaadin/client/widget/escalator/RowVisibilityChangeHandler.java new file mode 100644 index 0000000000..80a30184c0 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/escalator/RowVisibilityChangeHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.widget.escalator; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Event handler that gets notified when the range of visible rows changes e.g. + * because of scrolling. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface RowVisibilityChangeHandler extends EventHandler { + + /** + * Called when the range of visible rows changes e.g. because of scrolling. + * + * @param event + * the row visibility change event describing the change + */ + void onRowVisibilityChange(RowVisibilityChangeEvent event); + +} diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/ScrollbarBundle.java b/client/src/main/java/com/vaadin/client/widget/escalator/ScrollbarBundle.java new file mode 100644 index 0000000000..958029889d --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/escalator/ScrollbarBundle.java @@ -0,0 +1,866 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.widget.escalator; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.event.shared.GwtEvent; +import com.google.gwt.event.shared.HandlerManager; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.EventListener; +import com.google.gwt.user.client.Timer; +import com.vaadin.client.DeferredWorker; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.widget.grid.events.ScrollEvent; +import com.vaadin.client.widget.grid.events.ScrollHandler; + +/** + * An element-like bundle representing a configurable and visual scrollbar in + * one axis. + * + * @since 7.4 + * @author Vaadin Ltd + * @see VerticalScrollbarBundle + * @see HorizontalScrollbarBundle + */ +public abstract class ScrollbarBundle implements DeferredWorker { + + private class ScrollEventFirer { + private final ScheduledCommand fireEventCommand = new ScheduledCommand() { + @Override + public void execute() { + + /* + * Some kind of native-scroll-event related asynchronous problem + * occurs here (at least on desktops) where the internal + * bookkeeping isn't up to date with the real scroll position. + * The weird thing is, that happens only once, and if you drag + * scrollbar fast enough. After it has failed once, it never + * fails again. + * + * Theory: the user drags the scrollbar, and this command is + * executed before the browser has a chance to fire a scroll + * event (which normally would correct this situation). This + * would explain why slow scrolling doesn't trigger the problem, + * while fast scrolling does. + * + * To make absolutely sure that we have the latest scroll + * position, let's update the internal value. + * + * This might lead to a slight performance hit (on my computer + * it was never more than 3ms on either of Chrome 38 or Firefox + * 31). It also _slightly_ counteracts the purpose of the + * internal bookkeeping. But since getScrollPos is called 3 + * times (on one direction) per scroll loop, it's still better + * to have take this small penalty than removing it altogether. + */ + updateScrollPosFromDom(); + + getHandlerManager().fireEvent(new ScrollEvent()); + isBeingFired = false; + } + }; + + private boolean isBeingFired; + + public void scheduleEvent() { + if (!isBeingFired) { + /* + * We'll gather all the scroll events, and only fire once, once + * everything has calmed down. + */ + Scheduler.get().scheduleDeferred(fireEventCommand); + isBeingFired = true; + } + } + } + + /** + * The orientation of the scrollbar. + */ + public enum Direction { + VERTICAL, HORIZONTAL; + } + + private class TemporaryResizer { + private static final int TEMPORARY_RESIZE_DELAY = 1000; + + private final Timer timer = new Timer() { + @Override + public void run() { + internalSetScrollbarThickness(1); + root.getStyle().setVisibility(Visibility.HIDDEN); + } + }; + + public void show() { + internalSetScrollbarThickness(OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX); + root.getStyle().setVisibility(Visibility.VISIBLE); + timer.schedule(TEMPORARY_RESIZE_DELAY); + } + } + + /** + * A means to listen to when the scrollbar handle in a + * {@link ScrollbarBundle} either appears or is removed. + */ + public interface VisibilityHandler extends EventHandler { + /** + * This method is called whenever the scrollbar handle's visibility is + * changed in a {@link ScrollbarBundle}. + * + * @param event + * the {@link VisibilityChangeEvent} + */ + void visibilityChanged(VisibilityChangeEvent event); + } + + public static class VisibilityChangeEvent extends + GwtEvent { + public static final Type TYPE = new Type() { + @Override + public String toString() { + return "VisibilityChangeEvent"; + } + }; + + private final boolean isScrollerVisible; + + private VisibilityChangeEvent(boolean isScrollerVisible) { + this.isScrollerVisible = isScrollerVisible; + } + + /** + * Checks whether the scroll handle is currently visible or not + * + * @return true if the scroll handle is currently visible. + * false if not. + */ + public boolean isScrollerVisible() { + return isScrollerVisible; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(VisibilityHandler handler) { + handler.visibilityChanged(this); + } + } + + /** + * The pixel size for OSX's invisible scrollbars. + *

+ * Touch devices don't show a scrollbar at all, so the scrollbar size is + * irrelevant in their case. There doesn't seem to be any other popular + * platforms that has scrollbars similar to OSX. Thus, this behavior is + * tailored for OSX only, until additional platforms start behaving this + * way. + */ + private static final int OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX = 13; + + /** + * A representation of a single vertical scrollbar. + * + * @see VerticalScrollbarBundle#getElement() + */ + public final static class VerticalScrollbarBundle extends ScrollbarBundle { + + @Override + public void setStylePrimaryName(String primaryStyleName) { + super.setStylePrimaryName(primaryStyleName); + root.addClassName(primaryStyleName + "-scroller-vertical"); + } + + @Override + protected void internalSetScrollPos(int px) { + root.setScrollTop(px); + } + + @Override + protected int internalGetScrollPos() { + return root.getScrollTop(); + } + + @Override + protected void internalSetScrollSize(double px) { + scrollSizeElement.getStyle().setHeight(px, Unit.PX); + } + + @Override + protected String internalGetScrollSize() { + return scrollSizeElement.getStyle().getHeight(); + } + + @Override + protected void internalSetOffsetSize(double px) { + root.getStyle().setHeight(px, Unit.PX); + } + + @Override + public String internalGetOffsetSize() { + return root.getStyle().getHeight(); + } + + @Override + protected void internalSetScrollbarThickness(double px) { + root.getStyle().setPaddingRight(px, Unit.PX); + root.getStyle().setWidth(0, Unit.PX); + scrollSizeElement.getStyle().setWidth(px, Unit.PX); + } + + @Override + protected String internalGetScrollbarThickness() { + return scrollSizeElement.getStyle().getWidth(); + } + + @Override + protected void internalForceScrollbar(boolean enable) { + if (enable) { + root.getStyle().setOverflowY(Overflow.SCROLL); + } else { + root.getStyle().clearOverflowY(); + } + } + + @Override + public Direction getDirection() { + return Direction.VERTICAL; + } + } + + /** + * A representation of a single horizontal scrollbar. + * + * @see HorizontalScrollbarBundle#getElement() + */ + public final static class HorizontalScrollbarBundle extends ScrollbarBundle { + + @Override + public void setStylePrimaryName(String primaryStyleName) { + super.setStylePrimaryName(primaryStyleName); + root.addClassName(primaryStyleName + "-scroller-horizontal"); + } + + @Override + protected void internalSetScrollPos(int px) { + root.setScrollLeft(px); + } + + @Override + protected int internalGetScrollPos() { + return root.getScrollLeft(); + } + + @Override + protected void internalSetScrollSize(double px) { + scrollSizeElement.getStyle().setWidth(px, Unit.PX); + } + + @Override + protected String internalGetScrollSize() { + return scrollSizeElement.getStyle().getWidth(); + } + + @Override + protected void internalSetOffsetSize(double px) { + root.getStyle().setWidth(px, Unit.PX); + } + + @Override + public String internalGetOffsetSize() { + return root.getStyle().getWidth(); + } + + @Override + protected void internalSetScrollbarThickness(double px) { + root.getStyle().setPaddingBottom(px, Unit.PX); + root.getStyle().setHeight(0, Unit.PX); + scrollSizeElement.getStyle().setHeight(px, Unit.PX); + } + + @Override + protected String internalGetScrollbarThickness() { + return scrollSizeElement.getStyle().getHeight(); + } + + @Override + protected void internalForceScrollbar(boolean enable) { + if (enable) { + root.getStyle().setOverflowX(Overflow.SCROLL); + } else { + root.getStyle().clearOverflowX(); + } + } + + @Override + public Direction getDirection() { + return Direction.HORIZONTAL; + } + } + + protected final Element root = DOM.createDiv(); + protected final Element scrollSizeElement = DOM.createDiv(); + protected boolean isInvisibleScrollbar = false; + + private double scrollPos = 0; + private double maxScrollPos = 0; + + private boolean scrollHandleIsVisible = false; + + private boolean isLocked = false; + + /** @deprecated access via {@link #getHandlerManager()} instead. */ + @Deprecated + private HandlerManager handlerManager; + + private TemporaryResizer invisibleScrollbarTemporaryResizer = new TemporaryResizer(); + + private final ScrollEventFirer scrollEventFirer = new ScrollEventFirer(); + + private HandlerRegistration scrollSizeTemporaryScrollHandler; + private HandlerRegistration offsetSizeTemporaryScrollHandler; + + private ScrollbarBundle() { + root.appendChild(scrollSizeElement); + root.getStyle().setDisplay(Display.NONE); + root.setTabIndex(-1); + } + + protected abstract String internalGetScrollSize(); + + /** + * Sets the primary style name + * + * @param primaryStyleName + * The primary style name to use + */ + public void setStylePrimaryName(String primaryStyleName) { + root.setClassName(primaryStyleName + "-scroller"); + } + + /** + * Gets the root element of this scrollbar-composition. + * + * @return the root element + */ + public final Element getElement() { + return root; + } + + /** + * Modifies the scroll position of this scrollbar by a number of pixels. + *

+ * Note: Even though {@code double} values are used, they are + * currently only used as integers as large {@code int} (or small but fast + * {@code long}). This means, all values are truncated to zero decimal + * places. + * + * @param delta + * the delta in pixels to change the scroll position by + */ + public final void setScrollPosByDelta(double delta) { + if (delta != 0) { + setScrollPos(getScrollPos() + delta); + } + } + + /** + * Modifies {@link #root root's} dimensions in the axis the scrollbar is + * representing. + * + * @param px + * the new size of {@link #root} in the dimension this scrollbar + * is representing + */ + protected abstract void internalSetOffsetSize(double px); + + /** + * Sets the length of the scrollbar. + * + * @param px + * the length of the scrollbar in pixels + */ + public final void setOffsetSize(final double px) { + + /* + * This needs to be made step-by-step because IE8 flat-out refuses to + * fire a scroll event when the scroll size becomes smaller than the + * offset size. All other browser need to suffer alongside. + */ + + boolean newOffsetSizeIsGreaterThanScrollSize = px > getScrollSize(); + boolean offsetSizeBecomesGreaterThanScrollSize = showsScrollHandle() + && newOffsetSizeIsGreaterThanScrollSize; + if (offsetSizeBecomesGreaterThanScrollSize && getScrollPos() != 0) { + // must be a field because Java insists. + offsetSizeTemporaryScrollHandler = addScrollHandler(new ScrollHandler() { + @Override + public void onScroll(ScrollEvent event) { + setOffsetSizeNow(px); + } + }); + setScrollPos(0); + } else { + setOffsetSizeNow(px); + } + } + + private void setOffsetSizeNow(double px) { + internalSetOffsetSize(Math.max(0, px)); + recalculateMaxScrollPos(); + forceScrollbar(showsScrollHandle()); + fireVisibilityChangeIfNeeded(); + if (offsetSizeTemporaryScrollHandler != null) { + offsetSizeTemporaryScrollHandler.removeHandler(); + offsetSizeTemporaryScrollHandler = null; + } + } + + /** + * Force the scrollbar to be visible with CSS. In practice, this means to + * set either overflow-x or overflow-y to " + * scroll" in the scrollbar's direction. + *

+ * This is an IE8 workaround, since it doesn't always show scrollbars with + * overflow: auto enabled. + */ + protected void forceScrollbar(boolean enable) { + if (enable) { + root.getStyle().clearDisplay(); + } else { + root.getStyle().setDisplay(Display.NONE); + } + internalForceScrollbar(enable); + } + + protected abstract void internalForceScrollbar(boolean enable); + + /** + * Gets the length of the scrollbar + * + * @return the length of the scrollbar in pixels + */ + public double getOffsetSize() { + return parseCssDimensionToPixels(internalGetOffsetSize()); + } + + public abstract String internalGetOffsetSize(); + + /** + * Sets the scroll position of the scrollbar in the axis the scrollbar is + * representing. + *

+ * Note: Even though {@code double} values are used, they are + * currently only used as integers as large {@code int} (or small but fast + * {@code long}). This means, all values are truncated to zero decimal + * places. + * + * @param px + * the new scroll position in pixels + */ + public final void setScrollPos(double px) { + if (isLocked()) { + return; + } + + double oldScrollPos = scrollPos; + scrollPos = Math.max(0, Math.min(maxScrollPos, truncate(px))); + + if (!WidgetUtil.pixelValuesEqual(oldScrollPos, scrollPos)) { + if (isInvisibleScrollbar) { + invisibleScrollbarTemporaryResizer.show(); + } + + /* + * This is where the value needs to be converted into an integer no + * matter how we flip it, since GWT expects an integer value. + * There's no point making a JSNI method that accepts doubles as the + * scroll position, since the browsers themselves don't support such + * large numbers (as of today, 25.3.2014). This double-ranged is + * only facilitating future virtual scrollbars. + */ + internalSetScrollPos(toInt32(scrollPos)); + } + } + + /** + * Should be called whenever this bundle is attached to the DOM (typically, + * from the onLoad of the containing widget). Used to ensure the DOM scroll + * position is maintained when detaching and reattaching the bundle. + * + * @since 7.4.1 + */ + public void onLoad() { + internalSetScrollPos(toInt32(scrollPos)); + } + + /** + * Truncates a double such that no decimal places are retained. + *

+ * E.g. {@code trunc(2.3d) == 2.0d} and {@code trunc(-2.3d) == -2.0d}. + * + * @param num + * the double value to be truncated + * @return the {@code num} value without any decimal digits + */ + private static double truncate(double num) { + if (num > 0) { + return Math.floor(num); + } else { + return Math.ceil(num); + } + } + + /** + * Modifies the element's scroll position (scrollTop or scrollLeft). + *

+ * Note: The parameter here is a type of integer (instead of a + * double) by design. The browsers internally convert all double values into + * an integer value. To make this fact explicit, this API has chosen to + * force integers already at this level. + * + * @param px + * integer pixel value to scroll to + */ + protected abstract void internalSetScrollPos(int px); + + /** + * Gets the scroll position of the scrollbar in the axis the scrollbar is + * representing. + * + * @return the new scroll position in pixels + */ + public final double getScrollPos() { + assert internalGetScrollPos() == toInt32(scrollPos) : "calculated scroll position (" + + toInt32(scrollPos) + + ") did not match the DOM element scroll position (" + + internalGetScrollPos() + ")"; + return scrollPos; + } + + /** + * Retrieves the element's scroll position (scrollTop or scrollLeft). + *

+ * Note: The parameter here is a type of integer (instead of a + * double) by design. The browsers internally convert all double values into + * an integer value. To make this fact explicit, this API has chosen to + * force integers already at this level. + * + * @return integer pixel value of the scroll position + */ + protected abstract int internalGetScrollPos(); + + /** + * Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in + * such a way that the scrollbar is able to scroll a certain number of + * pixels in the axis it is representing. + * + * @param px + * the new size of {@link #scrollSizeElement} in the dimension + * this scrollbar is representing + */ + protected abstract void internalSetScrollSize(double px); + + /** + * Sets the amount of pixels the scrollbar needs to be able to scroll + * through. + * + * @param px + * the number of pixels the scrollbar should be able to scroll + * through + */ + public final void setScrollSize(final double px) { + + /* + * This needs to be made step-by-step because IE8 flat-out refuses to + * fire a scroll event when the scroll size becomes smaller than the + * offset size. All other browser need to suffer alongside. + */ + + boolean newScrollSizeIsSmallerThanOffsetSize = px <= getOffsetSize(); + boolean scrollSizeBecomesSmallerThanOffsetSize = showsScrollHandle() + && newScrollSizeIsSmallerThanOffsetSize; + if (scrollSizeBecomesSmallerThanOffsetSize && getScrollPos() != 0) { + // must be a field because Java insists. + scrollSizeTemporaryScrollHandler = addScrollHandler(new ScrollHandler() { + @Override + public void onScroll(ScrollEvent event) { + setScrollSizeNow(px); + } + }); + setScrollPos(0); + } else { + setScrollSizeNow(px); + } + } + + private void setScrollSizeNow(double px) { + internalSetScrollSize(Math.max(0, px)); + recalculateMaxScrollPos(); + forceScrollbar(showsScrollHandle()); + fireVisibilityChangeIfNeeded(); + if (scrollSizeTemporaryScrollHandler != null) { + scrollSizeTemporaryScrollHandler.removeHandler(); + scrollSizeTemporaryScrollHandler = null; + } + } + + /** + * Gets the amount of pixels the scrollbar needs to be able to scroll + * through. + * + * @return the number of pixels the scrollbar should be able to scroll + * through + */ + public double getScrollSize() { + return parseCssDimensionToPixels(internalGetScrollSize()); + } + + /** + * Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in the + * opposite axis to what the scrollbar is representing. + * + * @param px + * the dimension that {@link #scrollSizeElement} should take in + * the opposite axis to what the scrollbar is representing + */ + protected abstract void internalSetScrollbarThickness(double px); + + /** + * Sets the scrollbar's thickness. + *

+ * If the thickness is set to 0, the scrollbar will be treated as an + * "invisible" scrollbar. This means, the DOM structure will be given a + * non-zero size, but {@link #getScrollbarThickness()} will still return the + * value 0. + * + * @param px + * the scrollbar's thickness in pixels + */ + public final void setScrollbarThickness(double px) { + isInvisibleScrollbar = (px == 0); + + if (isInvisibleScrollbar) { + Event.sinkEvents(root, Event.ONSCROLL); + Event.setEventListener(root, new EventListener() { + @Override + public void onBrowserEvent(Event event) { + invisibleScrollbarTemporaryResizer.show(); + } + }); + root.getStyle().setVisibility(Visibility.HIDDEN); + } else { + Event.sinkEvents(root, 0); + Event.setEventListener(root, null); + root.getStyle().clearVisibility(); + } + + internalSetScrollbarThickness(Math.max(1d, px)); + } + + /** + * Gets the scrollbar's thickness as defined in the DOM. + * + * @return the scrollbar's thickness as defined in the DOM, in pixels + */ + protected abstract String internalGetScrollbarThickness(); + + /** + * Gets the scrollbar's thickness. + *

+ * This value will differ from the value in the DOM, if the thickness was + * set to 0 with {@link #setScrollbarThickness(double)}, as the scrollbar is + * then treated as "invisible." + * + * @return the scrollbar's thickness in pixels + */ + public final double getScrollbarThickness() { + if (!isInvisibleScrollbar) { + return parseCssDimensionToPixels(internalGetScrollbarThickness()); + } else { + return 0; + } + } + + /** + * Checks whether the scrollbar's handle is visible. + *

+ * In other words, this method checks whether the contents is larger than + * can visually fit in the element. + * + * @return true iff the scrollbar's handle is visible + */ + public boolean showsScrollHandle() { + return getScrollSize() - getOffsetSize() > WidgetUtil.PIXEL_EPSILON; + } + + public void recalculateMaxScrollPos() { + double scrollSize = getScrollSize(); + double offsetSize = getOffsetSize(); + maxScrollPos = Math.max(0, scrollSize - offsetSize); + + // make sure that the correct max scroll position is maintained. + setScrollPos(scrollPos); + } + + /** + * This is a method that JSNI can call to synchronize the object state from + * the DOM. + */ + private final void updateScrollPosFromDom() { + + /* + * TODO: this method probably shouldn't be called from Escalator's JSNI, + * but probably could be handled internally by this listening to its own + * element. Would clean up the code quite a bit. Needs further + * investigation. + */ + + int newScrollPos = internalGetScrollPos(); + if (!isLocked()) { + scrollPos = newScrollPos; + scrollEventFirer.scheduleEvent(); + } else if (scrollPos != newScrollPos) { + // we need to actually undo the setting of the scroll. + internalSetScrollPos(toInt32(scrollPos)); + } + } + + protected HandlerManager getHandlerManager() { + if (handlerManager == null) { + handlerManager = new HandlerManager(this); + } + return handlerManager; + } + + /** + * Adds handler for the scrollbar handle visibility. + * + * @param handler + * the {@link VisibilityHandler} to add + * @return {@link HandlerRegistration} used to remove the handler + */ + public HandlerRegistration addVisibilityHandler( + final VisibilityHandler handler) { + return getHandlerManager().addHandler(VisibilityChangeEvent.TYPE, + handler); + } + + private void fireVisibilityChangeIfNeeded() { + final boolean oldHandleIsVisible = scrollHandleIsVisible; + scrollHandleIsVisible = showsScrollHandle(); + if (oldHandleIsVisible != scrollHandleIsVisible) { + final VisibilityChangeEvent event = new VisibilityChangeEvent( + scrollHandleIsVisible); + getHandlerManager().fireEvent(event); + } + } + + /** + * Converts a double into an integer by JavaScript's terms. + *

+ * Implementation copied from {@link Element#toInt32(double)}. + * + * @param val + * the double value to convert into an integer + * @return the double value converted to an integer + */ + private static native int toInt32(double val) + /*-{ + return val | 0; + }-*/; + + /** + * Locks or unlocks the scrollbar bundle. + *

+ * A locked scrollbar bundle will refuse to scroll, both programmatically + * and via user-triggered events. + * + * @param isLocked + * true to lock, false to unlock + */ + public void setLocked(boolean isLocked) { + this.isLocked = isLocked; + } + + /** + * Checks whether the scrollbar bundle is locked or not. + * + * @return true iff the scrollbar bundle is locked + */ + public boolean isLocked() { + return isLocked; + } + + /** + * Returns the scroll direction of this scrollbar bundle. + * + * @return the scroll direction of this scrollbar bundle + */ + public abstract Direction getDirection(); + + /** + * Adds a scroll handler to the scrollbar bundle. + * + * @param handler + * the handler to add + * @return the registration object for the handler registration + */ + public HandlerRegistration addScrollHandler(final ScrollHandler handler) { + return getHandlerManager().addHandler(ScrollEvent.TYPE, handler); + } + + private static double parseCssDimensionToPixels(String size) { + + /* + * Sizes of elements are calculated from CSS rather than + * element.getOffset*() because those values are 0 whenever display: + * none. Because we know that all elements have populated + * CSS-dimensions, it's better to do it that way. + * + * Another solution would be to make the elements visible while + * measuring and then re-hide them, but that would cause unnecessary + * reflows that would probably kill the performance dead. + */ + + if (size.isEmpty()) { + return 0; + } else { + assert size.endsWith("px") : "Can't parse CSS dimension \"" + size + + "\""; + return Double.parseDouble(size.substring(0, size.length() - 2)); + } + } + + @Override + public boolean isWorkPending() { + return scrollSizeTemporaryScrollHandler != null + || offsetSizeTemporaryScrollHandler != null; + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/Spacer.java b/client/src/main/java/com/vaadin/client/widget/escalator/Spacer.java new file mode 100644 index 0000000000..789a64a21e --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/escalator/Spacer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.escalator; + +import com.google.gwt.dom.client.Element; + +/** + * A representation of a spacer element in a + * {@link com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer}. + * + * @since 7.5.0 + * @author Vaadin Ltd + */ +public interface Spacer { + + /** + * Gets the root element for the spacer content. + * + * @return the root element for the spacer content + */ + Element getElement(); + + /** + * Gets the decorative element for this spacer. + */ + Element getDecoElement(); + + /** + * Gets the row index. + * + * @return the row index. + */ + int getRow(); +} diff --git a/client/src/main/java/com/vaadin/client/widget/escalator/SpacerUpdater.java b/client/src/main/java/com/vaadin/client/widget/escalator/SpacerUpdater.java new file mode 100644 index 0000000000..49adefd536 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/escalator/SpacerUpdater.java @@ -0,0 +1,64 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.escalator; + +import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; + +/** + * An interface that handles the display of content for spacers. + *

+ * The updater is responsible for making sure all elements are properly + * constructed and cleaned up. + * + * @since 7.5.0 + * @author Vaadin Ltd + * @see Spacer + * @see BodyRowContainer + */ +public interface SpacerUpdater { + + /** A spacer updater that does nothing. */ + public static final SpacerUpdater NULL = new SpacerUpdater() { + @Override + public void init(Spacer spacer) { + // NOOP + } + + @Override + public void destroy(Spacer spacer) { + // NOOP + } + }; + + /** + * Called whenever a spacer should be initialized with content. + * + * @param spacer + * the spacer reference that should be initialized + */ + void init(Spacer spacer); + + /** + * Called whenever a spacer should be cleaned. + *

+ * The structure to clean up is the same that has been constructed by + * {@link #init(Spacer)}. + * + * @param spacer + * the spacer reference that should be destroyed + */ + void destroy(Spacer spacer); +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/AutoScroller.java b/client/src/main/java/com/vaadin/client/widget/grid/AutoScroller.java new file mode 100644 index 0000000000..9cc238ac15 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/AutoScroller.java @@ -0,0 +1,643 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +import com.google.gwt.animation.client.AnimationScheduler; +import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback; +import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.TableElement; +import com.google.gwt.dom.client.TableSectionElement; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Event.NativePreviewHandler; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.widgets.Grid; + +/** + * A class for handling automatic scrolling vertically / horizontally in the + * Grid when the cursor is close enough the edge of the body of the grid, + * depending on the scroll direction chosen. + * + * @since 7.5.0 + * @author Vaadin Ltd + */ +public class AutoScroller { + + /** + * Callback that notifies when the cursor is on top of a new row or column + * because of the automatic scrolling. + */ + public interface AutoScrollerCallback { + + /** + * Triggered when doing automatic scrolling. + *

+ * Because the auto scroller currently only supports scrolling in one + * axis, this method is used for both vertical and horizontal scrolling. + * + * @param scrollDiff + * the amount of pixels that have been auto scrolled since + * last call + */ + void onAutoScroll(int scrollDiff); + + /** + * Triggered when the grid scroll has reached the minimum scroll + * position. Depending on the scroll axis, either scrollLeft or + * scrollTop is 0. + */ + void onAutoScrollReachedMin(); + + /** + * Triggered when the grid scroll has reached the max scroll position. + * Depending on the scroll axis, either scrollLeft or scrollTop is at + * its maximum value. + */ + void onAutoScrollReachedMax(); + } + + public enum ScrollAxis { + VERTICAL, HORIZONTAL + } + + /** The maximum number of pixels per second to autoscroll. */ + private static final int SCROLL_TOP_SPEED_PX_SEC = 500; + + /** + * The minimum area where the grid doesn't scroll while the pointer is + * pressed. + */ + private static final int MIN_NO_AUTOSCROLL_AREA_PX = 50; + + /** The size of the autoscroll area, both top/left and bottom/right. */ + private int scrollAreaPX = 100; + + /** + * This class's main objective is to listen when to stop autoscrolling, and + * make sure everything stops accordingly. + */ + private class TouchEventHandler implements NativePreviewHandler { + @Override + public void onPreviewNativeEvent(final NativePreviewEvent event) { + /* + * Remember: targetElement is always where touchstart started, not + * where the finger is pointing currently. + */ + switch (event.getTypeInt()) { + case Event.ONTOUCHSTART: { + if (event.getNativeEvent().getTouches().length() == 1) { + /* + * Something has dropped a touchend/touchcancel and the + * scroller is most probably running amok. Let's cancel it + * and pretend that everything's going as expected + * + * Because this is a preview, this code is run before start + * event can be passed to the start(...) method. + */ + stop(); + + /* + * Related TODO: investigate why iOS seems to ignore a + * touchend/touchcancel when frames are dropped, and/or if + * something can be done about that. + */ + } + break; + } + + case Event.ONTOUCHMOVE: + event.cancel(); + break; + + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + // TODO investigate if this works as desired + stop(); + break; + } + } + + } + + /** + * This class's responsibility is to scroll the table while a pointer is + * kept in a scrolling zone. + *

+ * Techical note: This class is an AnimationCallback because we + * need a timer: when the finger is kept in place while the grid scrolls, we + * still need to be able to make new selections. So, instead of relying on + * events (which won't be fired, since the pointer isn't necessarily + * moving), we do this check on each frame while the pointer is "active" + * (mouse is pressed, finger is on screen). + */ + private class AutoScrollingFrame implements AnimationCallback { + + /** + * If the acceleration gradient area is smaller than this, autoscrolling + * will be disabled (it becomes too quick to accelerate to be usable). + */ + private static final int GRADIENT_MIN_THRESHOLD_PX = 10; + + /** + * The speed at which the gradient area recovers, once scrolling in that + * direction has started. + */ + private static final int SCROLL_AREA_REBOUND_PX_PER_SEC = 1; + private static final double SCROLL_AREA_REBOUND_PX_PER_MS = SCROLL_AREA_REBOUND_PX_PER_SEC / 1000.0d; + + /** + * The lowest y/x-coordinate on the {@link Event#getClientY() client-y} + * or {@link Event#getClientX() client-x} from where we need to start + * scrolling towards the top/left. + */ + private int startBound = -1; + + /** + * The highest y/x-coordinate on the {@link Event#getClientY() client-y} + * or {@link Event#getClientX() client-x} from where we need to + * scrolling towards the bottom. + */ + private int endBound = -1; + + /** + * The area where the selection acceleration takes place. If < + * {@link #GRADIENT_MIN_THRESHOLD_PX}, autoscrolling is disabled + */ + private final int gradientArea; + + /** + * The number of pixels per seconds we currently are scrolling (negative + * is towards the top/left, positive is towards the bottom/right). + */ + private double scrollSpeed = 0; + + private double prevTimestamp = 0; + + /** + * This field stores fractions of pixels to scroll, to make sure that + * we're able to scroll less than one px per frame. + */ + private double pixelsToScroll = 0.0d; + + /** Should this animator be running. */ + private boolean running = false; + + /** The handle in which this instance is running. */ + private AnimationHandle handle; + + /** + * The pointer's pageY (VERTICAL) / pageX (HORIZONTAL) coordinate + * depending on scrolling axis. + */ + private int scrollingAxisPageCoordinate; + + /** @see #doScrollAreaChecks(int) */ + private int finalStartBound; + + /** @see #doScrollAreaChecks(int) */ + private int finalEndBound; + + private boolean scrollAreaShouldRebound = false; + + public AutoScrollingFrame(final int startBound, final int endBound, + final int gradientArea) { + finalStartBound = startBound; + finalEndBound = endBound; + this.gradientArea = gradientArea; + } + + @Override + public void execute(final double timestamp) { + final double timeDiff = timestamp - prevTimestamp; + prevTimestamp = timestamp; + + reboundScrollArea(timeDiff); + + pixelsToScroll += scrollSpeed * (timeDiff / 1000.0d); + final int intPixelsToScroll = (int) pixelsToScroll; + pixelsToScroll -= intPixelsToScroll; + + if (intPixelsToScroll != 0) { + double scrollPos; + double maxScrollPos; + double newScrollPos; + if (scrollDirection == ScrollAxis.VERTICAL) { + scrollPos = grid.getScrollTop(); + maxScrollPos = getMaxScrollTop(); + } else { + scrollPos = grid.getScrollLeft(); + maxScrollPos = getMaxScrollLeft(); + } + if (intPixelsToScroll > 0 && scrollPos < maxScrollPos + || intPixelsToScroll < 0 && scrollPos > 0) { + newScrollPos = scrollPos + intPixelsToScroll; + if (scrollDirection == ScrollAxis.VERTICAL) { + grid.setScrollTop(newScrollPos); + } else { + grid.setScrollLeft(newScrollPos); + } + callback.onAutoScroll(intPixelsToScroll); + if (newScrollPos <= 0) { + callback.onAutoScrollReachedMin(); + } else if (newScrollPos >= maxScrollPos) { + callback.onAutoScrollReachedMax(); + } + } + } + + reschedule(); + } + + /** + * If the scroll are has been offset by the pointer starting out there, + * move it back a bit + */ + private void reboundScrollArea(double timeDiff) { + if (!scrollAreaShouldRebound) { + return; + } + + int reboundPx = (int) Math.ceil(SCROLL_AREA_REBOUND_PX_PER_MS + * timeDiff); + if (startBound < finalStartBound) { + startBound += reboundPx; + startBound = Math.min(startBound, finalStartBound); + updateScrollSpeed(scrollingAxisPageCoordinate); + } else if (endBound > finalEndBound) { + endBound -= reboundPx; + endBound = Math.max(endBound, finalEndBound); + updateScrollSpeed(scrollingAxisPageCoordinate); + } + } + + private void updateScrollSpeed(final int pointerPageCordinate) { + + final double ratio; + if (pointerPageCordinate < startBound) { + final double distance = pointerPageCordinate - startBound; + ratio = Math.max(-1, distance / gradientArea); + } + + else if (pointerPageCordinate > endBound) { + final double distance = pointerPageCordinate - endBound; + ratio = Math.min(1, distance / gradientArea); + } + + else { + ratio = 0; + } + + scrollSpeed = ratio * SCROLL_TOP_SPEED_PX_SEC; + } + + public void start() { + running = true; + reschedule(); + } + + public void stop() { + running = false; + + if (handle != null) { + handle.cancel(); + handle = null; + } + } + + private void reschedule() { + if (running && gradientArea >= GRADIENT_MIN_THRESHOLD_PX) { + handle = AnimationScheduler.get().requestAnimationFrame(this, + grid.getElement()); + } + } + + public void updatePointerCoords(int pageX, int pageY) { + final int pageCordinate; + if (scrollDirection == ScrollAxis.VERTICAL) { + pageCordinate = pageY; + } else { + pageCordinate = pageX; + } + doScrollAreaChecks(pageCordinate); + updateScrollSpeed(pageCordinate); + scrollingAxisPageCoordinate = pageCordinate; + } + + /** + * This method checks whether the first pointer event started in an area + * that would start scrolling immediately, and does some actions + * accordingly. + *

+ * If it is, that scroll area will be offset "beyond" the pointer (above + * if pointer is towards the top/left, otherwise below/right). + */ + private void doScrollAreaChecks(int pageCordinate) { + /* + * The first run makes sure that neither scroll position is + * underneath the finger, but offset to either direction from + * underneath the pointer. + */ + if (startBound == -1) { + startBound = Math.min(finalStartBound, pageCordinate); + endBound = Math.max(finalEndBound, pageCordinate); + } + + /* + * Subsequent runs make sure that the scroll area grows (but doesn't + * shrink) with the finger, but no further than the final bound. + */ + else { + int oldTopBound = startBound; + if (startBound < finalStartBound) { + startBound = Math.max(startBound, + Math.min(finalStartBound, pageCordinate)); + } + + int oldBottomBound = endBound; + if (endBound > finalEndBound) { + endBound = Math.min(endBound, + Math.max(finalEndBound, pageCordinate)); + } + + final boolean startDidNotMove = oldTopBound == startBound; + final boolean endDidNotMove = oldBottomBound == endBound; + final boolean wasMovement = pageCordinate != scrollingAxisPageCoordinate; + scrollAreaShouldRebound = (startDidNotMove && endDidNotMove && wasMovement); + } + } + } + + /** + * This handler makes sure that pointer movements are handled. + *

+ * Essentially, a native preview handler is registered (so that selection + * gestures can happen outside of the selection column). The handler itself + * makes sure that it's detached when the pointer is "lifted". + */ + private final NativePreviewHandler scrollPreviewHandler = new NativePreviewHandler() { + @Override + public void onPreviewNativeEvent(final NativePreviewEvent event) { + if (autoScroller == null) { + stop(); + return; + } + + final NativeEvent nativeEvent = event.getNativeEvent(); + int pageY = 0; + int pageX = 0; + switch (event.getTypeInt()) { + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + pageY = WidgetUtil.getTouchOrMouseClientY(nativeEvent); + pageX = WidgetUtil.getTouchOrMouseClientX(nativeEvent); + autoScroller.updatePointerCoords(pageX, pageY); + break; + case Event.ONMOUSEUP: + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + stop(); + break; + } + } + }; + /** The registration info for {@link #scrollPreviewHandler} */ + private HandlerRegistration handlerRegistration; + + /** + * The top/left bound, as calculated from the {@link Event#getClientY() + * client-y} or {@link Event#getClientX() client-x} coordinates. + */ + private double startingBound = -1; + + /** + * The bottom/right bound, as calculated from the {@link Event#getClientY() + * client-y} or or {@link Event#getClientX() client-x} coordinates. + */ + private int endingBound = -1; + + /** The size of the autoscroll acceleration area. */ + private int gradientArea; + + private Grid grid; + + private HandlerRegistration nativePreviewHandlerRegistration; + + private ScrollAxis scrollDirection; + + private AutoScrollingFrame autoScroller; + + private AutoScrollerCallback callback; + + /** + * Creates a new instance for scrolling the given grid. + * + * @param grid + * the grid to auto scroll + */ + public AutoScroller(Grid grid) { + this.grid = grid; + } + + /** + * Starts the automatic scrolling detection. + * + * @param startEvent + * the event that starts the automatic scroll + * @param scrollAxis + * the axis along which the scrolling should happen + * @param callback + * the callback for getting info about the automatic scrolling + */ + public void start(final NativeEvent startEvent, ScrollAxis scrollAxis, + AutoScrollerCallback callback) { + scrollDirection = scrollAxis; + this.callback = callback; + injectNativeHandler(); + start(); + startEvent.preventDefault(); + startEvent.stopPropagation(); + } + + /** + * Stops the automatic scrolling. + */ + public void stop() { + if (handlerRegistration != null) { + handlerRegistration.removeHandler(); + handlerRegistration = null; + } + + if (autoScroller != null) { + autoScroller.stop(); + autoScroller = null; + } + + removeNativeHandler(); + } + + /** + * Set the auto scroll area height or width depending on the scrolling axis. + * This is the amount of pixels from the edge of the grid that the scroll is + * triggered. + *

+ * Defaults to 100px. + * + * @param px + * the pixel height/width for the auto scroll area depending on + * direction + */ + public void setScrollArea(int px) { + scrollAreaPX = px; + } + + /** + * Returns the size of the auto scroll area in pixels. + *

+ * Defaults to 100px. + * + * @return size in pixels + */ + public int getScrollArea() { + return scrollAreaPX; + } + + private void start() { + /* + * bounds are updated whenever the autoscroll cycle starts, to make sure + * that the widget hasn't changed in size, moved around, or whatnot. + */ + updateScrollBounds(); + + assert handlerRegistration == null : "handlerRegistration was not null"; + assert autoScroller == null : "autoScroller was not null"; + handlerRegistration = Event + .addNativePreviewHandler(scrollPreviewHandler); + autoScroller = new AutoScrollingFrame((int) Math.ceil(startingBound), + endingBound, gradientArea); + autoScroller.start(); + } + + private void updateScrollBounds() { + double startBorder = getBodyClientStart(); + final int endBorder = getBodyClientEnd(); + startBorder += getFrozenColumnsWidth(); + + startingBound = startBorder + scrollAreaPX; + endingBound = endBorder - scrollAreaPX; + gradientArea = scrollAreaPX; + + // modify bounds if they're too tightly packed + if (endingBound - startingBound < MIN_NO_AUTOSCROLL_AREA_PX) { + double adjustment = MIN_NO_AUTOSCROLL_AREA_PX + - (endingBound - startingBound); + startingBound -= adjustment / 2; + endingBound += adjustment / 2; + gradientArea -= adjustment / 2; + } + } + + private void injectNativeHandler() { + removeNativeHandler(); + nativePreviewHandlerRegistration = Event + .addNativePreviewHandler(new TouchEventHandler()); + } + + private void removeNativeHandler() { + if (nativePreviewHandlerRegistration != null) { + nativePreviewHandlerRegistration.removeHandler(); + nativePreviewHandlerRegistration = null; + } + } + + private TableElement getTableElement() { + final Element root = grid.getElement(); + final Element tablewrapper = Element.as(root.getChild(2)); + if (tablewrapper != null) { + return TableElement.as(tablewrapper.getFirstChildElement()); + } else { + return null; + } + } + + private TableSectionElement getTheadElement() { + TableElement table = getTableElement(); + if (table != null) { + return table.getTHead(); + } else { + return null; + } + } + + private TableSectionElement getTfootElement() { + TableElement table = getTableElement(); + if (table != null) { + return table.getTFoot(); + } else { + return null; + } + } + + private int getBodyClientEnd() { + if (scrollDirection == ScrollAxis.VERTICAL) { + return getTfootElement().getAbsoluteTop() - 1; + } else { + return getTableElement().getAbsoluteRight(); + } + + } + + private int getBodyClientStart() { + if (scrollDirection == ScrollAxis.VERTICAL) { + return getTheadElement().getAbsoluteBottom() + 1; + } else { + return getTableElement().getAbsoluteLeft(); + } + } + + public double getFrozenColumnsWidth() { + double value = 0; + + for (int i = 0; i < getRealFrozenColumnCount(); i++) { + value += grid.getColumn(i).getWidthActual(); + } + + return value; + } + + private int getRealFrozenColumnCount() { + if (grid.getFrozenColumnCount() < 0) { + return 0; + } else if (grid.getSelectionModel().getSelectionColumnRenderer() != null) { + // includes the selection column + return grid.getFrozenColumnCount() + 1; + } else { + return grid.getFrozenColumnCount(); + } + } + + private double getMaxScrollLeft() { + return grid.getScrollWidth() + - (getTableElement().getParentElement().getOffsetWidth() - getFrozenColumnsWidth()); + } + + private double getMaxScrollTop() { + return grid.getScrollHeight() - getTfootElement().getOffsetHeight() + - getTheadElement().getOffsetHeight(); + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/CellReference.java b/client/src/main/java/com/vaadin/client/widget/grid/CellReference.java new file mode 100644 index 0000000000..e783cb92ae --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/CellReference.java @@ -0,0 +1,151 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +import com.google.gwt.dom.client.TableCellElement; +import com.vaadin.client.widgets.Grid; + +/** + * A data class which contains information which identifies a cell in a + * {@link Grid}. + *

+ * Since this class follows the Flyweight-pattern any instance of + * this object is subject to change without the user knowing it and so should + * not be stored anywhere outside of the method providing these instances. + * + * @author Vaadin Ltd + * @param + * the type of the row object containing this cell + * @since 7.4 + */ +public class CellReference { + + private int columnIndexDOM; + private int columnIndex; + private Grid.Column column; + private final RowReference rowReference; + + public CellReference(RowReference rowReference) { + this.rowReference = rowReference; + } + + /** + * Sets the identifying information for this cell. + *

+ * The difference between {@link #columnIndexDOM} and {@link #columnIndex} + * comes from hidden columns. + * + * @param columnIndexDOM + * the index of the column in the DOM + * @param columnIndex + * the index of the column + * @param column + * the column object + */ + public void set(int columnIndexDOM, int columnIndex, + Grid.Column column) { + this.columnIndexDOM = columnIndexDOM; + this.columnIndex = columnIndex; + this.column = column; + } + + /** + * Gets the grid that contains the referenced cell. + * + * @return the grid that contains referenced cell + */ + public Grid getGrid() { + return rowReference.getGrid(); + } + + /** + * Gets the row index of the row. + * + * @return the index of the row + */ + public int getRowIndex() { + return rowReference.getRowIndex(); + } + + /** + * Gets the row data object. + * + * @return the row object + */ + public T getRow() { + return rowReference.getRow(); + } + + /** + * Gets the index of the column. + *

+ * NOTE: The index includes hidden columns in the count, unlike + * {@link #getColumnIndexDOM()}. + * + * @return the index of the column + */ + public int getColumnIndex() { + return columnIndex; + } + + /** + * Gets the index of the cell in the DOM. The difference to + * {@link #getColumnIndex()} is caused by hidden columns. + * + * @since 7.5.0 + * @return the index of the column in the DOM + */ + public int getColumnIndexDOM() { + return columnIndexDOM; + } + + /** + * Gets the column objects. + * + * @return the column object + */ + public Grid.Column getColumn() { + return column; + } + + /** + * Gets the value of the cell. + * + * @return the value of the cell + */ + public Object getValue() { + return getColumn().getValue(getRow()); + } + + /** + * Get the element of the cell. + * + * @return the element of the cell + */ + public TableCellElement getElement() { + return rowReference.getElement().getCells().getItem(columnIndexDOM); + } + + /** + * Gets the RowReference for this CellReference. + * + * @return the row reference + */ + protected RowReference getRowReference() { + return rowReference; + } + +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/CellStyleGenerator.java b/client/src/main/java/com/vaadin/client/widget/grid/CellStyleGenerator.java new file mode 100644 index 0000000000..bbc540de64 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/CellStyleGenerator.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +import com.vaadin.client.widgets.Grid; + +/** + * Callback interface for generating custom style names for cells + * + * @author Vaadin Ltd + * @param + * the row type of the target grid + * @see Grid#setCellStyleGenerator(CellStyleGenerator) + * @since 7.4 + */ +public interface CellStyleGenerator { + + /** + * Called by Grid to generate a style name for a column element. + * + * @param cellReference + * The cell to generate a style for + * @return the style name to add to this cell, or {@code null} to not set + * any style + */ + public abstract String getStyle(CellReference cellReference); +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/DataAvailableEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/DataAvailableEvent.java new file mode 100644 index 0000000000..d88fce4e11 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/DataAvailableEvent.java @@ -0,0 +1,55 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.shared.ui.grid.Range; + +/** + * Event object describing a change of row availability in DataSource of a Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class DataAvailableEvent extends GwtEvent { + + private Range rowsAvailable; + public static final Type TYPE = new Type(); + + public DataAvailableEvent(Range rowsAvailable) { + this.rowsAvailable = rowsAvailable; + } + + /** + * Returns the range of available rows in {@link DataSource} for this event. + * + * @return range of available rows + */ + public Range getAvailableRows() { + return rowsAvailable; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(DataAvailableHandler handler) { + handler.onDataAvailable(this); + } + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/DataAvailableHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/DataAvailableHandler.java new file mode 100644 index 0000000000..5e0650bc41 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/DataAvailableHandler.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler for {@link DataAvailableEvent}s. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface DataAvailableHandler extends EventHandler { + + /** + * Called when DataSource has data available. Supplied with row range. + * + * @param availableRows + * Range of rows available in the DataSource + * @return true if the command was successfully completed, false to call + * again the next time new data is available + */ + public void onDataAvailable(DataAvailableEvent event); +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java new file mode 100644 index 0000000000..e4a8783f54 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java @@ -0,0 +1,328 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +import com.google.gwt.core.client.Duration; +import com.google.gwt.dom.client.BrowserEvents; +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.FocusUtil; +import com.vaadin.client.widgets.Grid.Editor; +import com.vaadin.client.widgets.Grid.EditorDomEvent; + +/** + * The default handler for Grid editor events. Offers several overridable + * protected methods for easier customization. + * + * @since 7.6 + * @author Vaadin Ltd + */ +public class DefaultEditorEventHandler implements Editor.EventHandler { + + public static final int KEYCODE_OPEN = KeyCodes.KEY_ENTER; + public static final int KEYCODE_MOVE_VERTICAL = KeyCodes.KEY_ENTER; + public static final int KEYCODE_CLOSE = KeyCodes.KEY_ESCAPE; + public static final int KEYCODE_MOVE_HORIZONTAL = KeyCodes.KEY_TAB; + public static final int KEYCODE_BUFFERED_SAVE = KeyCodes.KEY_ENTER; + + private double lastTouchEventTime = 0; + private int lastTouchEventX = -1; + private int lastTouchEventY = -1; + private int lastTouchEventRow = -1; + + /** + * Returns whether the given event is a touch event that should open the + * editor. + * + * @param event + * the received event + * @return whether the event is a touch open event + */ + protected boolean isTouchOpenEvent(EditorDomEvent event) { + final Event e = event.getDomEvent(); + final int type = e.getTypeInt(); + + final double now = Duration.currentTimeMillis(); + final int currentX = WidgetUtil.getTouchOrMouseClientX(e); + final int currentY = WidgetUtil.getTouchOrMouseClientY(e); + + final boolean validTouchOpenEvent = type == Event.ONTOUCHEND + && now - lastTouchEventTime < 500 + && lastTouchEventRow == event.getCell().getRowIndex() + && Math.abs(lastTouchEventX - currentX) < 20 + && Math.abs(lastTouchEventY - currentY) < 20; + + if (type == Event.ONTOUCHSTART) { + lastTouchEventX = currentX; + lastTouchEventY = currentY; + } + + if (type == Event.ONTOUCHEND) { + lastTouchEventTime = now; + lastTouchEventRow = event.getCell().getRowIndex(); + } + + return validTouchOpenEvent; + } + + /** + * Returns whether the given event should open the editor. The default + * implementation returns true if and only if the event is a doubleclick or + * if it is a keydown event and the keycode is {@link #KEYCODE_OPEN}. + * + * @param event + * the received event + * @return true if the event is an open event, false otherwise + */ + protected boolean isOpenEvent(EditorDomEvent event) { + final Event e = event.getDomEvent(); + return e.getTypeInt() == Event.ONDBLCLICK + || (e.getTypeInt() == Event.ONKEYDOWN && e.getKeyCode() == KEYCODE_OPEN) + || isTouchOpenEvent(event); + } + + /** + * Opens the editor on the appropriate row if the received event is an open + * event. The default implementation uses + * {@link #isOpenEvent(EditorDomEvent) isOpenEvent}. + * + * @param event + * the received event + * @return true if this method handled the event and nothing else should be + * done, false otherwise + */ + protected boolean handleOpenEvent(EditorDomEvent event) { + if (isOpenEvent(event)) { + final EventCellReference cell = event.getCell(); + + editRow(event, cell.getRowIndex(), cell.getColumnIndexDOM()); + + event.getDomEvent().preventDefault(); + + return true; + } + return false; + } + + /** + * Moves the editor to another row or another column if the received event + * is a move event. The default implementation moves the editor to the + * clicked row if the event is a click; otherwise, if the event is a keydown + * and the keycode is {@link #KEYCODE_MOVE_VERTICAL}, moves the editor one + * row up or down if the shift key is pressed or not, respectively. Keydown + * event with keycode {@link #KEYCODE_MOVE_HORIZONTAL} moves the editor left + * or right if shift key is pressed or not, respectively. + * + * @param event + * the received event + * @return true if this method handled the event and nothing else should be + * done, false otherwise + */ + protected boolean handleMoveEvent(EditorDomEvent event) { + Event e = event.getDomEvent(); + final EventCellReference cell = event.getCell(); + + // TODO: Move on touch events + if (e.getTypeInt() == Event.ONCLICK) { + + editRow(event, cell.getRowIndex(), cell.getColumnIndexDOM()); + + return true; + } + + else if (e.getTypeInt() == Event.ONKEYDOWN) { + + int rowDelta = 0; + int colDelta = 0; + + if (e.getKeyCode() == KEYCODE_MOVE_VERTICAL) { + rowDelta = (e.getShiftKey() ? -1 : +1); + } else if (e.getKeyCode() == KEYCODE_MOVE_HORIZONTAL) { + colDelta = (e.getShiftKey() ? -1 : +1); + // Prevent tab out of Grid Editor + event.getDomEvent().preventDefault(); + } + + final boolean changed = rowDelta != 0 || colDelta != 0; + + if (changed) { + + int columnCount = event.getGrid().getVisibleColumns().size(); + + int colIndex = event.getFocusedColumnIndex() + colDelta; + int rowIndex = event.getRowIndex(); + + // Handle row change with horizontal move when column goes out + // of range. + if (rowDelta == 0) { + if (colIndex >= columnCount + && rowIndex < event.getGrid().getDataSource() + .size() - 1) { + rowDelta = 1; + colIndex = 0; + } else if (colIndex < 0 && rowIndex > 0) { + rowDelta = -1; + colIndex = columnCount - 1; + } + } + + editRow(event, rowIndex + rowDelta, colIndex); + } + + return changed; + } + + return false; + } + + /** + * Moves the editor to another column if the received event is a move event. + * By default the editor is moved on a keydown event with keycode + * {@link #KEYCODE_MOVE_HORIZONTAL}. This moves the editor left or right if + * shift key is pressed or not, respectively. + * + * @param event + * the received event + * @return true if this method handled the event and nothing else should be + * done, false otherwise + */ + protected boolean handleBufferedMoveEvent(EditorDomEvent event) { + Event e = event.getDomEvent(); + + if (e.getType().equals(BrowserEvents.CLICK) + && event.getRowIndex() == event.getCell().getRowIndex()) { + + editRow(event, event.getRowIndex(), event.getCell() + .getColumnIndexDOM()); + + return true; + + } else if (e.getType().equals(BrowserEvents.KEYDOWN) + && e.getKeyCode() == KEYCODE_MOVE_HORIZONTAL) { + + // Prevent tab out of Grid Editor + event.getDomEvent().preventDefault(); + + editRow(event, event.getRowIndex(), event.getFocusedColumnIndex() + + (e.getShiftKey() ? -1 : +1)); + + return true; + } else if (e.getType().equals(BrowserEvents.KEYDOWN) + && e.getKeyCode() == KEYCODE_BUFFERED_SAVE) { + triggerValueChangeEvent(event); + + // Save and close. + event.getGrid().getEditor().save(); + return true; + } + + return false; + } + + /** + * Returns whether the given event should close the editor. The default + * implementation returns true if and only if the event is a keydown event + * and the keycode is {@link #KEYCODE_CLOSE}. + * + * @param event + * the received event + * @return true if the event is a close event, false otherwise + */ + protected boolean isCloseEvent(EditorDomEvent event) { + final Event e = event.getDomEvent(); + return e.getTypeInt() == Event.ONKEYDOWN + && e.getKeyCode() == KEYCODE_CLOSE; + } + + /** + * Closes the editor if the received event is a close event. The default + * implementation uses {@link #isCloseEvent(EditorDomEvent) isCloseEvent}. + * + * @param event + * the received event + * @return true if this method handled the event and nothing else should be + * done, false otherwise + */ + protected boolean handleCloseEvent(EditorDomEvent event) { + if (isCloseEvent(event)) { + event.getEditor().cancel(); + FocusUtil.setFocus(event.getGrid(), true); + return true; + } + return false; + } + + protected void editRow(EditorDomEvent event, int rowIndex, int colIndex) { + int rowCount = event.getGrid().getDataSource().size(); + // Limit rowIndex between 0 and rowCount - 1 + rowIndex = Math.max(0, Math.min(rowCount - 1, rowIndex)); + + int colCount = event.getGrid().getVisibleColumns().size(); + // Limit colIndex between 0 and colCount - 1 + colIndex = Math.max(0, Math.min(colCount - 1, colIndex)); + + if (rowIndex != event.getRowIndex()) { + triggerValueChangeEvent(event); + } + + event.getEditor().editRow(rowIndex, colIndex); + } + + /** + * Triggers a value change event from the editor field if it has focus. This + * is based on the assumption that editor field will fire the value change + * when a blur event occurs. + * + * @param event + * the editor DOM event + */ + private void triggerValueChangeEvent(EditorDomEvent event) { + // Force a blur to cause a value change event + Widget editorWidget = event.getEditorWidget(); + if (editorWidget != null) { + Element focusedElement = WidgetUtil.getFocusedElement(); + if (editorWidget.getElement().isOrHasChild(focusedElement)) { + focusedElement.blur(); + focusedElement.focus(); + } + } + } + + @Override + public boolean handleEvent(EditorDomEvent event) { + final Editor editor = event.getEditor(); + final boolean isBody = event.getCell().isBody(); + + final boolean handled; + if (event.getGrid().isEditorActive()) { + handled = handleCloseEvent(event) + || (!editor.isBuffered() && isBody && handleMoveEvent(event)) + || (editor.isBuffered() && isBody && handleBufferedMoveEvent(event)); + } else { + handled = event.getGrid().isEnabled() && isBody + && handleOpenEvent(event); + } + + // Buffered mode should swallow all events, if not already handled. + boolean swallowEvent = event.getGrid().isEditorActive() + && editor.isBuffered(); + + return handled || swallowEvent; + } +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/DetailsGenerator.java b/client/src/main/java/com/vaadin/client/widget/grid/DetailsGenerator.java new file mode 100644 index 0000000000..b9427091a7 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/DetailsGenerator.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +import com.google.gwt.user.client.ui.Widget; + +/** + * A callback interface for generating details for a particular row in Grid. + * + * @since 7.5.0 + * @author Vaadin Ltd + */ +public interface DetailsGenerator { + + /** A details generator that provides no details */ + public static final DetailsGenerator NULL = new DetailsGenerator() { + @Override + public Widget getDetails(int rowIndex) { + return null; + } + }; + + /** + * This method is called for whenever a new details row needs to be + * generated. + * + * @param rowIndex + * the index of the row for which to generate details + * @return the details for the given row, or null to leave the + * details empty. + */ + Widget getDetails(int rowIndex); +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/EditorHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/EditorHandler.java new file mode 100644 index 0000000000..91198700ca --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/EditorHandler.java @@ -0,0 +1,174 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +import java.util.Collection; + +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.widgets.Grid; + +/** + * An interface for binding widgets and data to the grid row editor. Used by the + * editor to support different row types, data sources and custom data binding + * mechanisms. + * + * @param + * the row data type + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface EditorHandler { + + /** + * A request class passed as a parameter to the editor handler methods. The + * request is callback-based to facilitate usage with remote or otherwise + * asynchronous data sources. + *

+ * An implementation must call either {@link #success()} or {@link #fail()}, + * according to whether the operation was a success or failed during + * execution, respectively. + * + * @param + * the row data type + */ + public interface EditorRequest { + /** + * Returns the index of the row being requested. + * + * @return the row index + */ + public int getRowIndex(); + + /** + * Returns the index of the column being focused. + * + * @return the column index + */ + public int getColumnIndex(); + + /** + * Returns the row data related to the row being requested. + * + * @return the row data + */ + public T getRow(); + + /** + * Returns the grid instance related to this editor request. + * + * @return the grid instance + */ + public Grid getGrid(); + + /** + * Returns the editor widget used to edit the values of the given + * column. + * + * @param column + * the column whose widget to get + * @return the widget related to the column + */ + public Widget getWidget(Grid.Column column); + + /** + * Informs Grid that the editor request was a success. + */ + public void success(); + + /** + * Informs Grid that an error occurred while trying to process the + * request. + * + * @param errorMessage + * and error message to show to the user, or + * null to not show any message. + * @param errorColumns + * a collection of columns for which an error indicator + * should be shown, or null if no columns should + * be marked as erroneous. + */ + public void failure(String errorMessage, + Collection> errorColumns); + + /** + * Checks whether the request is completed or not. + * + * @return true iff the request is completed + */ + public boolean isCompleted(); + } + + /** + * Binds row data to the editor widgets. Called by the editor when it is + * opened for editing. + *

+ * The implementation must call either + * {@link EditorRequest#success()} or + * {@link EditorRequest#failure(String, Collection)} to signal a successful + * or a failed (respectively) bind action. + * + * @param request + * the data binding request + * + * @see Grid#editRow(int) + */ + public void bind(EditorRequest request); + + /** + * Called by the editor when editing is cancelled. This method may have an + * empty implementation in case no special processing is required. + *

+ * In contrast to {@link #bind(EditorRequest)} and + * {@link #save(EditorRequest)}, any calls to + * {@link EditorRequest#success()} or + * {@link EditorRequest#failure(String, Collection)} have no effect on the + * outcome of the cancel action. The editor is already closed when this + * method is called. + * + * @param request + * the cancel request + * + * @see Grid#cancelEditor() + */ + public void cancel(EditorRequest request); + + /** + * Commits changes in the currently active edit to the data source. Called + * by the editor when changes are saved. + *

+ * The implementation must call either + * {@link EditorRequest#success()} or {@link EditorRequest#fail()} to signal + * a successful or a failed (respectively) save action. + * + * @param request + * the save request + * + * @see Grid#saveEditor() + */ + public void save(EditorRequest request); + + /** + * Returns a widget instance that is used to edit the values in the given + * column. A null return value means the column is not editable. + * + * @param column + * the column whose values should be edited + * @return the editor widget for the column or null if the column is not + * editable + */ + public Widget getWidget(Grid.Column column); +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/EventCellReference.java b/client/src/main/java/com/vaadin/client/widget/grid/EventCellReference.java new file mode 100644 index 0000000000..4d37be2cc1 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/EventCellReference.java @@ -0,0 +1,128 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +import com.google.gwt.dom.client.TableCellElement; +import com.vaadin.client.widget.escalator.Cell; +import com.vaadin.client.widgets.Grid; +import com.vaadin.client.widgets.Grid.Column; +import com.vaadin.shared.ui.grid.GridConstants.Section; + +/** + * A data class which contains information which identifies a cell being the + * target of an event from {@link Grid}. + *

+ * Since this class follows the Flyweight-pattern any instance of + * this object is subject to change without the user knowing it and so should + * not be stored anywhere outside of the method providing these instances. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class EventCellReference extends CellReference { + + private Section section; + private TableCellElement element; + + public EventCellReference(Grid grid) { + super(new RowReference(grid)); + } + + /** + * Sets the RowReference and CellReference to point to given Cell. + * + * @param targetCell + * cell to point to + */ + public void set(Cell targetCell, Section section) { + Grid grid = getGrid(); + + int columnIndexDOM = targetCell.getColumn(); + Column column = null; + if (columnIndexDOM >= 0 + && columnIndexDOM < grid.getVisibleColumns().size()) { + column = grid.getVisibleColumns().get(columnIndexDOM); + } + + int row = targetCell.getRow(); + // Row objects only make sense for body section of Grid. + T rowObject; + if (section == Section.BODY && row >= 0 + && row < grid.getDataSource().size()) { + rowObject = grid.getDataSource().getRow(row); + } else { + rowObject = null; + } + + // At least for now we don't need to have the actual TableRowElement + // available. + getRowReference().set(row, rowObject, null); + + int columnIndex = grid.getColumns().indexOf(column); + set(columnIndexDOM, columnIndex, column); + + this.element = targetCell.getElement(); + this.section = section; + } + + @Override + public TableCellElement getElement() { + return element; + } + + /** + * Is the cell reference for a cell in the header of the Grid. + * + * @since 7.5 + * @return true if referenced cell is in the header, + * false if not + */ + public boolean isHeader() { + return section == Section.HEADER; + } + + /** + * Is the cell reference for a cell in the body of the Grid. + * + * @since 7.5 + * @return true if referenced cell is in the body, + * false if not + */ + public boolean isBody() { + return section == Section.BODY; + } + + /** + * Is the cell reference for a cell in the footer of the Grid. + * + * @since 7.5 + * @return true if referenced cell is in the footer, + * false if not + */ + public boolean isFooter() { + return section == Section.FOOTER; + } + + /** + * Gets the Grid section where the referenced cell is. + * + * @since 7.5 + * @return grid section + */ + public Section getSection() { + return section; + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/HeightAwareDetailsGenerator.java b/client/src/main/java/com/vaadin/client/widget/grid/HeightAwareDetailsGenerator.java new file mode 100644 index 0000000000..5364de3411 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/HeightAwareDetailsGenerator.java @@ -0,0 +1,45 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +/** + * {@link DetailsGenerator} that is aware of content heights. + *

+ * FOR INTERNAL USE ONLY! This class exists only for the sake of a + * temporary workaround and might be removed or renamed at any time. + *

+ * + * @since 7.6.1 + * @author Vaadin Ltd + */ +@Deprecated +public interface HeightAwareDetailsGenerator extends DetailsGenerator { + + /** + * This method is called for whenever a details row's height needs to be + * calculated. + *

+ * FOR INTERNAL USE ONLY! This method exists only for the sake of a + * temporary workaround and might be removed or renamed at any time. + *

+ * + * @since 7.6.1 + * @param rowIndex + * the index of the row for which to calculate details row height + * @return height of the details row + */ + public double getDetailsHeight(int rowIndex); +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/RendererCellReference.java b/client/src/main/java/com/vaadin/client/widget/grid/RendererCellReference.java new file mode 100644 index 0000000000..994db50aa0 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/RendererCellReference.java @@ -0,0 +1,93 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +import com.google.gwt.dom.client.TableCellElement; +import com.vaadin.client.widget.escalator.FlyweightCell; +import com.vaadin.client.widgets.Grid; + +/** + * A data class which contains information which identifies a cell being + * rendered in a {@link Grid}. + *

+ * Since this class follows the Flyweight-pattern any instance of + * this object is subject to change without the user knowing it and so should + * not be stored anywhere outside of the method providing these instances. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class RendererCellReference extends CellReference { + + /** + * Creates a new renderer cell reference bound to a row reference. + * + * @param rowReference + * the row reference to bind to + */ + public RendererCellReference(RowReference rowReference) { + super(rowReference); + } + + private FlyweightCell cell; + + /** + * Sets the identifying information for this cell. + * + * @param cell + * the flyweight cell to reference + * @param columnIndex + * the index of the column in the grid, including hidden cells + * @param column + * the column to reference + */ + public void set(FlyweightCell cell, int columnIndex, + Grid.Column column) { + this.cell = cell; + super.set(cell.getColumn(), columnIndex, + (Grid.Column) column); + } + + /** + * Returns the element of the cell. Can be either a TD element + * or a TH element. + * + * @return the element of the cell + */ + @Override + public TableCellElement getElement() { + return cell.getElement(); + } + + /** + * Sets the colspan attribute of the element of this cell. + * + * @param numberOfCells + * the number of columns that the cell should span + */ + public void setColSpan(int numberOfCells) { + cell.setColSpan(numberOfCells); + } + + /** + * Gets the colspan attribute of the element of this cell. + * + * @return the number of columns that the cell should span + */ + public int getColSpan() { + return cell.getColSpan(); + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/RowReference.java b/client/src/main/java/com/vaadin/client/widget/grid/RowReference.java new file mode 100644 index 0000000000..8874fcc5cc --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/RowReference.java @@ -0,0 +1,104 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +import com.google.gwt.dom.client.TableRowElement; +import com.vaadin.client.widgets.Grid; + +/** + * A data class which contains information which identifies a row in a + * {@link Grid}. + *

+ * Since this class follows the Flyweight-pattern any instance of + * this object is subject to change without the user knowing it and so should + * not be stored anywhere outside of the method providing these instances. + * + * @author Vaadin Ltd + * @param + * the row object type + * @since 7.4 + */ +public class RowReference { + private final Grid grid; + + private int rowIndex; + private T row; + + private TableRowElement element; + + /** + * Creates a new row reference for the given grid. + * + * @param grid + * the grid that the row belongs to + */ + public RowReference(Grid grid) { + this.grid = grid; + } + + /** + * Sets the identifying information for this row. + * + * @param rowIndex + * the index of the row + * @param row + * the row object + * @param elemenet + * the element of the row + */ + public void set(int rowIndex, T row, TableRowElement element) { + this.rowIndex = rowIndex; + this.row = row; + this.element = element; + } + + /** + * Gets the grid that contains the referenced row. + * + * @return the grid that contains referenced row + */ + public Grid getGrid() { + return grid; + } + + /** + * Gets the row index of the row. + * + * @return the index of the row + */ + public int getRowIndex() { + return rowIndex; + } + + /** + * Gets the row data object. + * + * @return the row object + */ + public T getRow() { + return row; + } + + /** + * Gets the table row element of the row. + * + * @return the element of the row + */ + public TableRowElement getElement() { + return element; + } + +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/RowStyleGenerator.java b/client/src/main/java/com/vaadin/client/widget/grid/RowStyleGenerator.java new file mode 100644 index 0000000000..a12a9ff47d --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/RowStyleGenerator.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +import java.io.Serializable; + +/** + * Callback interface for generating custom style names for data rows + * + * @author Vaadin Ltd + * @param + * the row type of the target grid + * @see Grid#setRowStyleGenerator(RowStyleGenerator) + * @since 7.4 + */ +public interface RowStyleGenerator extends Serializable { + + /** + * Called by Grid to generate a style name for a row. + * + * @param rowReference + * The row to generate a style for + * @return the style name to add to this row, or {@code null} to not set any + * style + */ + public abstract String getStyle(RowReference rowReference); +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/datasources/ListDataSource.java b/client/src/main/java/com/vaadin/client/widget/grid/datasources/ListDataSource.java new file mode 100644 index 0000000000..cf7ec53e68 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/datasources/ListDataSource.java @@ -0,0 +1,478 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.datasources; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import com.vaadin.client.data.DataChangeHandler; +import com.vaadin.client.data.DataSource; +import com.vaadin.client.widget.grid.events.SelectAllEvent; +import com.vaadin.client.widget.grid.events.SelectAllHandler; +import com.vaadin.shared.util.SharedUtil; + +/** + * A simple list based on an in-memory data source for simply adding a list of + * row pojos to the grid. Based on a wrapped list instance which supports adding + * and removing of items. + * + *

+ * Usage: + * + *

+ * ListDataSource<Integer> ds = new ListDataSource<Integer>(1, 2, 3, 4);
+ * 
+ * // Add item to the data source
+ * ds.asList().add(5);
+ * 
+ * // Remove item from the data source
+ * ds.asList().remove(3);
+ * 
+ * // Add multiple items
+ * ds.asList().addAll(Arrays.asList(5, 6, 7));
+ * 
+ * + * @since 7.4 + * @author Vaadin Ltd + */ +public class ListDataSource implements DataSource { + + private class RowHandleImpl extends RowHandle { + + private final T row; + + public RowHandleImpl(T row) { + this.row = row; + } + + @Override + public T getRow() { + /* + * We'll cheat here and don't throw an IllegalStateException even if + * this isn't pinned, because we know that the reference never gets + * stale. + */ + return row; + } + + @Override + public void pin() { + // NOOP, really + } + + @Override + public void unpin() throws IllegalStateException { + /* + * Just to make things easier for everyone, we won't throw the + * exception, even in illegal situations. + */ + } + + @Override + protected boolean equalsExplicit(Object obj) { + if (obj instanceof ListDataSource.RowHandleImpl) { + /* + * Java prefers AbstractRemoteDataSource.RowHandleImpl. I + * like the @SuppressWarnings more (keeps the line length in + * check.) + */ + @SuppressWarnings("unchecked") + RowHandleImpl rhi = (RowHandleImpl) obj; + return SharedUtil.equals(row, rhi.row); + } else { + return false; + } + } + + @Override + protected int hashCodeExplicit() { + return row.hashCode(); + } + + @Override + public void updateRow() { + if (changeHandler != null) { + changeHandler.dataUpdated(ds.indexOf(getRow()), 1); + } + } + } + + /** + * Wraps the datasource list and notifies the change handler of changing to + * the list + */ + private class ListWrapper implements List { + + @Override + public int size() { + return ds.size(); + } + + @Override + public boolean isEmpty() { + return ds.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return ds.contains(o); + } + + @Override + public Iterator iterator() { + return new ListWrapperIterator(ds.iterator()); + } + + @Override + public Object[] toArray() { + return ds.toArray(); + } + + @Override + @SuppressWarnings("hiding") + public T[] toArray(T[] a) { + return ds.toArray(a); + } + + @Override + public boolean add(T e) { + if (ds.add(e)) { + if (changeHandler != null) { + changeHandler.dataAdded(ds.size() - 1, 1); + } + return true; + } + return false; + } + + @Override + public boolean remove(Object o) { + int index = ds.indexOf(o); + if (ds.remove(o)) { + if (changeHandler != null) { + changeHandler.dataRemoved(index, 1); + } + return true; + } + return false; + } + + @Override + public boolean containsAll(Collection c) { + return ds.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + int idx = ds.size(); + if (ds.addAll(c)) { + if (changeHandler != null) { + changeHandler.dataAdded(idx, c.size()); + } + return true; + } + return false; + } + + @Override + public boolean addAll(int index, Collection c) { + if (ds.addAll(index, c)) { + if (changeHandler != null) { + changeHandler.dataAdded(index, c.size()); + } + return true; + } + return false; + } + + @Override + public boolean removeAll(Collection c) { + if (ds.removeAll(c)) { + if (changeHandler != null) { + // Have to update the whole list as the removal does not + // have to be a continuous range + changeHandler.dataUpdated(0, ds.size()); + changeHandler.dataAvailable(0, ds.size()); + } + return true; + } + return false; + } + + @Override + public boolean retainAll(Collection c) { + if (ds.retainAll(c)) { + if (changeHandler != null) { + // Have to update the whole list as the retain does not + // have to be a continuous range + changeHandler.dataUpdated(0, ds.size()); + changeHandler.dataAvailable(0, ds.size()); + } + return true; + } + return false; + } + + @Override + public void clear() { + int size = ds.size(); + ds.clear(); + if (changeHandler != null) { + changeHandler.dataRemoved(0, size); + } + } + + @Override + public T get(int index) { + return ds.get(index); + } + + @Override + public T set(int index, T element) { + T prev = ds.set(index, element); + if (changeHandler != null) { + changeHandler.dataUpdated(index, 1); + } + return prev; + } + + @Override + public void add(int index, T element) { + ds.add(index, element); + if (changeHandler != null) { + changeHandler.dataAdded(index, 1); + } + } + + @Override + public T remove(int index) { + T removed = ds.remove(index); + if (changeHandler != null) { + changeHandler.dataRemoved(index, 1); + } + return removed; + } + + @Override + public int indexOf(Object o) { + return ds.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return ds.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + // TODO could be implemented by a custom iterator. + throw new UnsupportedOperationException( + "List iterators not supported at this time."); + } + + @Override + public ListIterator listIterator(int index) { + // TODO could be implemented by a custom iterator. + throw new UnsupportedOperationException( + "List iterators not supported at this time."); + } + + @Override + public List subList(int fromIndex, int toIndex) { + throw new UnsupportedOperationException("Sub lists not supported."); + } + } + + /** + * Iterator returned by {@link ListWrapper} + */ + private class ListWrapperIterator implements Iterator { + + private final Iterator iterator; + + /** + * Constructs a new iterator + */ + public ListWrapperIterator(Iterator iterator) { + this.iterator = iterator; + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public T next() { + return iterator.next(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException( + "Iterator.remove() is not supported by this iterator."); + } + } + + /** + * Datasource for providing row pojo's + */ + private final List ds; + + /** + * Wrapper that wraps the data source + */ + private final ListWrapper wrapper; + + /** + * Handler for listening to changes in the underlying list. + */ + private DataChangeHandler changeHandler; + + /** + * Constructs a new list data source. + *

+ * Note: Modifications to the original list will not be reflected in the + * data source after the data source has been constructed. To add or remove + * items to the data source after it has been constructed use + * {@link ListDataSource#asList()}. + * + * + * @param datasource + * The list to use for providing the data to the grid + */ + public ListDataSource(List datasource) { + if (datasource == null) { + throw new IllegalArgumentException("datasource cannot be null"); + } + ds = new ArrayList(datasource); + wrapper = new ListWrapper(); + } + + /** + * Constructs a data source with a set of rows. You can dynamically add and + * remove rows from the data source via the list you get from + * {@link ListDataSource#asList()} + * + * @param rows + * The rows to initially add to the data source + */ + public ListDataSource(T... rows) { + if (rows == null) { + ds = new ArrayList(); + } else { + ds = new ArrayList(Arrays.asList(rows)); + } + wrapper = new ListWrapper(); + } + + @Override + public void ensureAvailability(int firstRowIndex, int numberOfRows) { + if (firstRowIndex >= ds.size()) { + throw new IllegalStateException( + "Trying to fetch rows outside of array"); + } + + if (changeHandler != null) { + changeHandler.dataAvailable(firstRowIndex, numberOfRows); + } + } + + @Override + public T getRow(int rowIndex) { + return ds.get(rowIndex); + } + + @Override + public int size() { + return ds.size(); + } + + @Override + public void setDataChangeHandler(DataChangeHandler dataChangeHandler) { + this.changeHandler = dataChangeHandler; + } + + /** + * Gets the list that backs this datasource. Any changes made to this list + * will be reflected in the datasource. + *

+ * Note: The list is not the same list as passed into the data source via + * the constructor. + * + * @return Returns a list implementation that wraps the real list that backs + * the data source and provides events for the data source + * listeners. + */ + public List asList() { + return wrapper; + } + + @Override + public RowHandle getHandle(T row) throws IllegalStateException { + assert ds.contains(row) : "This data source doesn't contain the row " + + row; + return new RowHandleImpl(row); + } + + /** + * Sort entire container according to a {@link Comparator}. + * + * @param comparator + * a comparator object, which compares two data source entries + * (beans/pojos) + */ + public void sort(Comparator comparator) { + Collections.sort(ds, comparator); + if (changeHandler != null) { + changeHandler.dataUpdated(0, ds.size()); + } + } + + /** + * Retrieves the index for given row object. + *

+ * Note: This method does not verify that the given row object + * exists at all in this DataSource. + * + * @param row + * the row object + * @return index of the row; or -1 if row is not available + */ + public int indexOf(T row) { + return ds.indexOf(row); + } + + /** + * Returns a {@link SelectAllHandler} for this ListDataSource. + * + * @return select all handler + */ + public SelectAllHandler getSelectAllHandler() { + return new SelectAllHandler() { + @Override + public void onSelectAll(SelectAllEvent event) { + event.getSelectionModel().select(asList()); + } + }; + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/datasources/ListSorter.java b/client/src/main/java/com/vaadin/client/widget/grid/datasources/ListSorter.java new file mode 100644 index 0000000000..69bea629b0 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/datasources/ListSorter.java @@ -0,0 +1,175 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.datasources; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gwt.event.shared.HandlerRegistration; +import com.vaadin.client.data.DataSource; +import com.vaadin.client.widget.grid.sort.SortEvent; +import com.vaadin.client.widget.grid.sort.SortHandler; +import com.vaadin.client.widget.grid.sort.SortOrder; +import com.vaadin.client.widgets.Grid; +import com.vaadin.shared.data.sort.SortDirection; + +/** + * Provides sorting facility from Grid for the {@link ListDataSource} in-memory + * data source. + * + * @author Vaadin Ltd + * @param + * Grid row data type + * @since 7.4 + */ +public class ListSorter { + + private Grid grid; + private Map, Comparator> comparators; + private HandlerRegistration sortHandlerRegistration; + + public ListSorter(Grid grid) { + + if (grid == null) { + throw new IllegalArgumentException("Grid can not be null"); + } + + this.grid = grid; + comparators = new HashMap, Comparator>(); + + sortHandlerRegistration = grid.addSortHandler(new SortHandler() { + @Override + public void sort(SortEvent event) { + ListSorter.this.sort(event.getOrder()); + } + }); + } + + /** + * Detach this Sorter from the Grid. This unregisters the sort event handler + * which was used to apply sorting to the ListDataSource. + */ + public void removeFromGrid() { + sortHandlerRegistration.removeHandler(); + } + + /** + * Assign or remove a comparator for a column. This comparator method, if + * defined, is always used in favour of 'natural' comparison of objects + * (i.e. the compareTo of objects implementing the Comparable interface, + * which includes all standard data classes like String, Number derivatives + * and Dates). Any existing comparator can be removed by passing in a + * non-null GridColumn and a null Comparator. + * + * @param column + * a grid column. May not be null. + * @param comparator + * comparator method for the values returned by the grid column. + * If null, any existing comparator is removed. + */ + public void setComparator(Grid.Column column, + Comparator comparator) { + if (column == null) { + throw new IllegalArgumentException( + "Column reference can not be null"); + } + if (comparator == null) { + comparators.remove(column); + } else { + comparators.put(column, comparator); + } + } + + /** + * Retrieve the comparator assigned for a specific grid column. + * + * @param column + * a grid column. May not be null. + * @return a comparator, or null if no comparator for the specified grid + * column has been set. + */ + @SuppressWarnings("unchecked") + public Comparator getComparator(Grid.Column column) { + if (column == null) { + throw new IllegalArgumentException( + "Column reference can not be null"); + } + return (Comparator) comparators.get(column); + } + + /** + * Remove all comparator mappings. Useful if the data source has changed but + * this Sorter is being re-used. + */ + public void clearComparators() { + comparators.clear(); + } + + /** + * Apply sorting to the current ListDataSource. + * + * @param order + * the sort order list provided by the grid sort event + */ + private void sort(final List order) { + DataSource ds = grid.getDataSource(); + if (!(ds instanceof ListDataSource)) { + throw new IllegalStateException("Grid " + grid + + " data source is not a ListDataSource!"); + } + + ((ListDataSource) ds).sort(new Comparator() { + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public int compare(T a, T b) { + + for (SortOrder o : order) { + + Grid.Column column = o.getColumn(); + Comparator cmp = ListSorter.this.comparators.get(column); + int result = 0; + Object value_a = column.getValue(a); + Object value_b = column.getValue(b); + if (cmp != null) { + result = cmp.compare(value_a, value_b); + } else { + if (!(value_a instanceof Comparable)) { + throw new IllegalStateException("Column " + column + + " has no assigned comparator and value " + + value_a + " isn't naturally comparable"); + } + result = ((Comparable) value_a).compareTo(value_b); + } + + if (result != 0) { + return o.getDirection() == SortDirection.ASCENDING ? result + : -result; + } + } + + if (order.size() > 0) { + return order.get(0).getDirection() == SortDirection.ASCENDING ? a + .hashCode() - b.hashCode() + : b.hashCode() - a.hashCode(); + } + return a.hashCode() - b.hashCode(); + } + }); + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/AbstractGridKeyEventHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/AbstractGridKeyEventHandler.java new file mode 100644 index 0000000000..120c32d380 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/AbstractGridKeyEventHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.EventHandler; +import com.vaadin.client.widgets.Grid.AbstractGridKeyEvent; + +/** + * Base interface of all handlers for {@link AbstractGridKeyEvent}s. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public abstract interface AbstractGridKeyEventHandler extends EventHandler { + + public abstract interface GridKeyDownHandler extends + AbstractGridKeyEventHandler { + public void onKeyDown(GridKeyDownEvent event); + } + + public abstract interface GridKeyUpHandler extends + AbstractGridKeyEventHandler { + public void onKeyUp(GridKeyUpEvent event); + } + + public abstract interface GridKeyPressHandler extends + AbstractGridKeyEventHandler { + public void onKeyPress(GridKeyPressEvent event); + } + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/AbstractGridMouseEventHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/AbstractGridMouseEventHandler.java new file mode 100644 index 0000000000..15e22a6d57 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/AbstractGridMouseEventHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.EventHandler; +import com.vaadin.client.widgets.Grid.AbstractGridMouseEvent; + +/** + * Base interface of all handlers for {@link AbstractGridMouseEvent}s. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public abstract interface AbstractGridMouseEventHandler extends EventHandler { + + public abstract interface GridClickHandler extends + AbstractGridMouseEventHandler { + public void onClick(GridClickEvent event); + } + + public abstract interface GridDoubleClickHandler extends + AbstractGridMouseEventHandler { + public void onDoubleClick(GridDoubleClickEvent event); + } + +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/BodyClickHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/BodyClickHandler.java new file mode 100644 index 0000000000..a66e170524 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/BodyClickHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridClickHandler; + +/** + * Handler for {@link GridClickEvent}s that happen in the body of the Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface BodyClickHandler extends GridClickHandler { + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/BodyDoubleClickHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/BodyDoubleClickHandler.java new file mode 100644 index 0000000000..7be29920e7 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/BodyDoubleClickHandler.java @@ -0,0 +1,29 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridDoubleClickHandler; + +/** + * Handler for {@link GridDoubleClickEvent}s that happen in the body of the + * Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface BodyDoubleClickHandler extends GridDoubleClickHandler { + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/BodyKeyDownHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/BodyKeyDownHandler.java new file mode 100644 index 0000000000..ff1ae82d2e --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/BodyKeyDownHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyDownHandler; + +/** + * Handler for {@link GridKeyDownEvent}s that happen when the focused cell is in + * the body of the Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface BodyKeyDownHandler extends GridKeyDownHandler { +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/BodyKeyPressHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/BodyKeyPressHandler.java new file mode 100644 index 0000000000..245250d4c0 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/BodyKeyPressHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyPressHandler; + +/** + * Handler for {@link GridKeyPressEvent}s that happen when the focused cell is + * in the body of the Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface BodyKeyPressHandler extends GridKeyPressHandler { +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/BodyKeyUpHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/BodyKeyUpHandler.java new file mode 100644 index 0000000000..2c0951ea40 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/BodyKeyUpHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyUpHandler; + +/** + * Handler for {@link GridKeyUpEvent}s that happen when the focused cell is in + * the body of the Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface BodyKeyUpHandler extends GridKeyUpHandler { +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java new file mode 100644 index 0000000000..1712871089 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnReorderEvent.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.GwtEvent; + +/** + * An event for notifying that the columns in the Grid have been reordered. + * + * @param + * The row type of the grid. The row type is the POJO type from where + * the data is retrieved into the column cells. + * @since 7.5.0 + * @author Vaadin Ltd + */ +public class ColumnReorderEvent extends GwtEvent> { + + /** + * Handler type. + */ + private final static Type> TYPE = new Type>(); + + public static final Type> getType() { + return TYPE; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Type> getAssociatedType() { + return (Type) TYPE; + } + + @Override + protected void dispatch(ColumnReorderHandler handler) { + handler.onColumnReorder(this); + } + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java new file mode 100644 index 0000000000..29c476058e --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnReorderHandler.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler for a Grid column reorder event, called when the Grid's columns has + * been reordered. + * + * @param + * The row type of the grid. The row type is the POJO type from where + * the data is retrieved into the column cells. + * @since 7.5.0 + * @author Vaadin Ltd + */ +public interface ColumnReorderHandler extends EventHandler { + + /** + * A column reorder event, fired by Grid when the columns of the Grid have + * been reordered. + * + * @param event + * column reorder event + */ + public void onColumnReorder(ColumnReorderEvent event); +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnResizeEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnResizeEvent.java new file mode 100644 index 0000000000..f5c8c0fa83 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnResizeEvent.java @@ -0,0 +1,67 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.client.widgets.Grid.Column; + +/** + * An event for notifying that the columns in the Grid have been resized. + * + * @param + * The row type of the grid. The row type is the POJO type from where + * the data is retrieved into the column cells. + * @since 7.6 + * @author Vaadin Ltd + */ +public class ColumnResizeEvent extends GwtEvent> { + + /** + * Handler type. + */ + private final static Type> TYPE = new Type>(); + + private Column column; + + /** + * @param column + */ + public ColumnResizeEvent(Column column) { + this.column = column; + } + + public static final Type> getType() { + return TYPE; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Type> getAssociatedType() { + return (Type) TYPE; + } + + @Override + protected void dispatch(ColumnResizeHandler handler) { + handler.onColumnResize(this); + } + + /** + * @return the column + */ + public Column getColumn() { + return column; + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnResizeHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnResizeHandler.java new file mode 100644 index 0000000000..a66dbf7bd2 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnResizeHandler.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler for a Grid column resize event, called when the Grid's columns has + * been resized. + * + * @param + * The row type of the grid. The row type is the POJO type from where + * the data is retrieved into the column cells. + * @since 7.6 + * @author Vaadin Ltd + */ +public interface ColumnResizeHandler extends EventHandler { + + /** + * A column resize event, fired by Grid when the columns of the Grid have + * been resized. + * + * @param event + * column resize event + */ + public void onColumnResize(ColumnResizeEvent event); +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java new file mode 100644 index 0000000000..63b788bcf2 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeEvent.java @@ -0,0 +1,93 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.client.widgets.Grid.Column; + +/** + * An event for notifying that the columns in the Grid's have changed + * visibility. + * + * @param + * The row type of the grid. The row type is the POJO type from where + * the data is retrieved into the column cells. + * @since 7.5.0 + * @author Vaadin Ltd + */ +public class ColumnVisibilityChangeEvent extends + GwtEvent> { + + private final static Type> TYPE = new Type>(); + + public static final Type> getType() { + return TYPE; + } + + private final Column column; + + private final boolean userOriginated; + + private final boolean hidden; + + public ColumnVisibilityChangeEvent(Column column, boolean hidden, + boolean userOriginated) { + this.column = column; + this.hidden = hidden; + this.userOriginated = userOriginated; + } + + /** + * Returns the column where the visibility change occurred. + * + * @return the column where the visibility change occurred. + */ + public Column getColumn() { + return column; + } + + /** + * Was the column set hidden or visible. + * + * @return true if the column was hidden false if + * it was set visible + */ + public boolean isHidden() { + return hidden; + } + + /** + * Is the visibility change triggered by user. + * + * @return true if the change was triggered by user, + * false if not + */ + public boolean isUserOriginated() { + return userOriginated; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public com.google.gwt.event.shared.GwtEvent.Type> getAssociatedType() { + return (Type) TYPE; + } + + @Override + protected void dispatch(ColumnVisibilityChangeHandler handler) { + handler.onVisibilityChange(this); + } + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java new file mode 100644 index 0000000000..542fe4e3c1 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/ColumnVisibilityChangeHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler for a Grid column visibility change event, called when the Grid's + * columns have changed visibility to hidden or visible. + * + * @param The row type of the grid. The row type is the POJO type from where + * the data is retrieved into the column cells. + * @since 7.5.0 + * @author Vaadin Ltd + */ +public interface ColumnVisibilityChangeHandler extends EventHandler { + + /** + * A column visibility change event, fired by Grid when a column in the Grid + * has changed visibility. + * + * @param event + * column visibility change event + */ + public void onVisibilityChange(ColumnVisibilityChangeEvent event); +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/FooterClickHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/FooterClickHandler.java new file mode 100644 index 0000000000..51fa38c948 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/FooterClickHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridClickHandler; + +/** + * Handler for {@link GridClickEvent}s that happen in the footer of the Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface FooterClickHandler extends GridClickHandler { + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/FooterDoubleClickHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/FooterDoubleClickHandler.java new file mode 100644 index 0000000000..2f5ba21787 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/FooterDoubleClickHandler.java @@ -0,0 +1,29 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridDoubleClickHandler; + +/** + * Handler for {@link GridDoubleClickEvent}s that happen in the footer of the + * Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface FooterDoubleClickHandler extends GridDoubleClickHandler { + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/FooterKeyDownHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/FooterKeyDownHandler.java new file mode 100644 index 0000000000..85f83970f2 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/FooterKeyDownHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyDownHandler; + +/** + * Handler for {@link GridKeyDownEvent}s that happen when the focused cell is in + * the footer of the Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface FooterKeyDownHandler extends GridKeyDownHandler { +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/FooterKeyPressHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/FooterKeyPressHandler.java new file mode 100644 index 0000000000..09778f6873 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/FooterKeyPressHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyPressHandler; + +/** + * Handler for {@link GridKeyPressEvent}s that happen when the focused cell is + * in the footer of the Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface FooterKeyPressHandler extends GridKeyPressHandler { +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/FooterKeyUpHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/FooterKeyUpHandler.java new file mode 100644 index 0000000000..688f89880f --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/FooterKeyUpHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyUpHandler; + +/** + * Handler for {@link GridKeyUpEvent}s that happen when the focused cell is in + * the footer of the Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface FooterKeyUpHandler extends GridKeyUpHandler { +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/GridClickEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/events/GridClickEvent.java new file mode 100644 index 0000000000..33a4c923b7 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/GridClickEvent.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.dom.client.BrowserEvents; +import com.vaadin.client.widget.grid.CellReference; +import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridClickHandler; +import com.vaadin.client.widgets.Grid; +import com.vaadin.client.widgets.Grid.AbstractGridMouseEvent; +import com.vaadin.shared.ui.grid.GridConstants.Section; + +/** + * Represents native mouse click event in Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class GridClickEvent extends AbstractGridMouseEvent { + + public GridClickEvent(Grid grid, CellReference targetCell) { + super(grid, targetCell); + } + + @Override + protected String getBrowserEventType() { + return BrowserEvents.CLICK; + } + + @Override + protected void doDispatch(GridClickHandler handler, Section section) { + if ((section == Section.BODY && handler instanceof BodyClickHandler) + || (section == Section.HEADER && handler instanceof HeaderClickHandler) + || (section == Section.FOOTER && handler instanceof FooterClickHandler)) { + handler.onClick(this); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/GridDoubleClickEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/events/GridDoubleClickEvent.java new file mode 100644 index 0000000000..64a1a88b42 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/GridDoubleClickEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.dom.client.BrowserEvents; +import com.vaadin.client.widget.grid.CellReference; +import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridDoubleClickHandler; +import com.vaadin.client.widgets.Grid; +import com.vaadin.client.widgets.Grid.AbstractGridMouseEvent; +import com.vaadin.shared.ui.grid.GridConstants.Section; + +/** + * Represents native mouse double click event in Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class GridDoubleClickEvent extends + AbstractGridMouseEvent { + + public GridDoubleClickEvent(Grid grid, CellReference targetCell) { + super(grid, targetCell); + } + + @Override + protected String getBrowserEventType() { + return BrowserEvents.DBLCLICK; + } + + @Override + protected void doDispatch(GridDoubleClickHandler handler, Section section) { + if ((section == Section.BODY && handler instanceof BodyDoubleClickHandler) + || (section == Section.HEADER && handler instanceof HeaderDoubleClickHandler) + || (section == Section.FOOTER && handler instanceof FooterDoubleClickHandler)) { + handler.onDoubleClick(this); + } + } + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/GridKeyDownEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/events/GridKeyDownEvent.java new file mode 100644 index 0000000000..9849137982 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/GridKeyDownEvent.java @@ -0,0 +1,121 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.dom.client.BrowserEvents; +import com.google.gwt.event.dom.client.KeyCodes; +import com.vaadin.client.widget.grid.CellReference; +import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyDownHandler; +import com.vaadin.client.widgets.Grid; +import com.vaadin.client.widgets.Grid.AbstractGridKeyEvent; +import com.vaadin.shared.ui.grid.GridConstants.Section; + +/** + * Represents native key down event in Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class GridKeyDownEvent extends AbstractGridKeyEvent { + + public GridKeyDownEvent(Grid grid, CellReference targetCell) { + super(grid, targetCell); + } + + @Override + protected void doDispatch(GridKeyDownHandler handler, Section section) { + if ((section == Section.BODY && handler instanceof BodyKeyDownHandler) + || (section == Section.HEADER && handler instanceof HeaderKeyDownHandler) + || (section == Section.FOOTER && handler instanceof FooterKeyDownHandler)) { + handler.onKeyDown(this); + } + } + + @Override + protected String getBrowserEventType() { + return BrowserEvents.KEYDOWN; + } + + /** + * Does the key code represent an arrow key? + * + * @param keyCode + * the key code + * @return if it is an arrow key code + */ + public static boolean isArrow(int keyCode) { + switch (keyCode) { + case KeyCodes.KEY_DOWN: + case KeyCodes.KEY_RIGHT: + case KeyCodes.KEY_UP: + case KeyCodes.KEY_LEFT: + return true; + default: + return false; + } + } + + /** + * Gets the native key code. These key codes are enumerated in the + * {@link KeyCodes} class. + * + * @return the key code + */ + public int getNativeKeyCode() { + return getNativeEvent().getKeyCode(); + } + + /** + * Is this a key down arrow? + * + * @return whether this is a down arrow key event + */ + public boolean isDownArrow() { + return getNativeKeyCode() == KeyCodes.KEY_DOWN; + } + + /** + * Is this a left arrow? + * + * @return whether this is a left arrow key event + */ + public boolean isLeftArrow() { + return getNativeKeyCode() == KeyCodes.KEY_LEFT; + } + + /** + * Is this a right arrow? + * + * @return whether this is a right arrow key event + */ + public boolean isRightArrow() { + return getNativeKeyCode() == KeyCodes.KEY_RIGHT; + } + + /** + * Is this a up arrow? + * + * @return whether this is a right arrow key event + */ + public boolean isUpArrow() { + return getNativeKeyCode() == KeyCodes.KEY_UP; + } + + @Override + public String toDebugString() { + return super.toDebugString() + "[" + getNativeKeyCode() + "]"; + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/GridKeyPressEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/events/GridKeyPressEvent.java new file mode 100644 index 0000000000..35a3af0c2e --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/GridKeyPressEvent.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.dom.client.BrowserEvents; +import com.vaadin.client.widget.grid.CellReference; +import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyPressHandler; +import com.vaadin.client.widgets.Grid; +import com.vaadin.client.widgets.Grid.AbstractGridKeyEvent; +import com.vaadin.shared.ui.grid.GridConstants.Section; + +/** + * Represents native key press event in Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class GridKeyPressEvent extends + AbstractGridKeyEvent { + + public GridKeyPressEvent(Grid grid, CellReference targetCell) { + super(grid, targetCell); + } + + @Override + protected void doDispatch(GridKeyPressHandler handler, Section section) { + if ((section == Section.BODY && handler instanceof BodyKeyPressHandler) + || (section == Section.HEADER && handler instanceof HeaderKeyPressHandler) + || (section == Section.FOOTER && handler instanceof FooterKeyPressHandler)) { + handler.onKeyPress(this); + } + } + + @Override + protected String getBrowserEventType() { + return BrowserEvents.KEYPRESS; + } + + /** + * Gets the char code for this event. + * + * @return the char code + */ + public char getCharCode() { + return (char) getUnicodeCharCode(); + } + + /** + * Gets the Unicode char code (code point) for this event. + * + * @return the Unicode char code + */ + public int getUnicodeCharCode() { + return getNativeEvent().getCharCode(); + } + + @Override + public String toDebugString() { + return super.toDebugString() + "[" + getCharCode() + "]"; + } +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/GridKeyUpEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/events/GridKeyUpEvent.java new file mode 100644 index 0000000000..d273835233 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/GridKeyUpEvent.java @@ -0,0 +1,121 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.dom.client.BrowserEvents; +import com.google.gwt.event.dom.client.KeyCodes; +import com.vaadin.client.widget.grid.CellReference; +import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyUpHandler; +import com.vaadin.client.widgets.Grid; +import com.vaadin.client.widgets.Grid.AbstractGridKeyEvent; +import com.vaadin.shared.ui.grid.GridConstants.Section; + +/** + * Represents native key up event in Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class GridKeyUpEvent extends AbstractGridKeyEvent { + + public GridKeyUpEvent(Grid grid, CellReference targetCell) { + super(grid, targetCell); + } + + @Override + protected void doDispatch(GridKeyUpHandler handler, Section section) { + if ((section == Section.BODY && handler instanceof BodyKeyUpHandler) + || (section == Section.HEADER && handler instanceof HeaderKeyUpHandler) + || (section == Section.FOOTER && handler instanceof FooterKeyUpHandler)) { + handler.onKeyUp(this); + } + } + + @Override + protected String getBrowserEventType() { + return BrowserEvents.KEYUP; + } + + /** + * Does the key code represent an arrow key? + * + * @param keyCode + * the key code + * @return if it is an arrow key code + */ + public static boolean isArrow(int keyCode) { + switch (keyCode) { + case KeyCodes.KEY_DOWN: + case KeyCodes.KEY_RIGHT: + case KeyCodes.KEY_UP: + case KeyCodes.KEY_LEFT: + return true; + default: + return false; + } + } + + /** + * Gets the native key code. These key codes are enumerated in the + * {@link KeyCodes} class. + * + * @return the key code + */ + public int getNativeKeyCode() { + return getNativeEvent().getKeyCode(); + } + + /** + * Is this a key down arrow? + * + * @return whether this is a down arrow key event + */ + public boolean isDownArrow() { + return getNativeKeyCode() == KeyCodes.KEY_DOWN; + } + + /** + * Is this a left arrow? + * + * @return whether this is a left arrow key event + */ + public boolean isLeftArrow() { + return getNativeKeyCode() == KeyCodes.KEY_LEFT; + } + + /** + * Is this a right arrow? + * + * @return whether this is a right arrow key event + */ + public boolean isRightArrow() { + return getNativeKeyCode() == KeyCodes.KEY_RIGHT; + } + + /** + * Is this a up arrow? + * + * @return whether this is a right arrow key event + */ + public boolean isUpArrow() { + return getNativeKeyCode() == KeyCodes.KEY_UP; + } + + @Override + public String toDebugString() { + return super.toDebugString() + "[" + getNativeKeyCode() + "]"; + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderClickHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderClickHandler.java new file mode 100644 index 0000000000..da20e80905 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderClickHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridClickHandler; + +/** + * Handler for {@link GridClickEvent}s that happen in the header of the Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface HeaderClickHandler extends GridClickHandler { + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderDoubleClickHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderDoubleClickHandler.java new file mode 100644 index 0000000000..16a4cfe1f5 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderDoubleClickHandler.java @@ -0,0 +1,29 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler.GridDoubleClickHandler; + +/** + * Handler for {@link GridDoubleClickEvent}s that happen in the header of the + * Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface HeaderDoubleClickHandler extends GridDoubleClickHandler { + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderKeyDownHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderKeyDownHandler.java new file mode 100644 index 0000000000..555eb936af --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderKeyDownHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyDownHandler; + +/** + * Handler for {@link GridKeyDownEvent}s that happen when the focused cell is in + * the header of the Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface HeaderKeyDownHandler extends GridKeyDownHandler { +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderKeyPressHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderKeyPressHandler.java new file mode 100644 index 0000000000..c4dd312f93 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderKeyPressHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyPressHandler; + +/** + * Handler for {@link GridKeyPressEvent}s that happen when the focused cell is + * in the header of the Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface HeaderKeyPressHandler extends GridKeyPressHandler { +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderKeyUpHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderKeyUpHandler.java new file mode 100644 index 0000000000..4dbe1c681e --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/HeaderKeyUpHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler.GridKeyUpHandler; + +/** + * Handler for {@link GridKeyUpEvent}s that happen when the focused cell is in + * the header of the Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface HeaderKeyUpHandler extends GridKeyUpHandler { +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/ScrollEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/events/ScrollEvent.java new file mode 100644 index 0000000000..08e1e07eab --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/ScrollEvent.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.GwtEvent; + +/** + * An event that signifies that a scrollbar bundle has been scrolled + * + * @author Vaadin Ltd + * @since 7.4 + */ +public class ScrollEvent extends GwtEvent { + + /** The type of this event */ + public static final Type TYPE = new Type(); + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(final ScrollHandler handler) { + handler.onScroll(this); + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/ScrollHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/ScrollHandler.java new file mode 100644 index 0000000000..1ce901e707 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/ScrollHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.EventHandler; + +/** + * A handler that gets called whenever a scrollbar bundle is scrolled + * + * @author Vaadin Ltd + * @since 7.4 + */ +public interface ScrollHandler extends EventHandler { + /** + * A callback method that is called once a scrollbar bundle has been + * scrolled. + * + * @param event + * the scroll event + */ + public void onScroll(ScrollEvent event); +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/SelectAllEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/events/SelectAllEvent.java new file mode 100644 index 0000000000..43c2055e95 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/SelectAllEvent.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.client.widget.grid.selection.SelectionModel; + +/** + * A select all event, fired by the Grid when it needs all rows in data source + * to be selected. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class SelectAllEvent extends GwtEvent> { + + /** + * Handler type. + */ + private final static Type> TYPE = new Type>();; + + private SelectionModel.Multi selectionModel; + + public SelectAllEvent(SelectionModel.Multi selectionModel) { + this.selectionModel = selectionModel; + } + + public static final Type> getType() { + return TYPE; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Type> getAssociatedType() { + return (Type) TYPE; + } + + @Override + protected void dispatch(SelectAllHandler handler) { + handler.onSelectAll(this); + } + + public SelectionModel.Multi getSelectionModel() { + return selectionModel; + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/events/SelectAllHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/events/SelectAllHandler.java new file mode 100644 index 0000000000..2cdee8d1b3 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/events/SelectAllHandler.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler for a Grid select all event, called when the Grid needs all rows in + * data source to be selected. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface SelectAllHandler extends EventHandler { + + /** + * Called when select all value in SelectionColumn header changes value. + * + * @param event + * select all event telling that all rows should be selected + */ + public void onSelectAll(SelectAllEvent event); + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/AbstractRowHandleSelectionModel.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/AbstractRowHandleSelectionModel.java new file mode 100644 index 0000000000..6b7bbb6294 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/AbstractRowHandleSelectionModel.java @@ -0,0 +1,66 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.selection; + +import com.vaadin.client.data.DataSource.RowHandle; + +/** + * An abstract class that adds a consistent API for common methods that's needed + * by Vaadin's server-based selection models to work. + *

+ * Note: This should be an interface instead of an abstract class, if + * only we could define protected methods in an interface. + * + * @author Vaadin Ltd + * @param + * The grid's row type + * @since 7.4 + */ +public abstract class AbstractRowHandleSelectionModel implements + SelectionModel { + /** + * Select a row, based on its + * {@link com.vaadin.client.data.DataSource.RowHandle RowHandle}. + *

+ * Note: this method may not fire selection change events. + * + * @param handle + * the handle to select by + * @return true iff the selection state was changed by this + * call + * @throws UnsupportedOperationException + * if the selection model does not support either handles or + * selection + */ + protected abstract boolean selectByHandle(RowHandle handle); + + /** + * Deselect a row, based on its + * {@link com.vaadin.client.data.DataSource.RowHandle RowHandle}. + *

+ * Note: this method may not fire selection change events. + * + * @param handle + * the handle to deselect by + * @return true iff the selection state was changed by this + * call + * @throws UnsupportedOperationException + * if the selection model does not support either handles or + * deselection + */ + protected abstract boolean deselectByHandle(RowHandle handle) + throws UnsupportedOperationException; +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/ClickSelectHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/ClickSelectHandler.java new file mode 100644 index 0000000000..c6bc52dd1c --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/ClickSelectHandler.java @@ -0,0 +1,77 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.selection; + +import com.google.gwt.event.shared.HandlerRegistration; +import com.vaadin.client.widget.grid.events.BodyClickHandler; +import com.vaadin.client.widget.grid.events.GridClickEvent; +import com.vaadin.client.widgets.Grid; + +/** + * Generic class to perform selections when clicking on cells in body of Grid. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class ClickSelectHandler { + + private Grid grid; + private HandlerRegistration clickHandler; + private boolean deselectAllowed = true; + + private class RowClickHandler implements BodyClickHandler { + + @Override + public void onClick(GridClickEvent event) { + T row = (T) event.getTargetCell().getRow(); + if (!grid.isSelected(row)) { + grid.select(row); + } else if (deselectAllowed) { + grid.deselect(row); + } + } + } + + /** + * Constructor for ClickSelectHandler. This constructor will add all + * necessary handlers for selecting rows by clicking cells. + * + * @param grid + * grid to attach to + */ + public ClickSelectHandler(Grid grid) { + this.grid = grid; + clickHandler = grid.addBodyClickHandler(new RowClickHandler()); + } + + /** + * Clean up function for removing all now obsolete handlers. + */ + public void removeHandler() { + clickHandler.removeHandler(); + } + + /** + * Sets whether clicking the currently selected row should deselect the row. + * + * @param deselectAllowed + * true to allow deselecting the selected row; + * otherwise false + */ + public void setDeselectAllowed(boolean deselectAllowed) { + this.deselectAllowed = deselectAllowed; + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/HasSelectionHandlers.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/HasSelectionHandlers.java new file mode 100644 index 0000000000..ffcad4c903 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/HasSelectionHandlers.java @@ -0,0 +1,42 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.selection; + +import com.google.gwt.event.shared.HandlerRegistration; + +/** + * Marker interface for widgets that fires selection events. + * + * @author Vaadin Ltd + * @since 7.4 + */ +public interface HasSelectionHandlers { + + /** + * Register a selection change handler. + *

+ * This handler is called whenever a + * {@link com.vaadin.ui.components.grid.selection.SelectionModel + * SelectionModel} detects a change in selection state. + * + * @param handler + * a {@link SelectionHandler} + * @return a handler registration object, which can be used to remove the + * handler. + */ + public HandlerRegistration addSelectionHandler(SelectionHandler handler); + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java new file mode 100644 index 0000000000..c64908f24c --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java @@ -0,0 +1,763 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.selection; + +import java.util.Collection; +import java.util.HashSet; + +import com.google.gwt.animation.client.AnimationScheduler; +import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback; +import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle; +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.BrowserEvents; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.TableElement; +import com.google.gwt.dom.client.TableSectionElement; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.MouseDownEvent; +import com.google.gwt.event.dom.client.MouseDownHandler; +import com.google.gwt.event.dom.client.TouchStartEvent; +import com.google.gwt.event.dom.client.TouchStartHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Event.NativePreviewHandler; +import com.google.gwt.user.client.ui.CheckBox; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.renderers.ClickableRenderer; +import com.vaadin.client.widget.grid.CellReference; +import com.vaadin.client.widget.grid.RendererCellReference; +import com.vaadin.client.widget.grid.selection.SelectionModel.Multi.Batched; +import com.vaadin.client.widgets.Grid; + +/** + * Renderer showing multi selection check boxes. + * + * @author Vaadin Ltd + * @param + * the type of the associated grid + * @since 7.4 + */ +public class MultiSelectionRenderer extends + ClickableRenderer { + + private static final String SELECTION_CHECKBOX_CLASSNAME = "-selection-checkbox"; + + /** The size of the autoscroll area, both top and bottom. */ + private static final int SCROLL_AREA_GRADIENT_PX = 100; + + /** The maximum number of pixels per second to autoscroll. */ + private static final int SCROLL_TOP_SPEED_PX_SEC = 500; + + /** + * The minimum area where the grid doesn't scroll while the pointer is + * pressed. + */ + private static final int MIN_NO_AUTOSCROLL_AREA_PX = 50; + + /** + * Handler for MouseDown and TouchStart events for selection checkboxes. + * + * @since 7.5 + */ + private final class CheckBoxEventHandler implements MouseDownHandler, + TouchStartHandler, ClickHandler { + private final CheckBox checkBox; + + /** + * @param checkBox + * checkbox widget for this handler + */ + private CheckBoxEventHandler(CheckBox checkBox) { + this.checkBox = checkBox; + } + + @Override + public void onMouseDown(MouseDownEvent event) { + if (event.getNativeButton() == NativeEvent.BUTTON_LEFT) { + startDragSelect(event.getNativeEvent(), checkBox.getElement()); + } + } + + @Override + public void onTouchStart(TouchStartEvent event) { + startDragSelect(event.getNativeEvent(), checkBox.getElement()); + } + + @Override + public void onClick(ClickEvent event) { + // Clicking is already handled with MultiSelectionRenderer + event.preventDefault(); + event.stopPropagation(); + } + } + + /** + * This class's main objective is to listen when to stop autoscrolling, and + * make sure everything stops accordingly. + */ + private class TouchEventHandler implements NativePreviewHandler { + @Override + public void onPreviewNativeEvent(final NativePreviewEvent event) { + switch (event.getTypeInt()) { + case Event.ONTOUCHSTART: { + if (event.getNativeEvent().getTouches().length() == 1) { + /* + * Something has dropped a touchend/touchcancel and the + * scroller is most probably running amok. Let's cancel it + * and pretend that everything's going as expected + * + * Because this is a preview, this code is run before the + * event handler in MultiSelectionRenderer.onBrowserEvent. + * Therefore, we can simply kill everything and let that + * method restart things as they should. + */ + autoScrollHandler.stop(); + + /* + * Related TODO: investigate why iOS seems to ignore a + * touchend/touchcancel when frames are dropped, and/or if + * something can be done about that. + */ + } + break; + } + + case Event.ONTOUCHMOVE: + event.cancel(); + break; + + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + /* + * Remember: targetElement is always where touchstart started, + * not where the finger is pointing currently. + */ + final Element targetElement = Element.as(event.getNativeEvent() + .getEventTarget()); + if (isInFirstColumn(targetElement)) { + removeNativeHandler(); + event.cancel(); + } + break; + } + } + + private boolean isInFirstColumn(final Element element) { + if (element == null) { + return false; + } + final Element tbody = getTbodyElement(); + + if (tbody == null || !tbody.isOrHasChild(element)) { + return false; + } + + /* + * The null-parent in the while clause is in the case where element + * is an immediate tr child in the tbody. Should never happen in + * internal code, but hey... + */ + Element cursor = element; + while (cursor.getParentElement() != null + && cursor.getParentElement().getParentElement() != tbody) { + cursor = cursor.getParentElement(); + } + + final Element tr = cursor.getParentElement(); + return tr.getFirstChildElement().equals(cursor); + } + } + + /** + * This class's responsibility is to + *

    + *
  • scroll the table while a pointer is kept in a scrolling zone and + *
  • select rows whenever a pointer is "activated" on a selection cell + *
+ *

+ * Techical note: This class is an AnimationCallback because we + * need a timer: when the finger is kept in place while the grid scrolls, we + * still need to be able to make new selections. So, instead of relying on + * events (which won't be fired, since the pointer isn't necessarily + * moving), we do this check on each frame while the pointer is "active" + * (mouse is pressed, finger is on screen). + */ + private class AutoScrollerAndSelector implements AnimationCallback { + + /** + * If the acceleration gradient area is smaller than this, autoscrolling + * will be disabled (it becomes too quick to accelerate to be usable). + */ + private static final int GRADIENT_MIN_THRESHOLD_PX = 10; + + /** + * The speed at which the gradient area recovers, once scrolling in that + * direction has started. + */ + private static final int SCROLL_AREA_REBOUND_PX_PER_SEC = 1; + private static final double SCROLL_AREA_REBOUND_PX_PER_MS = SCROLL_AREA_REBOUND_PX_PER_SEC / 1000.0d; + + /** + * The lowest y-coordinate on the {@link Event#getClientY() client} from + * where we need to start scrolling towards the top. + */ + private int topBound = -1; + + /** + * The highest y-coordinate on the {@link Event#getClientY() client} + * from where we need to scrolling towards the bottom. + */ + private int bottomBound = -1; + + /** + * true if the pointer is selecting, false if + * the pointer is deselecting. + */ + private final boolean selectionPaint; + + /** + * The area where the selection acceleration takes place. If < + * {@link #GRADIENT_MIN_THRESHOLD_PX}, autoscrolling is disabled + */ + private final int gradientArea; + + /** + * The number of pixels per seconds we currently are scrolling (negative + * is towards the top, positive is towards the bottom). + */ + private double scrollSpeed = 0; + + private double prevTimestamp = 0; + + /** + * This field stores fractions of pixels to scroll, to make sure that + * we're able to scroll less than one px per frame. + */ + private double pixelsToScroll = 0.0d; + + /** Should this animator be running. */ + private boolean running = false; + + /** The handle in which this instance is running. */ + private AnimationHandle handle; + + /** The pointer's pageX coordinate of the first click. */ + private int initialPageX = -1; + + /** The pointer's pageY coordinate. */ + private int pageY; + + /** The logical index of the row that was most recently modified. */ + private int lastModifiedLogicalRow = -1; + + /** @see #doScrollAreaChecks(int) */ + private int finalTopBound; + + /** @see #doScrollAreaChecks(int) */ + private int finalBottomBound; + + private boolean scrollAreaShouldRebound = false; + + private final int bodyAbsoluteTop; + private final int bodyAbsoluteBottom; + + public AutoScrollerAndSelector(final int topBound, + final int bottomBound, final int gradientArea, + final boolean selectionPaint) { + finalTopBound = topBound; + finalBottomBound = bottomBound; + this.gradientArea = gradientArea; + this.selectionPaint = selectionPaint; + + bodyAbsoluteTop = getBodyClientTop(); + bodyAbsoluteBottom = getBodyClientBottom(); + } + + @Override + public void execute(final double timestamp) { + final double timeDiff = timestamp - prevTimestamp; + prevTimestamp = timestamp; + + reboundScrollArea(timeDiff); + + pixelsToScroll += scrollSpeed * (timeDiff / 1000.0d); + final int intPixelsToScroll = (int) pixelsToScroll; + pixelsToScroll -= intPixelsToScroll; + + if (intPixelsToScroll != 0) { + grid.setScrollTop(grid.getScrollTop() + intPixelsToScroll); + } + + int constrainedPageY = Math.max(bodyAbsoluteTop, + Math.min(bodyAbsoluteBottom, pageY)); + int logicalRow = getLogicalRowIndex(WidgetUtil.getElementFromPoint( + initialPageX, constrainedPageY)); + + int incrementOrDecrement = (logicalRow > lastModifiedLogicalRow) ? 1 + : -1; + + /* + * Both pageY and initialPageX have their initialized (and + * unupdated) values while the cursor hasn't moved since the first + * invocation. This will lead to logicalRow being -1, until the + * pointer has been moved. + */ + while (logicalRow != -1 && lastModifiedLogicalRow != logicalRow) { + lastModifiedLogicalRow += incrementOrDecrement; + setSelected(lastModifiedLogicalRow, selectionPaint); + } + + reschedule(); + } + + /** + * If the scroll are has been offset by the pointer starting out there, + * move it back a bit + */ + private void reboundScrollArea(double timeDiff) { + if (!scrollAreaShouldRebound) { + return; + } + + int reboundPx = (int) Math.ceil(SCROLL_AREA_REBOUND_PX_PER_MS + * timeDiff); + if (topBound < finalTopBound) { + topBound += reboundPx; + topBound = Math.min(topBound, finalTopBound); + updateScrollSpeed(pageY); + } else if (bottomBound > finalBottomBound) { + bottomBound -= reboundPx; + bottomBound = Math.max(bottomBound, finalBottomBound); + updateScrollSpeed(pageY); + } + } + + private void updateScrollSpeed(final int pointerPageY) { + + final double ratio; + if (pointerPageY < topBound) { + final double distance = pointerPageY - topBound; + ratio = Math.max(-1, distance / gradientArea); + } + + else if (pointerPageY > bottomBound) { + final double distance = pointerPageY - bottomBound; + ratio = Math.min(1, distance / gradientArea); + } + + else { + ratio = 0; + } + + scrollSpeed = ratio * SCROLL_TOP_SPEED_PX_SEC; + } + + public void start(int logicalRowIndex) { + running = true; + setSelected(logicalRowIndex, selectionPaint); + lastModifiedLogicalRow = logicalRowIndex; + reschedule(); + } + + public void stop() { + running = false; + + if (handle != null) { + handle.cancel(); + handle = null; + } + } + + private void reschedule() { + if (running && gradientArea >= GRADIENT_MIN_THRESHOLD_PX) { + handle = AnimationScheduler.get().requestAnimationFrame(this, + grid.getElement()); + } + } + + public void updatePointerCoords(int pageX, int pageY) { + doScrollAreaChecks(pageY); + updateScrollSpeed(pageY); + this.pageY = pageY; + + if (initialPageX == -1) { + initialPageX = pageX; + } + } + + /** + * This method checks whether the first pointer event started in an area + * that would start scrolling immediately, and does some actions + * accordingly. + *

+ * If it is, that scroll area will be offset "beyond" the pointer (above + * if pointer is towards the top, otherwise below). + *

+ * *) This behavior will change in + * future patches (henrik paul 2.7.2014) + */ + private void doScrollAreaChecks(int pageY) { + /* + * The first run makes sure that neither scroll position is + * underneath the finger, but offset to either direction from + * underneath the pointer. + */ + if (topBound == -1) { + topBound = Math.min(finalTopBound, pageY); + bottomBound = Math.max(finalBottomBound, pageY); + } + + /* + * Subsequent runs make sure that the scroll area grows (but doesn't + * shrink) with the finger, but no further than the final bound. + */ + else { + int oldTopBound = topBound; + if (topBound < finalTopBound) { + topBound = Math.max(topBound, + Math.min(finalTopBound, pageY)); + } + + int oldBottomBound = bottomBound; + if (bottomBound > finalBottomBound) { + bottomBound = Math.min(bottomBound, + Math.max(finalBottomBound, pageY)); + } + + final boolean topDidNotMove = oldTopBound == topBound; + final boolean bottomDidNotMove = oldBottomBound == bottomBound; + final boolean wasVerticalMovement = pageY != this.pageY; + scrollAreaShouldRebound = (topDidNotMove && bottomDidNotMove && wasVerticalMovement); + } + } + } + + /** + * This class makes sure that pointer movemenets are registered and + * delegated to the autoscroller so that it can: + *

    + *
  • modify the speed in which we autoscroll. + *
  • "paint" a new row with the selection. + *
+ * Essentially, when a pointer is pressed on the selection column, a native + * preview handler is registered (so that selection gestures can happen + * outside of the selection column). The handler itself makes sure that it's + * detached when the pointer is "lifted". + */ + private class AutoScrollHandler { + private AutoScrollerAndSelector autoScroller; + + /** The registration info for {@link #scrollPreviewHandler} */ + private HandlerRegistration handlerRegistration; + + private final NativePreviewHandler scrollPreviewHandler = new NativePreviewHandler() { + @Override + public void onPreviewNativeEvent(final NativePreviewEvent event) { + if (autoScroller == null) { + stop(); + return; + } + + final NativeEvent nativeEvent = event.getNativeEvent(); + int pageY = 0; + int pageX = 0; + switch (event.getTypeInt()) { + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + pageY = WidgetUtil.getTouchOrMouseClientY(nativeEvent); + pageX = WidgetUtil.getTouchOrMouseClientX(nativeEvent); + autoScroller.updatePointerCoords(pageX, pageY); + break; + case Event.ONMOUSEUP: + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + stop(); + break; + } + } + }; + + /** + * The top bound, as calculated from the {@link Event#getClientY() + * client} coordinates. + */ + private int topBound = -1; + + /** + * The bottom bound, as calculated from the {@link Event#getClientY() + * client} coordinates. + */ + private int bottomBound = -1; + + /** The size of the autoscroll acceleration area. */ + private int gradientArea; + + public void start(int logicalRowIndex) { + + SelectionModel model = grid.getSelectionModel(); + if (model instanceof Batched) { + Batched batchedModel = (Batched) model; + batchedModel.startBatchSelect(); + } + + /* + * bounds are updated whenever the autoscroll cycle starts, to make + * sure that the widget hasn't changed in size, moved around, or + * whatnot. + */ + updateScrollBounds(); + + assert handlerRegistration == null : "handlerRegistration was not null"; + assert autoScroller == null : "autoScroller was not null"; + handlerRegistration = Event + .addNativePreviewHandler(scrollPreviewHandler); + + autoScroller = new AutoScrollerAndSelector(topBound, bottomBound, + gradientArea, !isSelected(logicalRowIndex)); + autoScroller.start(logicalRowIndex); + } + + private void updateScrollBounds() { + final int topBorder = getBodyClientTop(); + final int bottomBorder = getBodyClientBottom(); + + topBound = topBorder + SCROLL_AREA_GRADIENT_PX; + bottomBound = bottomBorder - SCROLL_AREA_GRADIENT_PX; + gradientArea = SCROLL_AREA_GRADIENT_PX; + + // modify bounds if they're too tightly packed + if (bottomBound - topBound < MIN_NO_AUTOSCROLL_AREA_PX) { + int adjustment = MIN_NO_AUTOSCROLL_AREA_PX + - (bottomBound - topBound); + topBound -= adjustment / 2; + bottomBound += adjustment / 2; + gradientArea -= adjustment / 2; + } + } + + public void stop() { + if (handlerRegistration != null) { + handlerRegistration.removeHandler(); + handlerRegistration = null; + } + + if (autoScroller != null) { + autoScroller.stop(); + autoScroller = null; + } + + SelectionModel model = grid.getSelectionModel(); + if (model instanceof Batched) { + Batched batchedModel = (Batched) model; + batchedModel.commitBatchSelect(); + } + + removeNativeHandler(); + } + } + + private static final String LOGICAL_ROW_PROPERTY_INT = "vEscalatorLogicalRow"; + + private final Grid grid; + private HandlerRegistration nativePreviewHandlerRegistration; + + private final AutoScrollHandler autoScrollHandler = new AutoScrollHandler(); + + public MultiSelectionRenderer(final Grid grid) { + this.grid = grid; + } + + @Override + public void destroy() { + if (nativePreviewHandlerRegistration != null) { + removeNativeHandler(); + } + } + + @Override + public CheckBox createWidget() { + final CheckBox checkBox = GWT.create(CheckBox.class); + checkBox.setStylePrimaryName(grid.getStylePrimaryName() + + SELECTION_CHECKBOX_CLASSNAME); + CheckBoxEventHandler handler = new CheckBoxEventHandler(checkBox); + + // Sink events + checkBox.sinkBitlessEvent(BrowserEvents.MOUSEDOWN); + checkBox.sinkBitlessEvent(BrowserEvents.TOUCHSTART); + checkBox.sinkBitlessEvent(BrowserEvents.CLICK); + + // Add handlers + checkBox.addMouseDownHandler(handler); + checkBox.addTouchStartHandler(handler); + checkBox.addClickHandler(handler); + + return checkBox; + } + + @Override + public void render(final RendererCellReference cell, final Boolean data, + CheckBox checkBox) { + checkBox.setValue(data, false); + checkBox.setEnabled(!grid.isEditorActive()); + checkBox.getElement().setPropertyInt(LOGICAL_ROW_PROPERTY_INT, + cell.getRowIndex()); + } + + @Override + public Collection getConsumedEvents() { + final HashSet events = new HashSet(); + + /* + * this column's first interest is only to attach a NativePreventHandler + * that does all the magic. These events are the beginning of that + * cycle. + */ + events.add(BrowserEvents.MOUSEDOWN); + events.add(BrowserEvents.TOUCHSTART); + + return events; + } + + @Override + public boolean onBrowserEvent(final CellReference cell, + final NativeEvent event) { + if (BrowserEvents.TOUCHSTART.equals(event.getType()) + || (BrowserEvents.MOUSEDOWN.equals(event.getType()) && event + .getButton() == NativeEvent.BUTTON_LEFT)) { + startDragSelect(event, Element.as(event.getEventTarget())); + return true; + } else { + throw new IllegalStateException("received unexpected event: " + + event.getType()); + } + } + + private void startDragSelect(NativeEvent event, final Element target) { + injectNativeHandler(); + int logicalRowIndex = getLogicalRowIndex(target); + autoScrollHandler.start(logicalRowIndex); + event.preventDefault(); + event.stopPropagation(); + } + + private void injectNativeHandler() { + removeNativeHandler(); + nativePreviewHandlerRegistration = Event + .addNativePreviewHandler(new TouchEventHandler()); + } + + private void removeNativeHandler() { + if (nativePreviewHandlerRegistration != null) { + nativePreviewHandlerRegistration.removeHandler(); + nativePreviewHandlerRegistration = null; + } + } + + private int getLogicalRowIndex(final Element target) { + if (target == null) { + return -1; + } + + /* + * We can't simply go backwards until we find a first element, + * because of the table-in-table scenario. We need to, unfortunately, go + * up from our known root. + */ + final Element tbody = getTbodyElement(); + Element tr = tbody.getFirstChildElement(); + while (tr != null) { + if (tr.isOrHasChild(target)) { + final Element td = tr.getFirstChildElement(); + assert td != null : "Cell has disappeared"; + + final Element checkbox = td.getFirstChildElement(); + assert checkbox != null : "Checkbox has disappeared"; + + return checkbox.getPropertyInt(LOGICAL_ROW_PROPERTY_INT); + } + tr = tr.getNextSiblingElement(); + } + return -1; + } + + private TableElement getTableElement() { + final Element root = grid.getElement(); + final Element tablewrapper = Element.as(root.getChild(2)); + if (tablewrapper != null) { + return TableElement.as(tablewrapper.getFirstChildElement()); + } else { + return null; + } + } + + private TableSectionElement getTbodyElement() { + TableElement table = getTableElement(); + if (table != null) { + return table.getTBodies().getItem(0); + } else { + return null; + } + } + + private TableSectionElement getTheadElement() { + TableElement table = getTableElement(); + if (table != null) { + return table.getTHead(); + } else { + return null; + } + } + + private TableSectionElement getTfootElement() { + TableElement table = getTableElement(); + if (table != null) { + return table.getTFoot(); + } else { + return null; + } + } + + /** Get the "top" of an element in relation to "client" coordinates. */ + private int getClientTop(final Element e) { + return e.getAbsoluteTop(); + } + + private int getBodyClientBottom() { + return getClientTop(getTfootElement()) - 1; + } + + private int getBodyClientTop() { + // Off by one pixel miscalculation. possibly border related. + return getClientTop(grid.getElement()) + + getTheadElement().getOffsetHeight() + 1; + } + + protected boolean isSelected(final int logicalRow) { + return grid.isSelected(grid.getDataSource().getRow(logicalRow)); + } + + protected void setSelected(final int logicalRow, final boolean select) { + T row = grid.getDataSource().getRow(logicalRow); + if (select) { + grid.select(row); + } else { + grid.deselect(row); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionEvent.java new file mode 100644 index 0000000000..528beb5809 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionEvent.java @@ -0,0 +1,178 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.selection; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.client.widgets.Grid; + +/** + * Event object describing a change in Grid row selection state. + * + * @since 7.4 + * @author Vaadin Ltd + */ +@SuppressWarnings("rawtypes") +public class SelectionEvent extends GwtEvent { + + private static final Type eventType = new Type(); + + private final Grid grid; + private final List added; + private final List removed; + private final boolean batched; + + /** + * Creates an event with a single added or removed row. + * + * @param grid + * grid reference, used for getSource + * @param added + * the added row, or null if a row was not added + * @param removed + * the removed row, or null if a row was not removed + * @param batched + * whether or not this selection change event is triggered during + * a batched selection/deselection action + * @see SelectionModel.Multi.Batched + */ + public SelectionEvent(Grid grid, T added, T removed, boolean batched) { + this.grid = grid; + this.batched = batched; + + if (added != null) { + this.added = Collections.singletonList(added); + } else { + this.added = Collections.emptyList(); + } + + if (removed != null) { + this.removed = Collections.singletonList(removed); + } else { + this.removed = Collections.emptyList(); + } + } + + /** + * Creates an event where several rows have been added or removed. + * + * @param grid + * Grid reference, used for getSource + * @param added + * a collection of added rows, or null if no rows + * were added + * @param removed + * a collection of removed rows, or null if no rows + * were removed + * @param batched + * whether or not this selection change event is triggered during + * a batched selection/deselection action + * @see SelectionModel.Multi.Batched + */ + public SelectionEvent(Grid grid, Collection added, + Collection removed, boolean batched) { + this.grid = grid; + this.batched = batched; + + if (added != null) { + this.added = new ArrayList(added); + } else { + this.added = Collections.emptyList(); + } + + if (removed != null) { + this.removed = new ArrayList(removed); + } else { + this.removed = Collections.emptyList(); + } + } + + /** + * Gets a reference to the Grid object that fired this event. + * + * @return a grid reference + */ + @Override + public Grid getSource() { + return grid; + } + + /** + * Gets all rows added to the selection since the last + * {@link SelectionEvent} . + * + * @return a collection of added rows. Empty collection if no rows were + * added. + */ + public Collection getAdded() { + return Collections.unmodifiableCollection(added); + } + + /** + * Gets all rows removed from the selection since the last + * {@link SelectionEvent}. + * + * @return a collection of removed rows. Empty collection if no rows were + * removed. + */ + public Collection getRemoved() { + return Collections.unmodifiableCollection(removed); + } + + /** + * Gets currently selected rows. + * + * @return a non-null collection containing all currently selected rows. + */ + public Collection getSelected() { + return grid.getSelectedRows(); + } + + /** + * Gets a type identifier for this event. + * + * @return a {@link Type} identifier. + */ + public static Type getType() { + return eventType; + } + + @Override + public Type getAssociatedType() { + return eventType; + } + + @Override + @SuppressWarnings("unchecked") + protected void dispatch(SelectionHandler handler) { + handler.onSelect(this); + } + + /** + * Checks if this selection change event is fired during a batched + * selection/deselection operation. + * + * @return true iff this event is fired during a batched + * selection/deselection operation + */ + public boolean isBatchedSelection() { + return batched; + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionHandler.java new file mode 100644 index 0000000000..4f939fa798 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.selection; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler for {@link SelectionEvent}s. + * + * @author Vaadin Ltd + * @param + * The row data type + * @since 7.4 + */ +public interface SelectionHandler extends EventHandler { + + /** + * Called when a selection model's selection state is changed. + * + * @param event + * a selection event, containing info about rows that have been + * added to or removed from the selection. + */ + public void onSelect(SelectionEvent event); + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModel.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModel.java new file mode 100644 index 0000000000..ec36ab52e8 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModel.java @@ -0,0 +1,258 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.selection; + +import java.util.Collection; + +import com.vaadin.client.renderers.Renderer; +import com.vaadin.client.widgets.Grid; + +/** + * Common interface for all selection models. + *

+ * Selection models perform tracking of selected rows in the Grid, as well as + * dispatching events when the selection state changes. + * + * @author Vaadin Ltd + * @param + * Grid's row type + * @since 7.4 + */ +public interface SelectionModel { + + /** + * Return true if the provided row is considered selected under the + * implementing selection model. + * + * @param row + * row object instance + * @return true, if the row given as argument is considered + * selected. + */ + public boolean isSelected(T row); + + /** + * Return the {@link Renderer} responsible for rendering the selection + * column. + * + * @return a renderer instance. If null is returned, a selection column will + * not be drawn. + */ + public Renderer getSelectionColumnRenderer(); + + /** + * Tells this SelectionModel which Grid it belongs to. + *

+ * Implementations are free to have this be a no-op. This method is called + * internally by Grid. + * + * @param grid + * a {@link Grid} instance; null when removing from + * Grid + */ + public void setGrid(Grid grid); + + /** + * Resets the SelectionModel to the initial state. + *

+ * This method can be called internally, for example, when the attached + * Grid's data source changes. + */ + public void reset(); + + /** + * Returns a Collection containing all selected rows. + * + * @return a non-null collection. + */ + public Collection getSelectedRows(); + + /** + * Selection model that allows a maximum of one row to be selected at any + * one time. + * + * @param + * type parameter corresponding with Grid row type + */ + public interface Single extends SelectionModel { + + /** + * Selects a row. + * + * @param row + * a {@link Grid} row object + * @return true, if this row as not previously selected. + */ + public boolean select(T row); + + /** + * Deselects a row. + *

+ * This is a no-op unless {@link row} is the currently selected row. + * + * @param row + * a {@link Grid} row object + * @return true, if the currently selected row was deselected. + */ + public boolean deselect(T row); + + /** + * Returns the currently selected row. + * + * @return a {@link Grid} row object or null, if nothing is selected. + */ + public T getSelectedRow(); + + /** + * Sets whether it's allowed to deselect the selected row through the + * UI. Deselection is allowed by default. + * + * @param deselectAllowed + * true if the selected row can be deselected + * without selecting another row instead; otherwise + * false. + */ + public void setDeselectAllowed(boolean deselectAllowed); + + /** + * Sets whether it's allowed to deselect the selected row through the + * UI. + * + * @return true if deselection is allowed; otherwise + * false + */ + public boolean isDeselectAllowed(); + + } + + /** + * Selection model that allows for several rows to be selected at once. + * + * @param + * type parameter corresponding with Grid row type + */ + public interface Multi extends SelectionModel { + + /** + * A multi selection model that can send selections and deselections in + * a batch, instead of committing them one-by-one. + * + * @param + * type parameter corresponding with Grid row type + */ + public interface Batched extends Multi { + /** + * Starts a batch selection. + *

+ * Any commands to any select or deselect method will be batched + * into one, and a final selection event will be fired when + * {@link #commitBatchSelect()} is called. + *

+ * Note: {@link SelectionEvent SelectionChangeEvents} will + * still be fired for each selection/deselection. You should check + * whether the event is a part of a batch or not with + * {@link SelectionEvent#isBatchedSelection()}. + */ + public void startBatchSelect(); + + /** + * Commits and ends a batch selection. + *

+ * Any and all selections and deselections since the last invocation + * of {@link #startBatchSelect()} will be fired at once as one + * collated {@link SelectionEvent}. + */ + public void commitBatchSelect(); + + /** + * Checks whether or not a batch has been started. + * + * @return true iff a batch has been started + */ + public boolean isBeingBatchSelected(); + + /** + * Gets all the rows that would become selected in this batch. + * + * @return a collection of the rows that would become selected + */ + public Collection getSelectedRowsBatch(); + + /** + * Gets all the rows that would become deselected in this batch. + * + * @return a collection of the rows that would become deselected + */ + public Collection getDeselectedRowsBatch(); + } + + /** + * Selects one or more rows. + * + * @param rows + * {@link Grid} row objects + * @return true, if the set of selected rows was changed. + */ + public boolean select(T... rows); + + /** + * Deselects one or more rows. + * + * @param rows + * Grid row objects + * @return true, if the set of selected rows was changed. + */ + public boolean deselect(T... rows); + + /** + * De-selects all rows. + * + * @return true, if any row was previously selected. + */ + public boolean deselectAll(); + + /** + * Select all rows in a {@link Collection}. + * + * @param rows + * a collection of Grid row objects + * @return true, if the set of selected rows was changed. + */ + public boolean select(Collection rows); + + /** + * Deselect all rows in a {@link Collection}. + * + * @param rows + * a collection of Grid row objects + * @return true, if the set of selected rows was changed. + */ + public boolean deselect(Collection rows); + + } + + /** + * Interface for a selection model that does not allow anything to be + * selected. + * + * @param + * type parameter corresponding with Grid row type + */ + public interface None extends SelectionModel { + + } + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelMulti.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelMulti.java new file mode 100644 index 0000000000..d654a28b7d --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelMulti.java @@ -0,0 +1,273 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.selection; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import com.vaadin.client.data.DataSource.RowHandle; +import com.vaadin.client.renderers.Renderer; +import com.vaadin.client.widgets.Grid; + +/** + * Multi-row selection model. + * + * @author Vaadin Ltd + * @since 7.4 + */ +public class SelectionModelMulti extends AbstractRowHandleSelectionModel + implements SelectionModel.Multi.Batched { + + private final LinkedHashSet> selectedRows; + private Renderer renderer; + private Grid grid; + + private boolean batchStarted = false; + private final LinkedHashSet> selectionBatch = new LinkedHashSet>(); + private final LinkedHashSet> deselectionBatch = new LinkedHashSet>(); + + /* Event handling for selection with space key */ + private SpaceSelectHandler spaceSelectHandler; + + public SelectionModelMulti() { + grid = null; + renderer = null; + selectedRows = new LinkedHashSet>(); + } + + @Override + public boolean isSelected(T row) { + return isSelectedByHandle(grid.getDataSource().getHandle(row)); + } + + @Override + public Renderer getSelectionColumnRenderer() { + return renderer; + } + + @Override + public void setGrid(Grid grid) { + if (this.grid != null && grid != null) { + // Trying to replace grid + throw new IllegalStateException( + "Selection model is already attached to a grid. " + + "Remove the selection model first from " + + "the grid and then add it."); + } + + this.grid = grid; + if (this.grid != null) { + spaceSelectHandler = new SpaceSelectHandler(grid); + this.renderer = new MultiSelectionRenderer(grid); + } else { + spaceSelectHandler.removeHandler(); + spaceSelectHandler = null; + this.renderer = null; + } + + } + + @Override + public boolean select(T... rows) { + if (rows == null) { + throw new IllegalArgumentException("Rows cannot be null"); + } + return select(Arrays.asList(rows)); + } + + @Override + public boolean deselect(T... rows) { + if (rows == null) { + throw new IllegalArgumentException("Rows cannot be null"); + } + return deselect(Arrays.asList(rows)); + } + + @Override + public boolean deselectAll() { + if (selectedRows.size() > 0) { + + @SuppressWarnings("unchecked") + final LinkedHashSet> selectedRowsClone = (LinkedHashSet>) selectedRows + .clone(); + SelectionEvent event = new SelectionEvent(grid, null, + getSelectedRows(), isBeingBatchSelected()); + selectedRows.clear(); + + if (isBeingBatchSelected()) { + selectionBatch.clear(); + deselectionBatch.clear(); + deselectionBatch.addAll(selectedRowsClone); + } + + grid.fireEvent(event); + return true; + } + return false; + } + + @Override + public boolean select(Collection rows) { + if (rows == null) { + throw new IllegalArgumentException("Rows cannot be null"); + } + + Set added = new LinkedHashSet(); + + for (T row : rows) { + RowHandle handle = grid.getDataSource().getHandle(row); + if (selectByHandle(handle)) { + added.add(row); + } + } + + if (added.size() > 0) { + grid.fireEvent(new SelectionEvent(grid, added, null, + isBeingBatchSelected())); + + return true; + } + return false; + } + + @Override + public boolean deselect(Collection rows) { + if (rows == null) { + throw new IllegalArgumentException("Rows cannot be null"); + } + + Set removed = new LinkedHashSet(); + + for (T row : rows) { + RowHandle handle = grid.getDataSource().getHandle(row); + if (deselectByHandle(handle)) { + removed.add(row); + } + } + + if (removed.size() > 0) { + grid.fireEvent(new SelectionEvent(grid, null, removed, + isBeingBatchSelected())); + return true; + } + return false; + } + + protected boolean isSelectedByHandle(RowHandle handle) { + return selectedRows.contains(handle); + } + + @Override + protected boolean selectByHandle(RowHandle handle) { + if (selectedRows.add(handle)) { + handle.pin(); + + if (isBeingBatchSelected()) { + deselectionBatch.remove(handle); + selectionBatch.add(handle); + } + + return true; + } + return false; + } + + @Override + protected boolean deselectByHandle(RowHandle handle) { + if (selectedRows.remove(handle)) { + + if (!isBeingBatchSelected()) { + handle.unpin(); + } else { + selectionBatch.remove(handle); + deselectionBatch.add(handle); + } + return true; + } + return false; + } + + @Override + public Collection getSelectedRows() { + Set selected = new LinkedHashSet(); + for (RowHandle handle : selectedRows) { + selected.add(handle.getRow()); + } + return Collections.unmodifiableSet(selected); + } + + @Override + public void reset() { + deselectAll(); + } + + @Override + public void startBatchSelect() { + assert !isBeingBatchSelected() : "Batch has already been started"; + batchStarted = true; + } + + @Override + public void commitBatchSelect() { + assert isBeingBatchSelected() : "Batch was never started"; + if (!isBeingBatchSelected()) { + return; + } + + batchStarted = false; + + final Collection added = getSelectedRowsBatch(); + selectionBatch.clear(); + + final Collection removed = getDeselectedRowsBatch(); + + // unpin deselected rows + for (RowHandle handle : deselectionBatch) { + handle.unpin(); + } + deselectionBatch.clear(); + + grid.fireEvent(new SelectionEvent(grid, added, removed, + isBeingBatchSelected())); + } + + @Override + public boolean isBeingBatchSelected() { + return batchStarted; + } + + @Override + public Collection getSelectedRowsBatch() { + return rowHandlesToRows(selectionBatch); + } + + @Override + public Collection getDeselectedRowsBatch() { + return rowHandlesToRows(deselectionBatch); + } + + private ArrayList rowHandlesToRows(Collection> rowHandles) { + ArrayList rows = new ArrayList(rowHandles.size()); + for (RowHandle handle : rowHandles) { + rows.add(handle.getRow()); + } + return rows; + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelNone.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelNone.java new file mode 100644 index 0000000000..4a8b203a94 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelNone.java @@ -0,0 +1,73 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.selection; + +import java.util.Collection; +import java.util.Collections; + +import com.vaadin.client.data.DataSource.RowHandle; +import com.vaadin.client.renderers.Renderer; +import com.vaadin.client.widgets.Grid; + +/** + * No-row selection model. + * + * @author Vaadin Ltd + * @since 7.4 + */ +public class SelectionModelNone extends AbstractRowHandleSelectionModel + implements SelectionModel.None { + + @Override + public boolean isSelected(T row) { + return false; + } + + @Override + public Renderer getSelectionColumnRenderer() { + return null; + } + + @Override + public void setGrid(Grid grid) { + // noop + } + + @Override + public void reset() { + // noop + } + + @Override + public Collection getSelectedRows() { + return Collections.emptySet(); + } + + @Override + protected boolean selectByHandle(RowHandle handle) + throws UnsupportedOperationException { + throw new UnsupportedOperationException("This selection model " + + "does not support selection"); + } + + @Override + protected boolean deselectByHandle(RowHandle handle) + throws UnsupportedOperationException { + throw new UnsupportedOperationException("This selection model " + + "does not support deselection"); + } + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelSingle.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelSingle.java new file mode 100644 index 0000000000..38605db12c --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelSingle.java @@ -0,0 +1,175 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.selection; + +import java.util.Collection; +import java.util.Collections; + +import com.vaadin.client.data.DataSource.RowHandle; +import com.vaadin.client.renderers.Renderer; +import com.vaadin.client.widgets.Grid; + +/** + * Single-row selection model. + * + * @author Vaadin Ltd + * @since 7.4 + */ +public class SelectionModelSingle extends AbstractRowHandleSelectionModel + implements SelectionModel.Single { + + private Grid grid; + private RowHandle selectedRow; + + /** Event handling for selection with space key */ + private SpaceSelectHandler spaceSelectHandler; + + /** Event handling for selection by clicking cells */ + private ClickSelectHandler clickSelectHandler; + + private boolean deselectAllowed = true; + + @Override + public boolean isSelected(T row) { + return selectedRow != null + && selectedRow.equals(grid.getDataSource().getHandle(row)); + } + + @Override + public Renderer getSelectionColumnRenderer() { + // No Selection column renderer for single selection + return null; + } + + @Override + public void setGrid(Grid grid) { + if (this.grid != null && grid != null) { + // Trying to replace grid + throw new IllegalStateException( + "Selection model is already attached to a grid. " + + "Remove the selection model first from " + + "the grid and then add it."); + } + + this.grid = grid; + if (this.grid != null) { + spaceSelectHandler = new SpaceSelectHandler(grid); + clickSelectHandler = new ClickSelectHandler(grid); + updateHandlerDeselectAllowed(); + } else { + spaceSelectHandler.removeHandler(); + clickSelectHandler.removeHandler(); + spaceSelectHandler = null; + clickSelectHandler = null; + } + } + + @Override + public boolean select(T row) { + + if (row == null) { + throw new IllegalArgumentException("Row cannot be null"); + } + + T removed = getSelectedRow(); + if (selectByHandle(grid.getDataSource().getHandle(row))) { + grid.fireEvent(new SelectionEvent(grid, row, removed, false)); + + return true; + } + return false; + } + + @Override + public boolean deselect(T row) { + + if (row == null) { + throw new IllegalArgumentException("Row cannot be null"); + } + + if (isSelected(row)) { + deselectByHandle(selectedRow); + grid.fireEvent(new SelectionEvent(grid, null, row, false)); + return true; + } + + return false; + } + + @Override + public T getSelectedRow() { + return (selectedRow != null ? selectedRow.getRow() : null); + } + + @Override + public void reset() { + if (selectedRow != null) { + deselect(getSelectedRow()); + } + } + + @Override + public Collection getSelectedRows() { + if (getSelectedRow() != null) { + return Collections.singleton(getSelectedRow()); + } + return Collections.emptySet(); + } + + @Override + protected boolean selectByHandle(RowHandle handle) { + if (handle != null && !handle.equals(selectedRow)) { + deselectByHandle(selectedRow); + selectedRow = handle; + selectedRow.pin(); + return true; + } else { + return false; + } + } + + @Override + protected boolean deselectByHandle(RowHandle handle) { + if (handle != null && handle.equals(selectedRow)) { + selectedRow.unpin(); + selectedRow = null; + return true; + } else { + return false; + } + } + + @Override + public void setDeselectAllowed(boolean deselectAllowed) { + this.deselectAllowed = deselectAllowed; + updateHandlerDeselectAllowed(); + } + + @Override + public boolean isDeselectAllowed() { + return deselectAllowed; + } + + private void updateHandlerDeselectAllowed() { + if (spaceSelectHandler != null) { + spaceSelectHandler.setDeselectAllowed(deselectAllowed); + } + if (clickSelectHandler != null) { + clickSelectHandler.setDeselectAllowed(deselectAllowed); + } + } + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/SpaceSelectHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/SpaceSelectHandler.java new file mode 100644 index 0000000000..3e04a6dfac --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/SpaceSelectHandler.java @@ -0,0 +1,137 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.selection; + +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.shared.HandlerRegistration; +import com.vaadin.client.widget.grid.DataAvailableEvent; +import com.vaadin.client.widget.grid.DataAvailableHandler; +import com.vaadin.client.widget.grid.events.BodyKeyDownHandler; +import com.vaadin.client.widget.grid.events.BodyKeyUpHandler; +import com.vaadin.client.widget.grid.events.GridKeyDownEvent; +import com.vaadin.client.widget.grid.events.GridKeyUpEvent; +import com.vaadin.client.widgets.Grid; +import com.vaadin.shared.ui.grid.ScrollDestination; + +/** + * Generic class to perform selections when pressing space key. + * + * @author Vaadin Ltd + * @param + * row data type + * @since 7.4 + */ +public class SpaceSelectHandler { + + /** + * Handler for space key down events in Grid Body + */ + private class SpaceKeyDownHandler implements BodyKeyDownHandler { + private HandlerRegistration scrollHandler = null; + + @Override + public void onKeyDown(GridKeyDownEvent event) { + if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE || spaceDown) { + return; + } + + // Prevent space page scrolling + event.getNativeEvent().preventDefault(); + + spaceDown = true; + final int rowIndex = event.getFocusedCell().getRowIndex(); + + if (scrollHandler != null) { + scrollHandler.removeHandler(); + scrollHandler = null; + } + + scrollHandler = grid + .addDataAvailableHandler(new DataAvailableHandler() { + + @Override + public void onDataAvailable( + DataAvailableEvent dataAvailableEvent) { + if (dataAvailableEvent.getAvailableRows().contains( + rowIndex)) { + setSelected(grid, rowIndex); + scrollHandler.removeHandler(); + scrollHandler = null; + } + } + }); + grid.scrollToRow(rowIndex, ScrollDestination.ANY); + } + + protected void setSelected(Grid grid, int rowIndex) { + T row = grid.getDataSource().getRow(rowIndex); + + if (!grid.isSelected(row)) { + grid.select(row); + } else if (deselectAllowed) { + grid.deselect(row); + } + } + } + + private boolean spaceDown = false; + private Grid grid; + private HandlerRegistration spaceUpHandler; + private HandlerRegistration spaceDownHandler; + private boolean deselectAllowed = true; + + /** + * Constructor for SpaceSelectHandler. This constructor will add all + * necessary handlers for selecting rows with space. + * + * @param grid + * grid to attach to + */ + public SpaceSelectHandler(Grid grid) { + this.grid = grid; + spaceDownHandler = grid + .addBodyKeyDownHandler(new SpaceKeyDownHandler()); + spaceUpHandler = grid.addBodyKeyUpHandler(new BodyKeyUpHandler() { + + @Override + public void onKeyUp(GridKeyUpEvent event) { + if (event.getNativeKeyCode() == KeyCodes.KEY_SPACE) { + spaceDown = false; + } + } + }); + } + + /** + * Clean up function for removing all now obsolete handlers. + */ + public void removeHandler() { + spaceDownHandler.removeHandler(); + spaceUpHandler.removeHandler(); + } + + /** + * Sets whether pressing space for the currently selected row should + * deselect the row. + * + * @param deselectAllowed + * true to allow deselecting the selected row; + * otherwise false + */ + public void setDeselectAllowed(boolean deselectAllowed) { + this.deselectAllowed = deselectAllowed; + } +} \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/sort/Sort.java b/client/src/main/java/com/vaadin/client/widget/grid/sort/Sort.java new file mode 100644 index 0000000000..b1f3c6e39a --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/sort/Sort.java @@ -0,0 +1,154 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.sort; + +import java.util.ArrayList; +import java.util.List; + +import com.vaadin.client.widgets.Grid; +import com.vaadin.shared.data.sort.SortDirection; + +/** + * Fluid Sort descriptor object. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class Sort { + + private final Sort previous; + private final SortOrder order; + private final int count; + + /** + * Basic constructor, used by the {@link #by(GridColumn)} and + * {@link #by(GridColumn, SortDirection)} methods. + * + * @param column + * a grid column + * @param direction + * a sort direction + */ + private Sort(Grid.Column column, SortDirection direction) { + previous = null; + count = 1; + order = new SortOrder(column, direction); + } + + /** + * Extension constructor. Performs object equality checks on all previous + * Sort objects in the chain to make sure that the column being passed in + * isn't already used earlier (which would indicate a bug). If the column + * has been used before, this constructor throws an + * {@link IllegalStateException}. + * + * @param previous + * the sort instance that the new sort instance is to extend + * @param column + * a (previously unused) grid column reference + * @param direction + * a sort direction + */ + private Sort(Sort previous, Grid.Column column, + SortDirection direction) { + this.previous = previous; + count = previous.count + 1; + order = new SortOrder(column, direction); + + Sort s = previous; + while (s != null) { + if (s.order.getColumn() == column) { + throw new IllegalStateException( + "Can not sort along the same column twice"); + } + s = s.previous; + } + } + + /** + * Start building a Sort order by sorting a provided column in ascending + * order. + * + * @param column + * a grid column object reference + * @return a sort instance, typed to the grid data type + */ + public static Sort by(Grid.Column column) { + return by(column, SortDirection.ASCENDING); + } + + /** + * Start building a Sort order by sorting a provided column. + * + * @param column + * a grid column object reference + * @param direction + * indicator of sort direction - either ascending or descending + * @return a sort instance, typed to the grid data type + */ + public static Sort by(Grid.Column column, SortDirection direction) { + return new Sort(column, direction); + } + + /** + * Continue building a Sort order. The provided column is sorted in + * ascending order if the previously added columns have been evaluated as + * equals. + * + * @param column + * a grid column object reference + * @return a sort instance, typed to the grid data type + */ + public Sort then(Grid.Column column) { + return then(column, SortDirection.ASCENDING); + } + + /** + * Continue building a Sort order. The provided column is sorted in + * specified order if the previously added columns have been evaluated as + * equals. + * + * @param column + * a grid column object reference + * @param direction + * indicator of sort direction - either ascending or descending + * @return a sort instance, typed to the grid data type + */ + public Sort then(Grid.Column column, SortDirection direction) { + return new Sort(this, column, direction); + } + + /** + * Build a sort order list. This method is called internally by Grid when + * calling {@link com.vaadin.client.ui.grid.Grid#sort(Sort)}, but can also + * be called manually to create a SortOrder list, which can also be provided + * directly to Grid. + * + * @return a sort order list. + */ + public List build() { + + List order = new ArrayList(count); + + Sort s = this; + for (int i = count - 1; i >= 0; --i) { + order.add(0, s.order); + s = s.previous; + } + + return order; + } +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/sort/SortEvent.java b/client/src/main/java/com/vaadin/client/widget/grid/sort/SortEvent.java new file mode 100644 index 0000000000..2aad6e4f95 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/sort/SortEvent.java @@ -0,0 +1,113 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.sort; + +import java.util.List; + +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.client.widgets.Grid; + +/** + * A sort event, fired by the Grid when it needs its data source to provide data + * sorted in a specific manner. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class SortEvent extends GwtEvent> { + + private static final Type> TYPE = new Type>(); + + private final Grid grid; + private final List order; + private final boolean userOriginated; + + /** + * Creates a new Sort Event. All provided parameters are final, and passed + * on as-is. + * + * @param grid + * a grid reference + * @param order + * an array dictating the desired sort order of the data source + * @param originator + * a value indicating where this event originated from + */ + public SortEvent(Grid grid, List order, boolean userOriginated) { + this.grid = grid; + this.order = order; + this.userOriginated = userOriginated; + } + + @Override + public Type> getAssociatedType() { + return TYPE; + } + + /** + * Static access to the GWT event type identifier associated with this Event + * class + * + * @return a type object, uniquely describing this event type. + */ + public static Type> getType() { + return TYPE; + } + + /** + * Get access to the Grid that fired this event + * + * @return the grid instance + */ + @Override + public Grid getSource() { + return grid; + } + + /** + * Get access to the Grid that fired this event + * + * @return the grid instance + */ + public Grid getGrid() { + return grid; + } + + /** + * Get the sort ordering that is to be applied to the Grid + * + * @return a list of sort order objects + */ + public List getOrder() { + return order; + } + + /** + * Returns whether this event originated from actions done by the user. + * + * @return true if sort event originated from user interaction + */ + public boolean isUserOriginated() { + return userOriginated; + } + + @SuppressWarnings("unchecked") + @Override + protected void dispatch(SortHandler handler) { + ((SortHandler) handler).sort(this); + } + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/sort/SortHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/sort/SortHandler.java new file mode 100644 index 0000000000..330cbe9d58 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/sort/SortHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.sort; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler for a Grid sort event, called when the Grid needs its data source to + * provide data sorted in a specific manner. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public interface SortHandler extends EventHandler { + + /** + * Handle sorting of the Grid. This method is called when a re-sorting of + * the Grid's data is requested. + * + * @param event + * the sort event + */ + public void sort(SortEvent event); + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/sort/SortOrder.java b/client/src/main/java/com/vaadin/client/widget/grid/sort/SortOrder.java new file mode 100644 index 0000000000..8166f1e6ed --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/sort/SortOrder.java @@ -0,0 +1,90 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.sort; + +import com.vaadin.client.widgets.Grid; +import com.vaadin.shared.data.sort.SortDirection; + +/** + * Sort order descriptor. Contains column and direction references. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class SortOrder { + + private final Grid.Column column; + private final SortDirection direction; + + /** + * Create a sort order descriptor with a default sorting direction value of + * {@link SortDirection#ASCENDING}. + * + * @param column + * a grid column descriptor object + */ + public SortOrder(Grid.Column column) { + this(column, SortDirection.ASCENDING); + } + + /** + * Create a sort order descriptor. + * + * @param column + * a grid column descriptor object + * @param direction + * a sorting direction value (ascending or descending) + */ + public SortOrder(Grid.Column column, SortDirection direction) { + if (column == null) { + throw new IllegalArgumentException( + "Grid column reference can not be null!"); + } + if (direction == null) { + throw new IllegalArgumentException( + "Direction value can not be null!"); + } + this.column = column; + this.direction = direction; + } + + /** + * Returns the {@link GridColumn} reference given in the constructor. + * + * @return a grid column reference + */ + public Grid.Column getColumn() { + return column; + } + + /** + * Returns the {@link SortDirection} value given in the constructor. + * + * @return a sort direction value + */ + public SortDirection getDirection() { + return direction; + } + + /** + * Returns a new SortOrder object with the sort direction reversed. + * + * @return a new sort order object + */ + public SortOrder getOpposite() { + return new SortOrder(column, direction.getOpposite()); + } +} diff --git a/client/src/main/java/com/vaadin/client/widgets/Escalator.java b/client/src/main/java/com/vaadin/client/widgets/Escalator.java new file mode 100644 index 0000000000..3585be1d60 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widgets/Escalator.java @@ -0,0 +1,6697 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widgets; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gwt.animation.client.Animation; +import com.google.gwt.animation.client.AnimationScheduler; +import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback; +import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle; +import com.google.gwt.core.client.Duration; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.NodeList; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.TableCellElement; +import com.google.gwt.dom.client.TableRowElement; +import com.google.gwt.dom.client.TableSectionElement; +import com.google.gwt.dom.client.Touch; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.logging.client.LogConfiguration; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.RequiresResize; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.UIObject; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.DeferredWorker; +import com.vaadin.client.Profiler; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.SubPartAware; +import com.vaadin.client.widget.escalator.Cell; +import com.vaadin.client.widget.escalator.ColumnConfiguration; +import com.vaadin.client.widget.escalator.EscalatorUpdater; +import com.vaadin.client.widget.escalator.FlyweightCell; +import com.vaadin.client.widget.escalator.FlyweightRow; +import com.vaadin.client.widget.escalator.PositionFunction; +import com.vaadin.client.widget.escalator.PositionFunction.AbsolutePosition; +import com.vaadin.client.widget.escalator.PositionFunction.Translate3DPosition; +import com.vaadin.client.widget.escalator.PositionFunction.TranslatePosition; +import com.vaadin.client.widget.escalator.PositionFunction.WebkitTranslate3DPosition; +import com.vaadin.client.widget.escalator.Row; +import com.vaadin.client.widget.escalator.RowContainer; +import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; +import com.vaadin.client.widget.escalator.RowVisibilityChangeEvent; +import com.vaadin.client.widget.escalator.RowVisibilityChangeHandler; +import com.vaadin.client.widget.escalator.ScrollbarBundle; +import com.vaadin.client.widget.escalator.ScrollbarBundle.HorizontalScrollbarBundle; +import com.vaadin.client.widget.escalator.ScrollbarBundle.VerticalScrollbarBundle; +import com.vaadin.client.widget.escalator.Spacer; +import com.vaadin.client.widget.escalator.SpacerUpdater; +import com.vaadin.client.widget.grid.events.ScrollEvent; +import com.vaadin.client.widget.grid.events.ScrollHandler; +import com.vaadin.client.widgets.Escalator.JsniUtil.TouchHandlerBundle; +import com.vaadin.shared.ui.grid.HeightMode; +import com.vaadin.shared.ui.grid.Range; +import com.vaadin.shared.ui.grid.ScrollDestination; +import com.vaadin.shared.util.SharedUtil; + +/*- + + Maintenance Notes! Reading these might save your day. + (note for editors: line width is 80 chars, including the + one-space indentation) + + + == Row Container Structure + + AbstractRowContainer + |-- AbstractStaticRowContainer + | |-- HeaderRowContainer + | `-- FooterContainer + `---- BodyRowContainerImpl + + AbstractRowContainer is intended to contain all common logic + between RowContainers. It manages the bookkeeping of row + count, makes sure that all individual cells are rendered + the same way, and so on. + + AbstractStaticRowContainer has some special logic that is + required by all RowContainers that don't scroll (hence the + word "static"). HeaderRowContainer and FooterRowContainer + are pretty thin special cases of a StaticRowContainer + (mostly relating to positioning of the root element). + + BodyRowContainerImpl could also be split into an additional + "AbstractScrollingRowContainer", but I felt that no more + inner classes were needed. So it contains both logic + required for making things scroll about, and equivalent + special cases for layouting, as are found in + Header/FooterRowContainers. + + + == The Three Indices + + Each RowContainer can be thought to have three levels of + indices for any given displayed row (but the distinction + matters primarily for the BodyRowContainerImpl, because of + the way it scrolls through data): + + - Logical index + - Physical (or DOM) index + - Visual index + + LOGICAL INDEX is the index that is linked to the data + source. If you want your data source to represent a SQL + database with 10 000 rows, the 7 000:th row in the SQL has a + logical index of 6 999, since the index is 0-based (unless + that data source does some funky logic). + + PHYSICAL INDEX is the index for a row that you see in a + browser's DOM inspector. If your row is the second + element within a tag, it has a physical index of 1 + (because of 0-based indices). In Header and + FooterRowContainers, you are safe to assume that the logical + index is the same as the physical index. But because the + BodyRowContainerImpl never displays large data sources + entirely in the DOM, a physical index usually has no + apparent direct relationship with its logical index. + + VISUAL INDEX is the index relating to the order that you + see a row in, in the browser, as it is rendered. The + topmost row is 0, the second is 1, and so on. The visual + index is similar to the physical index in the sense that + Header and FooterRowContainers can assume a 1:1 + relationship between visual index and logical index. And + again, BodyRowContainerImpl has no such relationship. The + body's visual index has additionally no apparent + relationship with its physical index. Because the tags + are reused in the body and visually repositioned with CSS + as the user scrolls, the relationship between physical + index and visual index is quickly broken. You can get an + element's visual index via the field + BodyRowContainerImpl.visualRowOrder. + + Currently, the physical and visual indices are kept in sync + _most of the time_ by a deferred rearrangement of rows. + They become desynced when scrolling. This is to help screen + readers to read the contents from the DOM in a natural + order. See BodyRowContainerImpl.DeferredDomSorter for more + about that. + + */ + +/** + * A workaround-class for GWT and JSNI. + *

+ * GWT is unable to handle some method calls to Java methods in inner-classes + * from within JSNI blocks. Having that inner class extend a non-inner-class (or + * implement such an interface), makes it possible for JSNI to indirectly refer + * to the inner class, by invoking methods and fields in the non-inner-class + * API. + * + * @see Escalator.Scroller + */ +abstract class JsniWorkaround { + /** + * A JavaScript function that handles the scroll DOM event, and passes it on + * to Java code. + * + * @see #createScrollListenerFunction(Escalator) + * @see Escalator#onScroll() + * @see Escalator.Scroller#onScroll() + */ + protected final JavaScriptObject scrollListenerFunction; + + /** + * A JavaScript function that handles the mousewheel DOM event, and passes + * it on to Java code. + * + * @see #createMousewheelListenerFunction(Escalator) + * @see Escalator#onScroll() + * @see Escalator.Scroller#onScroll() + */ + protected final JavaScriptObject mousewheelListenerFunction; + + /** + * A JavaScript function that handles the touch start DOM event, and passes + * it on to Java code. + * + * @see TouchHandlerBundle#touchStart(Escalator.JsniUtil.TouchHandlerBundle.CustomTouchEvent) + */ + protected JavaScriptObject touchStartFunction; + + /** + * A JavaScript function that handles the touch move DOM event, and passes + * it on to Java code. + * + * @see TouchHandlerBundle#touchMove(Escalator.JsniUtil.TouchHandlerBundle.CustomTouchEvent) + */ + protected JavaScriptObject touchMoveFunction; + + /** + * A JavaScript function that handles the touch end and cancel DOM events, + * and passes them on to Java code. + * + * @see TouchHandlerBundle#touchEnd(Escalator.JsniUtil.TouchHandlerBundle.CustomTouchEvent) + */ + protected JavaScriptObject touchEndFunction; + + protected TouchHandlerBundle touchHandlerBundle; + + protected JsniWorkaround(final Escalator escalator) { + scrollListenerFunction = createScrollListenerFunction(escalator); + mousewheelListenerFunction = createMousewheelListenerFunction(escalator); + + touchHandlerBundle = new TouchHandlerBundle(escalator); + touchStartFunction = touchHandlerBundle.getTouchStartHandler(); + touchMoveFunction = touchHandlerBundle.getTouchMoveHandler(); + touchEndFunction = touchHandlerBundle.getTouchEndHandler(); + } + + /** + * A method that constructs the JavaScript function that will be stored into + * {@link #scrollListenerFunction}. + * + * @param esc + * a reference to the current instance of {@link Escalator} + * @see Escalator#onScroll() + */ + protected abstract JavaScriptObject createScrollListenerFunction( + Escalator esc); + + /** + * A method that constructs the JavaScript function that will be stored into + * {@link #mousewheelListenerFunction}. + * + * @param esc + * a reference to the current instance of {@link Escalator} + * @see Escalator#onScroll() + */ + protected abstract JavaScriptObject createMousewheelListenerFunction( + Escalator esc); +} + +/** + * A low-level table-like widget that features a scrolling virtual viewport and + * lazily generated rows. + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class Escalator extends Widget implements RequiresResize, + DeferredWorker, SubPartAware { + + // todo comments legend + /* + * [[optimize]]: There's an opportunity to rewrite the code in such a way + * that it _might_ perform better (rememeber to measure, implement, + * re-measure) + */ + /* + * [[mpixscroll]]: This code will require alterations that are relevant for + * supporting the scrolling through more pixels than some browsers normally + * would support. (i.e. when we support more than "a million" pixels in the + * escalator DOM). NOTE: these bits can most often also be identified by + * searching for code that call scrollElem.getScrollTop();. + */ + /* + * [[spacer]]: Code that is important to make spacers work. + */ + + /** + * A utility class that contains utility methods that are usually called + * from JSNI. + *

+ * The methods are moved in this class to minimize the amount of JSNI code + * as much as feasible. + */ + static class JsniUtil { + public static class TouchHandlerBundle { + + /** + * A JavaScriptObject overlay for the JavaScript + * TouchEvent object. + *

+ * This needs to be used in the touch event handlers, since GWT's + * {@link com.google.gwt.event.dom.client.TouchEvent TouchEvent} + * can't be cast from the JSNI call, and the + * {@link com.google.gwt.dom.client.NativeEvent NativeEvent} isn't + * properly populated with the correct values. + */ + private final static class CustomTouchEvent extends + JavaScriptObject { + protected CustomTouchEvent() { + } + + public native NativeEvent getNativeEvent() + /*-{ + return this; + }-*/; + + public native int getPageX() + /*-{ + return this.targetTouches[0].pageX; + }-*/; + + public native int getPageY() + /*-{ + return this.targetTouches[0].pageY; + }-*/; + } + + private final Escalator escalator; + + public TouchHandlerBundle(final Escalator escalator) { + this.escalator = escalator; + } + + public native JavaScriptObject getTouchStartHandler() + /*-{ + // we need to store "this", since it won't be preserved on call. + var self = this; + return $entry(function (e) { + self.@com.vaadin.client.widgets.Escalator.JsniUtil.TouchHandlerBundle::touchStart(*)(e); + }); + }-*/; + + public native JavaScriptObject getTouchMoveHandler() + /*-{ + // we need to store "this", since it won't be preserved on call. + var self = this; + return $entry(function (e) { + self.@com.vaadin.client.widgets.Escalator.JsniUtil.TouchHandlerBundle::touchMove(*)(e); + }); + }-*/; + + public native JavaScriptObject getTouchEndHandler() + /*-{ + // we need to store "this", since it won't be preserved on call. + var self = this; + return $entry(function (e) { + self.@com.vaadin.client.widgets.Escalator.JsniUtil.TouchHandlerBundle::touchEnd(*)(e); + }); + }-*/; + + // Duration of the inertial scrolling simulation. Devices with + // larger screens take longer durations. + private static final int DURATION = Window.getClientHeight(); + // multiply scroll velocity with repeated touching + private int acceleration = 1; + private boolean touching = false; + // Two movement objects for storing status and processing touches + private Movement yMov, xMov; + final double MIN_VEL = 0.6, MAX_VEL = 4, F_VEL = 1500, F_ACC = 0.7, + F_AXIS = 1; + + // The object to deal with one direction scrolling + private class Movement { + final List speeds = new ArrayList(); + final ScrollbarBundle scroll; + double position, offset, velocity, prevPos, prevTime, delta; + boolean run, vertical; + + public Movement(boolean vertical) { + this.vertical = vertical; + scroll = vertical ? escalator.verticalScrollbar + : escalator.horizontalScrollbar; + } + + public void startTouch(CustomTouchEvent event) { + speeds.clear(); + prevPos = pagePosition(event); + prevTime = Duration.currentTimeMillis(); + } + + public void moveTouch(CustomTouchEvent event) { + double pagePosition = pagePosition(event); + if (pagePosition > -1) { + delta = prevPos - pagePosition; + double now = Duration.currentTimeMillis(); + double ellapsed = now - prevTime; + velocity = delta / ellapsed; + // if last speed was so low, reset speeds and start + // storing again + if (speeds.size() > 0 && !validSpeed(speeds.get(0))) { + speeds.clear(); + run = true; + } + speeds.add(0, velocity); + prevTime = now; + prevPos = pagePosition; + } + } + + public void endTouch(CustomTouchEvent event) { + // Compute average speed + velocity = 0; + for (double s : speeds) { + velocity += s / speeds.size(); + } + position = scroll.getScrollPos(); + + // Compute offset, and adjust it with an easing curve so as + // movement is smoother. + offset = F_VEL * velocity * acceleration + * easingInOutCos(velocity, MAX_VEL); + + // Enable or disable inertia movement in this axis + run = validSpeed(velocity); + if (run) { + event.getNativeEvent().preventDefault(); + } + } + + void validate(Movement other) { + if (!run || other.velocity > 0 + && Math.abs(velocity / other.velocity) < F_AXIS) { + delta = offset = 0; + run = false; + } + } + + void stepAnimation(double progress) { + scroll.setScrollPos(position + offset * progress); + } + + int pagePosition(CustomTouchEvent event) { + JsArray a = event.getNativeEvent().getTouches(); + return vertical ? a.get(0).getPageY() : a.get(0).getPageX(); + } + + boolean validSpeed(double speed) { + return Math.abs(speed) > MIN_VEL; + } + } + + // Using GWT animations which take care of native animation frames. + private Animation animation = new Animation() { + @Override + public void onUpdate(double progress) { + xMov.stepAnimation(progress); + yMov.stepAnimation(progress); + } + + @Override + public double interpolate(double progress) { + return easingOutCirc(progress); + }; + + @Override + public void onComplete() { + touching = false; + escalator.body.domSorter.reschedule(); + }; + + @Override + public void run(int duration) { + if (xMov.run || yMov.run) { + super.run(duration); + } else { + onComplete(); + } + }; + }; + + public void touchStart(final CustomTouchEvent event) { + if (event.getNativeEvent().getTouches().length() == 1) { + if (yMov == null) { + yMov = new Movement(true); + xMov = new Movement(false); + } + if (animation.isRunning()) { + acceleration += F_ACC; + event.getNativeEvent().preventDefault(); + animation.cancel(); + } else { + acceleration = 1; + } + xMov.startTouch(event); + yMov.startTouch(event); + touching = true; + } else { + touching = false; + animation.cancel(); + acceleration = 1; + } + } + + public void touchMove(final CustomTouchEvent event) { + if (touching) { + xMov.moveTouch(event); + yMov.moveTouch(event); + xMov.validate(yMov); + yMov.validate(xMov); + event.getNativeEvent().preventDefault(); + moveScrollFromEvent(escalator, xMov.delta, yMov.delta, + event.getNativeEvent()); + } + } + + public void touchEnd(final CustomTouchEvent event) { + if (touching) { + xMov.endTouch(event); + yMov.endTouch(event); + xMov.validate(yMov); + yMov.validate(xMov); + // Adjust duration so as longer movements take more duration + boolean vert = !xMov.run || yMov.run + && Math.abs(yMov.offset) > Math.abs(xMov.offset); + double delta = Math.abs((vert ? yMov : xMov).offset); + animation.run((int) (3 * DURATION * easingOutExp(delta))); + } + } + + private double easingInOutCos(double val, double max) { + return 0.5 - 0.5 * Math.cos(Math.PI * Math.signum(val) + * Math.min(Math.abs(val), max) / max); + } + + private double easingOutExp(double delta) { + return (1 - Math.pow(2, -delta / 1000)); + } + + private double easingOutCirc(double progress) { + return Math.sqrt(1 - (progress - 1) * (progress - 1)); + } + } + + public static void moveScrollFromEvent(final Escalator escalator, + final double deltaX, final double deltaY, + final NativeEvent event) { + + if (!Double.isNaN(deltaX)) { + escalator.horizontalScrollbar.setScrollPosByDelta(deltaX); + } + + if (!Double.isNaN(deltaY)) { + escalator.verticalScrollbar.setScrollPosByDelta(deltaY); + } + + /* + * TODO: only prevent if not scrolled to end/bottom. Or no? UX team + * needs to decide. + */ + final boolean warrantedYScroll = deltaY != 0 + && escalator.verticalScrollbar.showsScrollHandle(); + final boolean warrantedXScroll = deltaX != 0 + && escalator.horizontalScrollbar.showsScrollHandle(); + if (warrantedYScroll || warrantedXScroll) { + event.preventDefault(); + } + } + } + + /** + * ScrollDestination case-specific handling logic. + */ + private static double getScrollPos(final ScrollDestination destination, + final double targetStartPx, final double targetEndPx, + final double viewportStartPx, final double viewportEndPx, + final double padding) { + + final double viewportLength = viewportEndPx - viewportStartPx; + + switch (destination) { + + /* + * Scroll as little as possible to show the target element. If the + * element fits into view, this works as START or END depending on the + * current scroll position. If the element does not fit into view, this + * works as START. + */ + case ANY: { + final double startScrollPos = targetStartPx - padding; + final double endScrollPos = targetEndPx + padding - viewportLength; + + if (startScrollPos < viewportStartPx) { + return startScrollPos; + } else if (targetEndPx + padding > viewportEndPx) { + return endScrollPos; + } else { + // NOOP, it's already visible + return viewportStartPx; + } + } + + /* + * Scrolls so that the element is shown at the end of the viewport. The + * viewport will, however, not scroll before its first element. + */ + case END: { + return targetEndPx + padding - viewportLength; + } + + /* + * Scrolls so that the element is shown in the middle of the viewport. + * The viewport will, however, not scroll beyond its contents, given + * more elements than what the viewport is able to show at once. Under + * no circumstances will the viewport scroll before its first element. + */ + case MIDDLE: { + final double targetMiddle = targetStartPx + + (targetEndPx - targetStartPx) / 2; + return targetMiddle - viewportLength / 2; + } + + /* + * Scrolls so that the element is shown at the start of the viewport. + * The viewport will, however, not scroll beyond its contents. + */ + case START: { + return targetStartPx - padding; + } + + /* + * Throw an error if we're here. This can only mean that + * ScrollDestination has been carelessly amended.. + */ + default: { + throw new IllegalArgumentException( + "Internal: ScrollDestination has been modified, " + + "but Escalator.getScrollPos has not been updated " + + "to match new values."); + } + } + + } + + /** An inner class that handles all logic related to scrolling. */ + private class Scroller extends JsniWorkaround { + private double lastScrollTop = 0; + private double lastScrollLeft = 0; + + public Scroller() { + super(Escalator.this); + } + + @Override + protected native JavaScriptObject createScrollListenerFunction( + Escalator esc) + /*-{ + var vScroll = esc.@com.vaadin.client.widgets.Escalator::verticalScrollbar; + var vScrollElem = vScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::getElement()(); + + var hScroll = esc.@com.vaadin.client.widgets.Escalator::horizontalScrollbar; + var hScrollElem = hScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::getElement()(); + + return $entry(function(e) { + var target = e.target || e.srcElement; // IE8 uses e.scrElement + + // in case the scroll event was native (i.e. scrollbars were dragged, or + // the scrollTop/Left was manually modified), the bundles have old cache + // values. We need to make sure that the caches are kept up to date. + if (target === vScrollElem) { + vScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::updateScrollPosFromDom()(); + } else if (target === hScrollElem) { + hScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::updateScrollPosFromDom()(); + } else { + $wnd.console.error("unexpected scroll target: "+target); + } + }); + }-*/; + + @Override + protected native JavaScriptObject createMousewheelListenerFunction( + Escalator esc) + /*-{ + return $entry(function(e) { + var deltaX = e.deltaX ? e.deltaX : -0.5*e.wheelDeltaX; + var deltaY = e.deltaY ? e.deltaY : -0.5*e.wheelDeltaY; + + // Delta mode 0 is in pixels; we don't need to do anything... + + // A delta mode of 1 means we're scrolling by lines instead of pixels + // We need to scale the number of lines by the default line height + if(e.deltaMode === 1) { + var brc = esc.@com.vaadin.client.widgets.Escalator::body; + deltaY *= brc.@com.vaadin.client.widgets.Escalator.AbstractRowContainer::getDefaultRowHeight()(); + } + + // Other delta modes aren't supported + if((e.deltaMode !== undefined) && (e.deltaMode >= 2 || e.deltaMode < 0)) { + var msg = "Unsupported wheel delta mode \"" + e.deltaMode + "\""; + + // Print warning message + esc.@com.vaadin.client.widgets.Escalator::logWarning(*)(msg); + } + + // IE8 has only delta y + if (isNaN(deltaY)) { + deltaY = -0.5*e.wheelDelta; + } + + @com.vaadin.client.widgets.Escalator.JsniUtil::moveScrollFromEvent(*)(esc, deltaX, deltaY, e); + }); + }-*/; + + /** + * Recalculates the virtual viewport represented by the scrollbars, so + * that the sizes of the scroll handles appear correct in the browser + */ + public void recalculateScrollbarsForVirtualViewport() { + double scrollContentHeight = body.calculateTotalRowHeight() + + body.spacerContainer.getSpacerHeightsSum(); + double scrollContentWidth = columnConfiguration.calculateRowWidth(); + double tableWrapperHeight = heightOfEscalator; + double tableWrapperWidth = widthOfEscalator; + + boolean verticalScrollNeeded = scrollContentHeight > tableWrapperHeight + + WidgetUtil.PIXEL_EPSILON + - header.getHeightOfSection() + - footer.getHeightOfSection(); + boolean horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth + + WidgetUtil.PIXEL_EPSILON; + + // One dimension got scrollbars, but not the other. Recheck time! + if (verticalScrollNeeded != horizontalScrollNeeded) { + if (!verticalScrollNeeded && horizontalScrollNeeded) { + verticalScrollNeeded = scrollContentHeight > tableWrapperHeight + + WidgetUtil.PIXEL_EPSILON + - header.getHeightOfSection() + - footer.getHeightOfSection() + - horizontalScrollbar.getScrollbarThickness(); + } else { + horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth + + WidgetUtil.PIXEL_EPSILON + - verticalScrollbar.getScrollbarThickness(); + } + } + + // let's fix the table wrapper size, since it's now stable. + if (verticalScrollNeeded) { + tableWrapperWidth -= verticalScrollbar.getScrollbarThickness(); + tableWrapperWidth = Math.max(0, tableWrapperWidth); + } + if (horizontalScrollNeeded) { + tableWrapperHeight -= horizontalScrollbar + .getScrollbarThickness(); + tableWrapperHeight = Math.max(0, tableWrapperHeight); + } + tableWrapper.getStyle().setHeight(tableWrapperHeight, Unit.PX); + tableWrapper.getStyle().setWidth(tableWrapperWidth, Unit.PX); + + double footerHeight = footer.getHeightOfSection(); + double headerHeight = header.getHeightOfSection(); + double vScrollbarHeight = Math.max(0, tableWrapperHeight + - footerHeight - headerHeight); + verticalScrollbar.setOffsetSize(vScrollbarHeight); + verticalScrollbar.setScrollSize(scrollContentHeight); + + /* + * If decreasing the amount of frozen columns, and scrolled to the + * right, the scroll position might reset. So we need to remember + * the scroll position, and re-apply it once the scrollbar size has + * been adjusted. + */ + double prevScrollPos = horizontalScrollbar.getScrollPos(); + + double unfrozenPixels = columnConfiguration + .getCalculatedColumnsWidth(Range.between( + columnConfiguration.getFrozenColumnCount(), + columnConfiguration.getColumnCount())); + double frozenPixels = scrollContentWidth - unfrozenPixels; + double hScrollOffsetWidth = tableWrapperWidth - frozenPixels; + horizontalScrollbar.setOffsetSize(hScrollOffsetWidth); + horizontalScrollbar.setScrollSize(unfrozenPixels); + horizontalScrollbar.getElement().getStyle() + .setLeft(frozenPixels, Unit.PX); + horizontalScrollbar.setScrollPos(prevScrollPos); + + /* + * only show the scrollbar wrapper if the scrollbar itself is + * visible. + */ + if (horizontalScrollbar.showsScrollHandle()) { + horizontalScrollbarDeco.getStyle().clearDisplay(); + } else { + horizontalScrollbarDeco.getStyle().setDisplay(Display.NONE); + } + + /* + * only show corner background divs if the vertical scrollbar is + * visible. + */ + Style hCornerStyle = headerDeco.getStyle(); + Style fCornerStyle = footerDeco.getStyle(); + if (verticalScrollbar.showsScrollHandle()) { + hCornerStyle.clearDisplay(); + fCornerStyle.clearDisplay(); + + if (horizontalScrollbar.showsScrollHandle()) { + double offset = horizontalScrollbar.getScrollbarThickness(); + fCornerStyle.setBottom(offset, Unit.PX); + } else { + fCornerStyle.clearBottom(); + } + } else { + hCornerStyle.setDisplay(Display.NONE); + fCornerStyle.setDisplay(Display.NONE); + } + } + + /** + * Logical scrolling event handler for the entire widget. + */ + public void onScroll() { + + final double scrollTop = verticalScrollbar.getScrollPos(); + final double scrollLeft = horizontalScrollbar.getScrollPos(); + if (lastScrollLeft != scrollLeft) { + for (int i = 0; i < columnConfiguration.frozenColumns; i++) { + header.updateFreezePosition(i, scrollLeft); + body.updateFreezePosition(i, scrollLeft); + footer.updateFreezePosition(i, scrollLeft); + } + + position.set(headElem, -scrollLeft, 0); + + /* + * TODO [[optimize]]: cache this value in case the instanceof + * check has undesirable overhead. This could also be a + * candidate for some deferred binding magic so that e.g. + * AbsolutePosition is not even considered in permutations that + * we know support something better. That would let the compiler + * completely remove the entire condition since it knows that + * the if will never be true. + */ + if (position instanceof AbsolutePosition) { + /* + * we don't want to put "top: 0" on the footer, since it'll + * render wrong, as we already have + * "bottom: $footer-height". + */ + footElem.getStyle().setLeft(-scrollLeft, Unit.PX); + } else { + position.set(footElem, -scrollLeft, 0); + } + + lastScrollLeft = scrollLeft; + } + + body.setBodyScrollPosition(scrollLeft, scrollTop); + + lastScrollTop = scrollTop; + body.updateEscalatorRowsOnScroll(); + body.spacerContainer.updateSpacerDecosVisibility(); + /* + * TODO [[optimize]]: Might avoid a reflow by first calculating new + * scrolltop and scrolleft, then doing the escalator magic based on + * those numbers and only updating the positions after that. + */ + } + + public native void attachScrollListener(Element element) + /* + * Attaching events with JSNI instead of the GWT event mechanism because + * GWT didn't provide enough details in events, or triggering the event + * handlers with GWT bindings was unsuccessful. Maybe, with more time + * and skill, it could be done with better success. JavaScript overlay + * types might work. This might also get rid of the JsniWorkaround + * class. + */ + /*-{ + if (element.addEventListener) { + element.addEventListener("scroll", this.@com.vaadin.client.widgets.JsniWorkaround::scrollListenerFunction); + } else { + element.attachEvent("onscroll", this.@com.vaadin.client.widgets.JsniWorkaround::scrollListenerFunction); + } + }-*/; + + public native void detachScrollListener(Element element) + /* + * Attaching events with JSNI instead of the GWT event mechanism because + * GWT didn't provide enough details in events, or triggering the event + * handlers with GWT bindings was unsuccessful. Maybe, with more time + * and skill, it could be done with better success. JavaScript overlay + * types might work. This might also get rid of the JsniWorkaround + * class. + */ + /*-{ + if (element.addEventListener) { + element.removeEventListener("scroll", this.@com.vaadin.client.widgets.JsniWorkaround::scrollListenerFunction); + } else { + element.detachEvent("onscroll", this.@com.vaadin.client.widgets.JsniWorkaround::scrollListenerFunction); + } + }-*/; + + public native void attachMousewheelListener(Element element) + /* + * Attaching events with JSNI instead of the GWT event mechanism because + * GWT didn't provide enough details in events, or triggering the event + * handlers with GWT bindings was unsuccessful. Maybe, with more time + * and skill, it could be done with better success. JavaScript overlay + * types might work. This might also get rid of the JsniWorkaround + * class. + */ + /*-{ + if (element.addEventListener) { + // firefox likes "wheel", while others use "mousewheel" + var eventName = 'onmousewheel' in element ? 'mousewheel' : 'wheel'; + element.addEventListener(eventName, this.@com.vaadin.client.widgets.JsniWorkaround::mousewheelListenerFunction); + } else { + // IE8 + element.attachEvent("onmousewheel", this.@com.vaadin.client.widgets.JsniWorkaround::mousewheelListenerFunction); + } + }-*/; + + public native void detachMousewheelListener(Element element) + /* + * Detaching events with JSNI instead of the GWT event mechanism because + * GWT didn't provide enough details in events, or triggering the event + * handlers with GWT bindings was unsuccessful. Maybe, with more time + * and skill, it could be done with better success. JavaScript overlay + * types might work. This might also get rid of the JsniWorkaround + * class. + */ + /*-{ + if (element.addEventListener) { + // firefox likes "wheel", while others use "mousewheel" + var eventName = element.onwheel===undefined?"mousewheel":"wheel"; + element.removeEventListener(eventName, this.@com.vaadin.client.widgets.JsniWorkaround::mousewheelListenerFunction); + } else { + // IE8 + element.detachEvent("onmousewheel", this.@com.vaadin.client.widgets.JsniWorkaround::mousewheelListenerFunction); + } + }-*/; + + public native void attachTouchListeners(Element element) + /* + * Detaching events with JSNI instead of the GWT event mechanism because + * GWT didn't provide enough details in events, or triggering the event + * handlers with GWT bindings was unsuccessful. Maybe, with more time + * and skill, it could be done with better success. JavaScript overlay + * types might work. This might also get rid of the JsniWorkaround + * class. + */ + /*-{ + if (element.addEventListener) { + element.addEventListener("touchstart", this.@com.vaadin.client.widgets.JsniWorkaround::touchStartFunction); + element.addEventListener("touchmove", this.@com.vaadin.client.widgets.JsniWorkaround::touchMoveFunction); + element.addEventListener("touchend", this.@com.vaadin.client.widgets.JsniWorkaround::touchEndFunction); + element.addEventListener("touchcancel", this.@com.vaadin.client.widgets.JsniWorkaround::touchEndFunction); + } else { + // this would be IE8, but we don't support it with touch + } + }-*/; + + public native void detachTouchListeners(Element element) + /* + * Detaching events with JSNI instead of the GWT event mechanism because + * GWT didn't provide enough details in events, or triggering the event + * handlers with GWT bindings was unsuccessful. Maybe, with more time + * and skill, it could be done with better success. JavaScript overlay + * types might work. This might also get rid of the JsniWorkaround + * class. + */ + /*-{ + if (element.removeEventListener) { + element.removeEventListener("touchstart", this.@com.vaadin.client.widgets.JsniWorkaround::touchStartFunction); + element.removeEventListener("touchmove", this.@com.vaadin.client.widgets.JsniWorkaround::touchMoveFunction); + element.removeEventListener("touchend", this.@com.vaadin.client.widgets.JsniWorkaround::touchEndFunction); + element.removeEventListener("touchcancel", this.@com.vaadin.client.widgets.JsniWorkaround::touchEndFunction); + } else { + // this would be IE8, but we don't support it with touch + } + }-*/; + + public void scrollToColumn(final int columnIndex, + final ScrollDestination destination, final int padding) { + assert columnIndex >= columnConfiguration.frozenColumns : "Can't scroll to a frozen column"; + + /* + * To cope with frozen columns, we just pretend those columns are + * not there at all when calculating the position of the target + * column and the boundaries of the viewport. The resulting + * scrollLeft will be correct without compensation since the DOM + * structure effectively means that scrollLeft also ignores the + * frozen columns. + */ + final double frozenPixels = columnConfiguration + .getCalculatedColumnsWidth(Range.withLength(0, + columnConfiguration.frozenColumns)); + + final double targetStartPx = columnConfiguration + .getCalculatedColumnsWidth(Range.withLength(0, columnIndex)) + - frozenPixels; + final double targetEndPx = targetStartPx + + columnConfiguration.getColumnWidthActual(columnIndex); + + final double viewportStartPx = getScrollLeft(); + double viewportEndPx = viewportStartPx + + WidgetUtil + .getRequiredWidthBoundingClientRectDouble(getElement()) + - frozenPixels; + if (verticalScrollbar.showsScrollHandle()) { + viewportEndPx -= WidgetUtil.getNativeScrollbarSize(); + } + + final double scrollLeft = getScrollPos(destination, targetStartPx, + targetEndPx, viewportStartPx, viewportEndPx, padding); + + /* + * note that it doesn't matter if the scroll would go beyond the + * content, since the browser will adjust for that, and everything + * fall into line accordingly. + */ + setScrollLeft(scrollLeft); + } + + public void scrollToRow(final int rowIndex, + final ScrollDestination destination, final double padding) { + + final double targetStartPx = (body.getDefaultRowHeight() * rowIndex) + + body.spacerContainer + .getSpacerHeightsSumUntilIndex(rowIndex); + final double targetEndPx = targetStartPx + + body.getDefaultRowHeight(); + + final double viewportStartPx = getScrollTop(); + final double viewportEndPx = viewportStartPx + + body.getHeightOfSection(); + + final double scrollTop = getScrollPos(destination, targetStartPx, + targetEndPx, viewportStartPx, viewportEndPx, padding); + + /* + * note that it doesn't matter if the scroll would go beyond the + * content, since the browser will adjust for that, and everything + * falls into line accordingly. + */ + setScrollTop(scrollTop); + } + } + + protected abstract class AbstractRowContainer implements RowContainer { + private EscalatorUpdater updater = EscalatorUpdater.NULL; + + private int rows; + + /** + * The table section element ({@code }, {@code } or + * {@code }) the rows (i.e. {@code } tags) are contained in. + */ + protected final TableSectionElement root; + + /** + * The primary style name of the escalator. Most commonly provided by + * Escalator as "v-escalator". + */ + private String primaryStyleName = null; + + private boolean defaultRowHeightShouldBeAutodetected = true; + + private double defaultRowHeight = INITIAL_DEFAULT_ROW_HEIGHT; + + public AbstractRowContainer( + final TableSectionElement rowContainerElement) { + root = rowContainerElement; + } + + @Override + public TableSectionElement getElement() { + return root; + } + + /** + * Gets the tag name of an element to represent a cell in a row. + *

+ * Usually {@code "th"} or {@code "td"}. + *

+ * Note: To actually create such an element, use + * {@link #createCellElement(int, int)} instead. + * + * @return the tag name for the element to represent cells as + * @see #createCellElement(int, int) + */ + protected abstract String getCellElementTagName(); + + @Override + public EscalatorUpdater getEscalatorUpdater() { + return updater; + } + + /** + * {@inheritDoc} + *

+ * Implementation detail: This method does no DOM modifications + * (i.e. is very cheap to call) if there is no data for rows or columns + * when this method is called. + * + * @see #hasColumnAndRowData() + */ + @Override + public void setEscalatorUpdater(final EscalatorUpdater escalatorUpdater) { + if (escalatorUpdater == null) { + throw new IllegalArgumentException( + "escalator updater cannot be null"); + } + + updater = escalatorUpdater; + + if (hasColumnAndRowData() && getRowCount() > 0) { + refreshRows(0, getRowCount()); + } + } + + /** + * {@inheritDoc} + *

+ * Implementation detail: This method does no DOM modifications + * (i.e. is very cheap to call) if there are no rows in the DOM when + * this method is called. + * + * @see #hasSomethingInDom() + */ + @Override + public void removeRows(final int index, final int numberOfRows) { + assertArgumentsAreValidAndWithinRange(index, numberOfRows); + + rows -= numberOfRows; + + if (!isAttached()) { + return; + } + + if (hasSomethingInDom()) { + paintRemoveRows(index, numberOfRows); + } + } + + /** + * Removes those row elements from the DOM that correspond to the given + * range of logical indices. This may be fewer than {@code numberOfRows} + * , even zero, if not all the removed rows are actually visible. + *

+ * The implementation must call {@link #paintRemoveRow(Element, int)} + * for each row that is removed from the DOM. + * + * @param index + * the logical index of the first removed row + * @param numberOfRows + * number of logical rows to remove + */ + protected abstract void paintRemoveRows(final int index, + final int numberOfRows); + + /** + * Removes a row element from the DOM, invoking + * {@link #getEscalatorUpdater()} + * {@link EscalatorUpdater#preDetach(Row, Iterable) preDetach} and + * {@link EscalatorUpdater#postDetach(Row, Iterable) postDetach} before + * and after removing the row, respectively. + *

+ * This method must be called for each removed DOM row by any + * {@link #paintRemoveRows(int, int)} implementation. + * + * @param tr + * the row element to remove. + */ + protected void paintRemoveRow(final TableRowElement tr, + final int logicalRowIndex) { + + flyweightRow.setup(tr, logicalRowIndex, + columnConfiguration.getCalculatedColumnWidths()); + + getEscalatorUpdater().preDetach(flyweightRow, + flyweightRow.getCells()); + + tr.removeFromParent(); + + getEscalatorUpdater().postDetach(flyweightRow, + flyweightRow.getCells()); + + /* + * the "assert" guarantees that this code is run only during + * development/debugging. + */ + assert flyweightRow.teardown(); + + } + + protected void assertArgumentsAreValidAndWithinRange(final int index, + final int numberOfRows) throws IllegalArgumentException, + IndexOutOfBoundsException { + if (numberOfRows < 1) { + throw new IllegalArgumentException( + "Number of rows must be 1 or greater (was " + + numberOfRows + ")"); + } + + if (index < 0 || index + numberOfRows > getRowCount()) { + throw new IndexOutOfBoundsException("The given " + + "row range (" + index + ".." + (index + numberOfRows) + + ") was outside of the current number of rows (" + + getRowCount() + ")"); + } + } + + @Override + public int getRowCount() { + return rows; + } + + /** + * This method calculates the current row count directly from the DOM. + *

+ * While Escalator is stable, this value should equal to + * {@link #getRowCount()}, but while row counts are being updated, these + * two values might differ for a short while. + *

+ * Any extra content, such as spacers for the body, should not be + * included in this count. + * + * @since 7.5.0 + * + * @return the actual DOM count of rows + */ + public abstract int getDomRowCount(); + + /** + * {@inheritDoc} + *

+ * Implementation detail: This method does no DOM modifications + * (i.e. is very cheap to call) if there is no data for columns when + * this method is called. + * + * @see #hasColumnAndRowData() + */ + @Override + public void insertRows(final int index, final int numberOfRows) { + if (index < 0 || index > getRowCount()) { + throw new IndexOutOfBoundsException("The given index (" + index + + ") was outside of the current number of rows (0.." + + getRowCount() + ")"); + } + + if (numberOfRows < 1) { + throw new IllegalArgumentException( + "Number of rows must be 1 or greater (was " + + numberOfRows + ")"); + } + + rows += numberOfRows; + + /* + * only add items in the DOM if the widget itself is attached to the + * DOM. We can't calculate sizes otherwise. + */ + if (isAttached()) { + paintInsertRows(index, numberOfRows); + + if (rows == numberOfRows) { + /* + * We are inserting the first rows in this container. We + * potentially need to set the widths for the cells for the + * first time. + */ + Map colWidths = new HashMap(); + for (int i = 0; i < getColumnConfiguration() + .getColumnCount(); i++) { + Double width = Double.valueOf(getColumnConfiguration() + .getColumnWidth(i)); + Integer col = Integer.valueOf(i); + colWidths.put(col, width); + } + getColumnConfiguration().setColumnWidths(colWidths); + } + } + } + + /** + * Actually add rows into the DOM, now that everything can be + * calculated. + * + * @param visualIndex + * the DOM index to add rows into + * @param numberOfRows + * the number of rows to insert + * @return a list of the added row elements + */ + protected abstract void paintInsertRows(final int visualIndex, + final int numberOfRows); + + protected List paintInsertStaticRows( + final int visualIndex, final int numberOfRows) { + assert isAttached() : "Can't paint rows if Escalator is not attached"; + + final List addedRows = new ArrayList(); + + if (numberOfRows < 1) { + return addedRows; + } + + Node referenceRow; + if (root.getChildCount() != 0 && visualIndex != 0) { + // get the row node we're inserting stuff after + referenceRow = root.getChild(visualIndex - 1); + } else { + // index is 0, so just prepend. + referenceRow = null; + } + + for (int row = visualIndex; row < visualIndex + numberOfRows; row++) { + final TableRowElement tr = TableRowElement.as(DOM.createTR()); + addedRows.add(tr); + tr.addClassName(getStylePrimaryName() + "-row"); + + for (int col = 0; col < columnConfiguration.getColumnCount(); col++) { + final double colWidth = columnConfiguration + .getColumnWidthActual(col); + final TableCellElement cellElem = createCellElement(colWidth); + tr.appendChild(cellElem); + + // Set stylename and position if new cell is frozen + if (col < columnConfiguration.frozenColumns) { + cellElem.addClassName("frozen"); + position.set(cellElem, scroller.lastScrollLeft, 0); + } + if (columnConfiguration.frozenColumns > 0 + && col == columnConfiguration.frozenColumns - 1) { + cellElem.addClassName("last-frozen"); + } + } + + referenceRow = paintInsertRow(referenceRow, tr, row); + } + reapplyRowWidths(); + + recalculateSectionHeight(); + + return addedRows; + } + + /** + * Inserts a single row into the DOM, invoking + * {@link #getEscalatorUpdater()} + * {@link EscalatorUpdater#preAttach(Row, Iterable) preAttach} and + * {@link EscalatorUpdater#postAttach(Row, Iterable) postAttach} before + * and after inserting the row, respectively. The row should have its + * cells already inserted. + * + * @param referenceRow + * the row after which to insert or null if insert as first + * @param tr + * the row to be inserted + * @param logicalRowIndex + * the logical index of the inserted row + * @return the inserted row to be used as the new reference + */ + protected Node paintInsertRow(Node referenceRow, + final TableRowElement tr, int logicalRowIndex) { + flyweightRow.setup(tr, logicalRowIndex, + columnConfiguration.getCalculatedColumnWidths()); + + getEscalatorUpdater().preAttach(flyweightRow, + flyweightRow.getCells()); + + referenceRow = insertAfterReferenceAndUpdateIt(root, tr, + referenceRow); + + getEscalatorUpdater().postAttach(flyweightRow, + flyweightRow.getCells()); + updater.update(flyweightRow, flyweightRow.getCells()); + + /* + * the "assert" guarantees that this code is run only during + * development/debugging. + */ + assert flyweightRow.teardown(); + return referenceRow; + } + + private Node insertAfterReferenceAndUpdateIt(final Element parent, + final Element elem, final Node referenceNode) { + if (referenceNode != null) { + parent.insertAfter(elem, referenceNode); + } else { + /* + * referencenode being null means we have offset 0, i.e. make it + * the first row + */ + /* + * TODO [[optimize]]: Is insertFirst or append faster for an + * empty root? + */ + parent.insertFirst(elem); + } + return elem; + } + + abstract protected void recalculateSectionHeight(); + + /** + * Returns the height of all rows in the row container. + */ + protected double calculateTotalRowHeight() { + return getDefaultRowHeight() * getRowCount(); + } + + /** + * {@inheritDoc} + *

+ * Implementation detail: This method does no DOM modifications + * (i.e. is very cheap to call) if there is no data for columns when + * this method is called. + * + * @see #hasColumnAndRowData() + */ + @Override + // overridden because of JavaDoc + public void refreshRows(final int index, final int numberOfRows) { + Range rowRange = Range.withLength(index, numberOfRows); + Range colRange = Range.withLength(0, getColumnConfiguration() + .getColumnCount()); + refreshCells(rowRange, colRange); + } + + protected abstract void refreshCells(Range logicalRowRange, + Range colRange); + + void refreshRow(TableRowElement tr, int logicalRowIndex) { + refreshRow(tr, logicalRowIndex, Range.withLength(0, + getColumnConfiguration().getColumnCount())); + } + + void refreshRow(final TableRowElement tr, final int logicalRowIndex, + Range colRange) { + flyweightRow.setup(tr, logicalRowIndex, + columnConfiguration.getCalculatedColumnWidths()); + Iterable cellsToUpdate = flyweightRow.getCells( + colRange.getStart(), colRange.length()); + updater.update(flyweightRow, cellsToUpdate); + + /* + * the "assert" guarantees that this code is run only during + * development/debugging. + */ + assert flyweightRow.teardown(); + } + + /** + * Create and setup an empty cell element. + * + * @param width + * the width of the cell, in pixels + * + * @return a set-up empty cell element + */ + public TableCellElement createCellElement(final double width) { + final TableCellElement cellElem = TableCellElement.as(DOM + .createElement(getCellElementTagName())); + + final double height = getDefaultRowHeight(); + assert height >= 0 : "defaultRowHeight was negative. There's a setter leak somewhere."; + cellElem.getStyle().setHeight(height, Unit.PX); + + if (width >= 0) { + cellElem.getStyle().setWidth(width, Unit.PX); + } + cellElem.addClassName(getStylePrimaryName() + "-cell"); + return cellElem; + } + + @Override + public TableRowElement getRowElement(int index) { + return getTrByVisualIndex(index); + } + + /** + * Gets the child element that is visually at a certain index + * + * @param index + * the index of the element to retrieve + * @return the element at position {@code index} + * @throws IndexOutOfBoundsException + * if {@code index} is not valid within {@link #root} + */ + protected abstract TableRowElement getTrByVisualIndex(int index) + throws IndexOutOfBoundsException; + + protected void paintRemoveColumns(final int offset, + final int numberOfColumns) { + for (int i = 0; i < getDomRowCount(); i++) { + TableRowElement row = getTrByVisualIndex(i); + flyweightRow.setup(row, i, + columnConfiguration.getCalculatedColumnWidths()); + + Iterable attachedCells = flyweightRow.getCells( + offset, numberOfColumns); + getEscalatorUpdater().preDetach(flyweightRow, attachedCells); + + for (int j = 0; j < numberOfColumns; j++) { + row.getCells().getItem(offset).removeFromParent(); + } + + Iterable detachedCells = flyweightRow + .getUnattachedCells(offset, numberOfColumns); + getEscalatorUpdater().postDetach(flyweightRow, detachedCells); + + assert flyweightRow.teardown(); + } + } + + protected void paintInsertColumns(final int offset, + final int numberOfColumns, boolean frozen) { + + for (int row = 0; row < getDomRowCount(); row++) { + final TableRowElement tr = getTrByVisualIndex(row); + int logicalRowIndex = getLogicalRowIndex(tr); + paintInsertCells(tr, logicalRowIndex, offset, numberOfColumns); + } + reapplyRowWidths(); + + if (frozen) { + for (int col = offset; col < offset + numberOfColumns; col++) { + setColumnFrozen(col, true); + } + } + } + + /** + * Inserts new cell elements into a single row element, invoking + * {@link #getEscalatorUpdater()} + * {@link EscalatorUpdater#preAttach(Row, Iterable) preAttach} and + * {@link EscalatorUpdater#postAttach(Row, Iterable) postAttach} before + * and after inserting the cells, respectively. + *

+ * Precondition: The row must be already attached to the DOM and the + * FlyweightCell instances corresponding to the new columns added to + * {@code flyweightRow}. + * + * @param tr + * the row in which to insert the cells + * @param logicalRowIndex + * the index of the row + * @param offset + * the index of the first cell + * @param numberOfCells + * the number of cells to insert + */ + private void paintInsertCells(final TableRowElement tr, + int logicalRowIndex, final int offset, final int numberOfCells) { + + assert root.isOrHasChild(tr) : "The row must be attached to the document"; + + flyweightRow.setup(tr, logicalRowIndex, + columnConfiguration.getCalculatedColumnWidths()); + + Iterable cells = flyweightRow.getUnattachedCells( + offset, numberOfCells); + + for (FlyweightCell cell : cells) { + final double colWidth = columnConfiguration + .getColumnWidthActual(cell.getColumn()); + final TableCellElement cellElem = createCellElement(colWidth); + cell.setElement(cellElem); + } + + getEscalatorUpdater().preAttach(flyweightRow, cells); + + Node referenceCell; + if (offset != 0) { + referenceCell = tr.getChild(offset - 1); + } else { + referenceCell = null; + } + + for (FlyweightCell cell : cells) { + referenceCell = insertAfterReferenceAndUpdateIt(tr, + cell.getElement(), referenceCell); + } + + getEscalatorUpdater().postAttach(flyweightRow, cells); + getEscalatorUpdater().update(flyweightRow, cells); + + assert flyweightRow.teardown(); + } + + public void setColumnFrozen(int column, boolean frozen) { + toggleFrozenColumnClass(column, frozen, "frozen"); + + if (frozen) { + updateFreezePosition(column, scroller.lastScrollLeft); + } + } + + private void toggleFrozenColumnClass(int column, boolean frozen, + String className) { + final NodeList childRows = root.getRows(); + + for (int row = 0; row < childRows.getLength(); row++) { + final TableRowElement tr = childRows.getItem(row); + if (!rowCanBeFrozen(tr)) { + continue; + } + + TableCellElement cell = tr.getCells().getItem(column); + if (frozen) { + cell.addClassName(className); + } else { + cell.removeClassName(className); + position.reset(cell); + } + } + } + + public void setColumnLastFrozen(int column, boolean lastFrozen) { + toggleFrozenColumnClass(column, lastFrozen, "last-frozen"); + } + + public void updateFreezePosition(int column, double scrollLeft) { + final NodeList childRows = root.getRows(); + + for (int row = 0; row < childRows.getLength(); row++) { + final TableRowElement tr = childRows.getItem(row); + + if (rowCanBeFrozen(tr)) { + TableCellElement cell = tr.getCells().getItem(column); + position.set(cell, scrollLeft, 0); + } + } + } + + /** + * Checks whether a row is an element, or contains such elements, that + * can be frozen. + *

+ * In practice, this applies for all header and footer rows. For body + * rows, it applies for all rows except spacer rows. + * + * @since 7.5.0 + * + * @param tr + * the row element to check for if it is or has elements that + * can be frozen + * @return true iff this the given element, or any of its + * descendants, can be frozen + */ + abstract protected boolean rowCanBeFrozen(TableRowElement tr); + + /** + * Iterates through all the cells in a column and returns the width of + * the widest element in this RowContainer. + * + * @param index + * the index of the column to inspect + * @return the pixel width of the widest element in the indicated column + */ + public double calculateMaxColWidth(int index) { + TableRowElement row = TableRowElement.as(root + .getFirstChildElement()); + double maxWidth = 0; + while (row != null) { + final TableCellElement cell = row.getCells().getItem(index); + final boolean isVisible = !cell.getStyle().getDisplay() + .equals(Display.NONE.getCssName()); + if (isVisible) { + maxWidth = Math.max(maxWidth, WidgetUtil + .getRequiredWidthBoundingClientRectDouble(cell)); + } + row = TableRowElement.as(row.getNextSiblingElement()); + } + return maxWidth; + } + + /** + * Reapplies all the cells' widths according to the calculated widths in + * the column configuration. + */ + public void reapplyColumnWidths() { + Element row = root.getFirstChildElement(); + while (row != null) { + // Only handle non-spacer rows + if (!body.spacerContainer.isSpacer(row)) { + Element cell = row.getFirstChildElement(); + int columnIndex = 0; + while (cell != null) { + final double width = getCalculatedColumnWidthWithColspan( + cell, columnIndex); + + /* + * TODO Should Escalator implement ProvidesResize at + * some point, this is where we need to do that. + */ + cell.getStyle().setWidth(width, Unit.PX); + + cell = cell.getNextSiblingElement(); + columnIndex++; + } + } + row = row.getNextSiblingElement(); + } + + reapplyRowWidths(); + } + + private double getCalculatedColumnWidthWithColspan(final Element cell, + final int columnIndex) { + final int colspan = cell.getPropertyInt(FlyweightCell.COLSPAN_ATTR); + Range spannedColumns = Range.withLength(columnIndex, colspan); + + /* + * Since browsers don't explode with overflowing colspans, escalator + * shouldn't either. + */ + if (spannedColumns.getEnd() > columnConfiguration.getColumnCount()) { + spannedColumns = Range.between(columnIndex, + columnConfiguration.getColumnCount()); + } + return columnConfiguration + .getCalculatedColumnsWidth(spannedColumns); + } + + /** + * Applies the total length of the columns to each row element. + *

+ * Note: In contrast to {@link #reapplyColumnWidths()}, this + * method only modifies the width of the {@code } element, not the + * cells within. + */ + protected void reapplyRowWidths() { + double rowWidth = columnConfiguration.calculateRowWidth(); + if (rowWidth < 0) { + return; + } + + Element row = root.getFirstChildElement(); + while (row != null) { + // IF there is a rounding error when summing the columns, we + // need to round the tr width up to ensure that columns fit and + // do not wrap + // E.g.122.95+123.25+103.75+209.25+83.52+88.57+263.45+131.21+126.85+113.13=1365.9299999999998 + // For this we must set 1365.93 or the last column will wrap + row.getStyle().setWidth(WidgetUtil.roundSizeUp(rowWidth), + Unit.PX); + row = row.getNextSiblingElement(); + } + } + + /** + * The primary style name for the container. + * + * @param primaryStyleName + * the style name to use as prefix for all row and cell style + * names. + */ + protected void setStylePrimaryName(String primaryStyleName) { + String oldStyle = getStylePrimaryName(); + if (SharedUtil.equals(oldStyle, primaryStyleName)) { + return; + } + + this.primaryStyleName = primaryStyleName; + + // Update already rendered rows and cells + Element row = root.getRows().getItem(0); + while (row != null) { + UIObject.setStylePrimaryName(row, primaryStyleName + "-row"); + Element cell = TableRowElement.as(row).getCells().getItem(0); + while (cell != null) { + assert TableCellElement.is(cell); + UIObject.setStylePrimaryName(cell, primaryStyleName + + "-cell"); + cell = cell.getNextSiblingElement(); + } + row = row.getNextSiblingElement(); + } + } + + /** + * Returns the primary style name of the container. + * + * @return The primary style name or null if not set. + */ + protected String getStylePrimaryName() { + return primaryStyleName; + } + + @Override + public void setDefaultRowHeight(double px) + throws IllegalArgumentException { + if (px < 1) { + throw new IllegalArgumentException("Height must be positive. " + + px + " was given."); + } + + defaultRowHeightShouldBeAutodetected = false; + defaultRowHeight = px; + reapplyDefaultRowHeights(); + } + + @Override + public double getDefaultRowHeight() { + return defaultRowHeight; + } + + /** + * The default height of rows has (most probably) changed. + *

+ * Make sure that the displayed rows with a default height are updated + * in height and top position. + *

+ * Note:This implementation should not call + * {@link Escalator#recalculateElementSizes()} - it is done by the + * discretion of the caller of this method. + */ + protected abstract void reapplyDefaultRowHeights(); + + protected void reapplyRowHeight(final TableRowElement tr, + final double heightPx) { + assert heightPx >= 0 : "Height must not be negative"; + + Element cellElem = tr.getFirstChildElement(); + while (cellElem != null) { + cellElem.getStyle().setHeight(heightPx, Unit.PX); + cellElem = cellElem.getNextSiblingElement(); + } + + /* + * no need to apply height to tr-element, it'll be resized + * implicitly. + */ + } + + protected void setRowPosition(final TableRowElement tr, final int x, + final double y) { + positions.set(tr, x, y); + } + + /** + * Returns the assigned top position for the given element. + *

+ * Note: This method does not calculate what a row's top + * position should be. It just returns an assigned value, correct or + * not. + * + * @param tr + * the table row element to measure + * @return the current top position for {@code tr} + * @see BodyRowContainerImpl#getRowTop(int) + */ + protected double getRowTop(final TableRowElement tr) { + return positions.getTop(tr); + } + + protected void removeRowPosition(TableRowElement tr) { + positions.remove(tr); + } + + public void autodetectRowHeightLater() { + Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() { + @Override + public void execute() { + if (defaultRowHeightShouldBeAutodetected && isAttached()) { + autodetectRowHeightNow(); + defaultRowHeightShouldBeAutodetected = false; + } + } + }); + } + + public void autodetectRowHeightNow() { + if (!isAttached()) { + // Run again when attached + defaultRowHeightShouldBeAutodetected = true; + return; + } + + final Element detectionTr = DOM.createTR(); + detectionTr.setClassName(getStylePrimaryName() + "-row"); + + final Element cellElem = DOM.createElement(getCellElementTagName()); + cellElem.setClassName(getStylePrimaryName() + "-cell"); + cellElem.setInnerText("Ij"); + + detectionTr.appendChild(cellElem); + root.appendChild(detectionTr); + double boundingHeight = WidgetUtil + .getRequiredHeightBoundingClientRectDouble(cellElem); + defaultRowHeight = Math.max(1.0d, boundingHeight); + root.removeChild(detectionTr); + + if (root.hasChildNodes()) { + reapplyDefaultRowHeights(); + applyHeightByRows(); + } + } + + @Override + public Cell getCell(final Element element) { + if (element == null) { + throw new IllegalArgumentException("Element cannot be null"); + } + + /* + * Ensure that element is not root nor the direct descendant of root + * (a row) and ensure the element is inside the dom hierarchy of the + * root element. If not, return. + */ + if (root == element || element.getParentElement() == root + || !root.isOrHasChild(element)) { + return null; + } + + /* + * Ensure element is the cell element by iterating up the DOM + * hierarchy until reaching cell element. + */ + Element cellElementCandidate = element; + while (cellElementCandidate.getParentElement().getParentElement() != root) { + cellElementCandidate = cellElementCandidate.getParentElement(); + } + final TableCellElement cellElement = TableCellElement + .as(cellElementCandidate); + + // Find dom column + int domColumnIndex = -1; + for (Element e = cellElement; e != null; e = e + .getPreviousSiblingElement()) { + domColumnIndex++; + } + + // Find dom row + int domRowIndex = -1; + for (Element e = cellElement.getParentElement(); e != null; e = e + .getPreviousSiblingElement()) { + domRowIndex++; + } + + return new Cell(domRowIndex, domColumnIndex, cellElement); + } + + double measureCellWidth(TableCellElement cell, boolean withContent) { + /* + * To get the actual width of the contents, we need to get the cell + * content without any hardcoded height or width. + * + * But we don't want to modify the existing column, because that + * might trigger some unnecessary listeners and whatnot. So, + * instead, we make a deep clone of that cell, but without any + * explicit dimensions, and measure that instead. + */ + + TableCellElement cellClone = TableCellElement.as((Element) cell + .cloneNode(withContent)); + cellClone.getStyle().clearHeight(); + cellClone.getStyle().clearWidth(); + + cell.getParentElement().insertBefore(cellClone, cell); + double requiredWidth = WidgetUtil + .getRequiredWidthBoundingClientRectDouble(cellClone); + if (BrowserInfo.get().isIE()) { + /* + * IE browsers have some issues with subpixels. Occasionally + * content is overflown even if not necessary. Increase the + * counted required size by 0.01 just to be on the safe side. + */ + requiredWidth += 0.01; + } + + cellClone.removeFromParent(); + + return requiredWidth; + } + + /** + * Gets the minimum width needed to display the cell properly. + * + * @param colIndex + * index of column to measure + * @param withContent + * true if content is taken into account, + * false if not + * @return cell width needed for displaying correctly + */ + double measureMinCellWidth(int colIndex, boolean withContent) { + assert isAttached() : "Can't measure max width of cell, since Escalator is not attached to the DOM."; + + double minCellWidth = -1; + NodeList rows = root.getRows(); + + for (int row = 0; row < rows.getLength(); row++) { + + TableCellElement cell = rows.getItem(row).getCells() + .getItem(colIndex); + + if (cell != null && !cellIsPartOfSpan(cell)) { + double cellWidth = measureCellWidth(cell, withContent); + minCellWidth = Math.max(minCellWidth, cellWidth); + } + } + + return minCellWidth; + } + + private boolean cellIsPartOfSpan(TableCellElement cell) { + boolean cellHasColspan = cell.getColSpan() > 1; + boolean cellIsHidden = Display.NONE.getCssName().equals( + cell.getStyle().getDisplay()); + return cellHasColspan || cellIsHidden; + } + + void refreshColumns(int index, int numberOfColumns) { + if (getRowCount() > 0) { + Range rowRange = Range.withLength(0, getRowCount()); + Range colRange = Range.withLength(index, numberOfColumns); + refreshCells(rowRange, colRange); + } + } + + /** + * The height of this table section. + *

+ * Note that {@link Escalator#getBody() the body} will calculate its + * height, while the others will return a precomputed value. + * + * @since 7.5.0 + * + * @return the height of this table section + */ + protected abstract double getHeightOfSection(); + + protected int getLogicalRowIndex(final TableRowElement tr) { + return tr.getSectionRowIndex(); + }; + + } + + private abstract class AbstractStaticRowContainer extends + AbstractRowContainer { + + /** The height of the combined rows in the DOM. Never negative. */ + private double heightOfSection = 0; + + public AbstractStaticRowContainer(final TableSectionElement headElement) { + super(headElement); + } + + @Override + public int getDomRowCount() { + return root.getChildCount(); + } + + @Override + protected void paintRemoveRows(final int index, final int numberOfRows) { + for (int i = index; i < index + numberOfRows; i++) { + final TableRowElement tr = root.getRows().getItem(index); + paintRemoveRow(tr, index); + } + recalculateSectionHeight(); + } + + @Override + protected TableRowElement getTrByVisualIndex(final int index) + throws IndexOutOfBoundsException { + if (index >= 0 && index < root.getChildCount()) { + return root.getRows().getItem(index); + } else { + throw new IndexOutOfBoundsException("No such visual index: " + + index); + } + } + + @Override + public void insertRows(int index, int numberOfRows) { + super.insertRows(index, numberOfRows); + recalculateElementSizes(); + applyHeightByRows(); + } + + @Override + public void removeRows(int index, int numberOfRows) { + + /* + * While the rows in a static section are removed, the scrollbar is + * temporarily shrunk and then re-expanded. This leads to the fact + * that the scroll position is scooted up a bit. This means that we + * need to reset the position here. + * + * If Escalator, at some point, gets a JIT evaluation functionality, + * this re-setting is a strong candidate for removal. + */ + double oldScrollPos = verticalScrollbar.getScrollPos(); + + super.removeRows(index, numberOfRows); + recalculateElementSizes(); + applyHeightByRows(); + + verticalScrollbar.setScrollPos(oldScrollPos); + } + + @Override + protected void reapplyDefaultRowHeights() { + if (root.getChildCount() == 0) { + return; + } + + Profiler.enter("Escalator.AbstractStaticRowContainer.reapplyDefaultRowHeights"); + + Element tr = root.getRows().getItem(0); + while (tr != null) { + reapplyRowHeight(TableRowElement.as(tr), getDefaultRowHeight()); + tr = tr.getNextSiblingElement(); + } + + /* + * Because all rows are immediately displayed in the static row + * containers, the section's overall height has most probably + * changed. + */ + recalculateSectionHeight(); + + Profiler.leave("Escalator.AbstractStaticRowContainer.reapplyDefaultRowHeights"); + } + + @Override + protected void recalculateSectionHeight() { + Profiler.enter("Escalator.AbstractStaticRowContainer.recalculateSectionHeight"); + + double newHeight = calculateTotalRowHeight(); + if (newHeight != heightOfSection) { + heightOfSection = newHeight; + sectionHeightCalculated(); + + /* + * We need to update the scrollbar dimension at this point. If + * we are scrolled too far down and the static section shrinks, + * the body will try to render rows that don't exist during + * body.verifyEscalatorCount. This is because the logical row + * indices are calculated from the scrollbar position. + */ + verticalScrollbar.setOffsetSize(heightOfEscalator + - header.getHeightOfSection() + - footer.getHeightOfSection()); + + body.verifyEscalatorCount(); + body.spacerContainer.updateSpacerDecosVisibility(); + } + + Profiler.leave("Escalator.AbstractStaticRowContainer.recalculateSectionHeight"); + } + + /** + * Informs the row container that the height of its respective table + * section has changed. + *

+ * These calculations might affect some layouting logic, such as the + * body is being offset by the footer, the footer needs to be readjusted + * according to its height, and so on. + *

+ * A table section is either header, body or footer. + */ + protected abstract void sectionHeightCalculated(); + + @Override + protected void refreshCells(Range logicalRowRange, Range colRange) { + Profiler.enter("Escalator.AbstractStaticRowContainer.refreshRows"); + + assertArgumentsAreValidAndWithinRange(logicalRowRange.getStart(), + logicalRowRange.length()); + + if (!isAttached()) { + return; + } + + if (hasColumnAndRowData()) { + for (int row = logicalRowRange.getStart(); row < logicalRowRange + .getEnd(); row++) { + final TableRowElement tr = getTrByVisualIndex(row); + refreshRow(tr, row, colRange); + } + } + + Profiler.leave("Escalator.AbstractStaticRowContainer.refreshRows"); + } + + @Override + protected void paintInsertRows(int visualIndex, int numberOfRows) { + paintInsertStaticRows(visualIndex, numberOfRows); + } + + @Override + protected boolean rowCanBeFrozen(TableRowElement tr) { + assert root.isOrHasChild(tr) : "Row does not belong to this table section"; + return true; + } + + @Override + protected double getHeightOfSection() { + return Math.max(0, heightOfSection); + } + } + + private class HeaderRowContainer extends AbstractStaticRowContainer { + public HeaderRowContainer(final TableSectionElement headElement) { + super(headElement); + } + + @Override + protected void sectionHeightCalculated() { + double heightOfSection = getHeightOfSection(); + bodyElem.getStyle().setMarginTop(heightOfSection, Unit.PX); + spacerDecoContainer.getStyle().setMarginTop(heightOfSection, + Unit.PX); + verticalScrollbar.getElement().getStyle() + .setTop(heightOfSection, Unit.PX); + headerDeco.getStyle().setHeight(heightOfSection, Unit.PX); + } + + @Override + protected String getCellElementTagName() { + return "th"; + } + + @Override + public void setStylePrimaryName(String primaryStyleName) { + super.setStylePrimaryName(primaryStyleName); + UIObject.setStylePrimaryName(root, primaryStyleName + "-header"); + } + } + + private class FooterRowContainer extends AbstractStaticRowContainer { + public FooterRowContainer(final TableSectionElement footElement) { + super(footElement); + } + + @Override + public void setStylePrimaryName(String primaryStyleName) { + super.setStylePrimaryName(primaryStyleName); + UIObject.setStylePrimaryName(root, primaryStyleName + "-footer"); + } + + @Override + protected String getCellElementTagName() { + return "td"; + } + + @Override + protected void sectionHeightCalculated() { + double headerHeight = header.getHeightOfSection(); + double footerHeight = footer.getHeightOfSection(); + int vscrollHeight = (int) Math.floor(heightOfEscalator + - headerHeight - footerHeight); + + final boolean horizontalScrollbarNeeded = columnConfiguration + .calculateRowWidth() > widthOfEscalator; + if (horizontalScrollbarNeeded) { + vscrollHeight -= horizontalScrollbar.getScrollbarThickness(); + } + + footerDeco.getStyle().setHeight(footer.getHeightOfSection(), + Unit.PX); + + verticalScrollbar.setOffsetSize(vscrollHeight); + } + } + + private class BodyRowContainerImpl extends AbstractRowContainer implements + BodyRowContainer { + /* + * TODO [[optimize]]: check whether a native JsArray might be faster + * than LinkedList + */ + /** + * The order in which row elements are rendered visually in the browser, + * with the help of CSS tricks. Usually has nothing to do with the DOM + * order. + * + * @see #sortDomElements() + */ + private final LinkedList visualRowOrder = new LinkedList(); + + /** + * The logical index of the topmost row. + * + * @deprecated Use the accessors {@link #setTopRowLogicalIndex(int)}, + * {@link #updateTopRowLogicalIndex(int)} and + * {@link #getTopRowLogicalIndex()} instead + */ + @Deprecated + private int topRowLogicalIndex = 0; + + private void setTopRowLogicalIndex(int topRowLogicalIndex) { + if (LogConfiguration.loggingIsEnabled(Level.INFO)) { + Logger.getLogger("Escalator.BodyRowContainer").fine( + "topRowLogicalIndex: " + this.topRowLogicalIndex + + " -> " + topRowLogicalIndex); + } + assert topRowLogicalIndex >= 0 : "topRowLogicalIndex became negative (top left cell contents: " + + visualRowOrder.getFirst().getCells().getItem(0) + .getInnerText() + ") "; + /* + * if there's a smart way of evaluating and asserting the max index, + * this would be a nice place to put it. I haven't found out an + * effective and generic solution. + */ + + this.topRowLogicalIndex = topRowLogicalIndex; + } + + public int getTopRowLogicalIndex() { + return topRowLogicalIndex; + } + + private void updateTopRowLogicalIndex(int diff) { + setTopRowLogicalIndex(topRowLogicalIndex + diff); + } + + private class DeferredDomSorter { + private static final int SORT_DELAY_MILLIS = 50; + + // as it happens, 3 frames = 50ms @ 60fps. + private static final int REQUIRED_FRAMES_PASSED = 3; + + private final AnimationCallback frameCounter = new AnimationCallback() { + @Override + public void execute(double timestamp) { + framesPassed++; + boolean domWasSorted = sortIfConditionsMet(); + if (!domWasSorted) { + animationHandle = AnimationScheduler.get() + .requestAnimationFrame(this); + } else { + waiting = false; + } + } + }; + + private int framesPassed; + private double startTime; + private AnimationHandle animationHandle; + + /** true if a sort is scheduled */ + public boolean waiting = false; + + public void reschedule() { + waiting = true; + resetConditions(); + animationHandle = AnimationScheduler.get() + .requestAnimationFrame(frameCounter); + } + + private boolean sortIfConditionsMet() { + boolean enoughFramesHavePassed = framesPassed >= REQUIRED_FRAMES_PASSED; + boolean enoughTimeHasPassed = (Duration.currentTimeMillis() - startTime) >= SORT_DELAY_MILLIS; + boolean notTouchActivity = !scroller.touchHandlerBundle.touching; + boolean conditionsMet = enoughFramesHavePassed + && enoughTimeHasPassed && notTouchActivity; + + if (conditionsMet) { + resetConditions(); + sortDomElements(); + } + + return conditionsMet; + } + + private void resetConditions() { + if (animationHandle != null) { + animationHandle.cancel(); + animationHandle = null; + } + startTime = Duration.currentTimeMillis(); + framesPassed = 0; + } + } + + private DeferredDomSorter domSorter = new DeferredDomSorter(); + + private final SpacerContainer spacerContainer = new SpacerContainer(); + + public BodyRowContainerImpl(final TableSectionElement bodyElement) { + super(bodyElement); + } + + @Override + public void setStylePrimaryName(String primaryStyleName) { + super.setStylePrimaryName(primaryStyleName); + UIObject.setStylePrimaryName(root, primaryStyleName + "-body"); + spacerContainer.setStylePrimaryName(primaryStyleName); + } + + public void updateEscalatorRowsOnScroll() { + if (visualRowOrder.isEmpty()) { + return; + } + + boolean rowsWereMoved = false; + + final double topElementPosition; + final double nextRowBottomOffset; + SpacerContainer.SpacerImpl topSpacer = spacerContainer + .getSpacer(getTopRowLogicalIndex() - 1); + + if (topSpacer != null) { + topElementPosition = topSpacer.getTop(); + nextRowBottomOffset = topSpacer.getHeight() + + getDefaultRowHeight(); + } else { + topElementPosition = getRowTop(visualRowOrder.getFirst()); + nextRowBottomOffset = getDefaultRowHeight(); + } + + // TODO [[mpixscroll]] + final double scrollTop = tBodyScrollTop; + final double viewportOffset = topElementPosition - scrollTop; + + /* + * TODO [[optimize]] this if-else can most probably be refactored + * into a neater block of code + */ + + if (viewportOffset > 0) { + // there's empty room on top + + double rowPx = getRowHeightsSumBetweenPx(scrollTop, + topElementPosition); + int originalRowsToMove = (int) Math.ceil(rowPx + / getDefaultRowHeight()); + int rowsToMove = Math.min(originalRowsToMove, + visualRowOrder.size()); + + final int end = visualRowOrder.size(); + final int start = end - rowsToMove; + final int logicalRowIndex = getLogicalRowIndex(scrollTop); + + moveAndUpdateEscalatorRows(Range.between(start, end), 0, + logicalRowIndex); + + setTopRowLogicalIndex(logicalRowIndex); + + rowsWereMoved = true; + } + + else if (viewportOffset + nextRowBottomOffset <= 0) { + /* + * the viewport has been scrolled more than the topmost visual + * row. + */ + + double rowPx = getRowHeightsSumBetweenPx(topElementPosition, + scrollTop); + + int originalRowsToMove = (int) (rowPx / getDefaultRowHeight()); + int rowsToMove = Math.min(originalRowsToMove, + visualRowOrder.size()); + + int logicalRowIndex; + if (rowsToMove < visualRowOrder.size()) { + /* + * We scroll so little that we can just keep adding the rows + * below the current escalator + */ + logicalRowIndex = getLogicalRowIndex(visualRowOrder + .getLast()) + 1; + } else { + /* + * Since we're moving all escalator rows, we need to + * calculate the first logical row index from the scroll + * position. + */ + logicalRowIndex = getLogicalRowIndex(scrollTop); + } + + /* + * Since we're moving the viewport downwards, the visual index + * is always at the bottom. Note: Due to how + * moveAndUpdateEscalatorRows works, this will work out even if + * we move all the rows, and try to place them "at the end". + */ + final int targetVisualIndex = visualRowOrder.size(); + + // make sure that we don't move rows over the data boundary + boolean aRowWasLeftBehind = false; + if (logicalRowIndex + rowsToMove > getRowCount()) { + /* + * TODO [[spacer]]: with constant row heights, there's + * always exactly one row that will be moved beyond the data + * source, when viewport is scrolled to the end. This, + * however, isn't guaranteed anymore once row heights start + * varying. + */ + rowsToMove--; + aRowWasLeftBehind = true; + } + + /* + * Make sure we don't scroll beyond the row content. This can + * happen if we have spacers for the last rows. + */ + rowsToMove = Math.max(0, + Math.min(rowsToMove, getRowCount() - logicalRowIndex)); + + moveAndUpdateEscalatorRows(Range.between(0, rowsToMove), + targetVisualIndex, logicalRowIndex); + + if (aRowWasLeftBehind) { + /* + * To keep visualRowOrder as a spatially contiguous block of + * rows, let's make sure that the one row we didn't move + * visually still stays with the pack. + */ + final Range strayRow = Range.withOnly(0); + + /* + * We cannot trust getLogicalRowIndex, because it hasn't yet + * been updated. But since we're leaving rows behind, it + * means we've scrolled to the bottom. So, instead, we + * simply count backwards from the end. + */ + final int topLogicalIndex = getRowCount() + - visualRowOrder.size(); + moveAndUpdateEscalatorRows(strayRow, 0, topLogicalIndex); + } + + final int naiveNewLogicalIndex = getTopRowLogicalIndex() + + originalRowsToMove; + final int maxLogicalIndex = getRowCount() + - visualRowOrder.size(); + setTopRowLogicalIndex(Math.min(naiveNewLogicalIndex, + maxLogicalIndex)); + + rowsWereMoved = true; + } + + if (rowsWereMoved) { + fireRowVisibilityChangeEvent(); + domSorter.reschedule(); + } + } + + private double getRowHeightsSumBetweenPx(double y1, double y2) { + assert y1 < y2 : "y1 must be smaller than y2"; + + double viewportPx = y2 - y1; + double spacerPx = spacerContainer.getSpacerHeightsSumBetweenPx(y1, + SpacerInclusionStrategy.PARTIAL, y2, + SpacerInclusionStrategy.PARTIAL); + + return viewportPx - spacerPx; + } + + private int getLogicalRowIndex(final double px) { + double rowPx = px - spacerContainer.getSpacerHeightsSumUntilPx(px); + return (int) (rowPx / getDefaultRowHeight()); + } + + @Override + protected void paintInsertRows(final int index, final int numberOfRows) { + if (numberOfRows == 0) { + return; + } + + spacerContainer.shiftSpacersByRows(index, numberOfRows); + + /* + * TODO: this method should probably only add physical rows, and not + * populate them - let everything be populated as appropriate by the + * logic that follows. + * + * This also would lead to the fact that paintInsertRows wouldn't + * need to return anything. + */ + final List addedRows = fillAndPopulateEscalatorRowsIfNeeded( + index, numberOfRows); + + /* + * insertRows will always change the number of rows - update the + * scrollbar sizes. + */ + scroller.recalculateScrollbarsForVirtualViewport(); + + final boolean addedRowsAboveCurrentViewport = index + * getDefaultRowHeight() < getScrollTop(); + final boolean addedRowsBelowCurrentViewport = index + * getDefaultRowHeight() > getScrollTop() + + getHeightOfSection(); + + if (addedRowsAboveCurrentViewport) { + /* + * We need to tweak the virtual viewport (scroll handle + * positions, table "scroll position" and row locations), but + * without re-evaluating any rows. + */ + + final double yDelta = numberOfRows * getDefaultRowHeight(); + moveViewportAndContent(yDelta); + updateTopRowLogicalIndex(numberOfRows); + } + + else if (addedRowsBelowCurrentViewport) { + // NOOP, we already recalculated scrollbars. + } + + else { // some rows were added inside the current viewport + + final int unupdatedLogicalStart = index + addedRows.size(); + final int visualOffset = getLogicalRowIndex(visualRowOrder + .getFirst()); + + /* + * At this point, we have added new escalator rows, if so + * needed. + * + * If more rows were added than the new escalator rows can + * account for, we need to start to spin the escalator to update + * the remaining rows aswell. + */ + final int rowsStillNeeded = numberOfRows - addedRows.size(); + + if (rowsStillNeeded > 0) { + final Range unupdatedVisual = convertToVisual(Range + .withLength(unupdatedLogicalStart, rowsStillNeeded)); + final int end = getDomRowCount(); + final int start = end - unupdatedVisual.length(); + final int visualTargetIndex = unupdatedLogicalStart + - visualOffset; + moveAndUpdateEscalatorRows(Range.between(start, end), + visualTargetIndex, unupdatedLogicalStart); + + // move the surrounding rows to their correct places. + double rowTop = (unupdatedLogicalStart + (end - start)) + * getDefaultRowHeight(); + + // TODO: Get rid of this try/catch block by fixing the + // underlying issue. The reason for this erroneous behavior + // might be that Escalator actually works 'by mistake', and + // the order of operations is, in fact, wrong. + try { + final ListIterator i = visualRowOrder + .listIterator(visualTargetIndex + (end - start)); + + int logicalRowIndexCursor = unupdatedLogicalStart; + while (i.hasNext()) { + rowTop += spacerContainer + .getSpacerHeight(logicalRowIndexCursor++); + + final TableRowElement tr = i.next(); + setRowPosition(tr, 0, rowTop); + rowTop += getDefaultRowHeight(); + } + } catch (Exception e) { + Logger logger = getLogger(); + logger.warning("Ignored out-of-bounds row element access"); + logger.warning("Escalator state: start=" + start + + ", end=" + end + ", visualTargetIndex=" + + visualTargetIndex + + ", visualRowOrder.size()=" + + visualRowOrder.size()); + logger.warning(e.toString()); + } + } + + fireRowVisibilityChangeEvent(); + sortDomElements(); + } + } + + /** + * Move escalator rows around, and make sure everything gets + * appropriately repositioned and repainted. + * + * @param visualSourceRange + * the range of rows to move to a new place + * @param visualTargetIndex + * the visual index where the rows will be placed to + * @param logicalTargetIndex + * the logical index to be assigned to the first moved row + */ + private void moveAndUpdateEscalatorRows(final Range visualSourceRange, + final int visualTargetIndex, final int logicalTargetIndex) + throws IllegalArgumentException { + + if (visualSourceRange.isEmpty()) { + return; + } + + assert visualSourceRange.getStart() >= 0 : "Visual source start " + + "must be 0 or greater (was " + + visualSourceRange.getStart() + ")"; + + assert logicalTargetIndex >= 0 : "Logical target must be 0 or " + + "greater (was " + logicalTargetIndex + ")"; + + assert visualTargetIndex >= 0 : "Visual target must be 0 or greater (was " + + visualTargetIndex + ")"; + + assert visualTargetIndex <= getDomRowCount() : "Visual target " + + "must not be greater than the number of escalator rows (was " + + visualTargetIndex + ", escalator rows " + + getDomRowCount() + ")"; + + assert logicalTargetIndex + visualSourceRange.length() <= getRowCount() : "Logical " + + "target leads to rows outside of the data range (" + + Range.withLength(logicalTargetIndex, + visualSourceRange.length()) + + " goes beyond " + + Range.withLength(0, getRowCount()) + ")"; + + /* + * Since we move a range into another range, the indices might move + * about. Having 10 rows, if we move 0..1 to index 10 (to the end of + * the collection), the target range will end up being 8..9, instead + * of 10..11. + * + * This applies only if we move elements forward in the collection, + * not backward. + */ + final int adjustedVisualTargetIndex; + if (visualSourceRange.getStart() < visualTargetIndex) { + adjustedVisualTargetIndex = visualTargetIndex + - visualSourceRange.length(); + } else { + adjustedVisualTargetIndex = visualTargetIndex; + } + + if (visualSourceRange.getStart() != adjustedVisualTargetIndex) { + + /* + * Reorder the rows to their correct places within + * visualRowOrder (unless rows are moved back to their original + * places) + */ + + /* + * TODO [[optimize]]: move whichever set is smaller: the ones + * explicitly moved, or the others. So, with 10 escalator rows, + * if we are asked to move idx[0..8] to the end of the list, + * it's faster to just move idx[9] to the beginning. + */ + + final List removedRows = new ArrayList( + visualSourceRange.length()); + for (int i = 0; i < visualSourceRange.length(); i++) { + final TableRowElement tr = visualRowOrder + .remove(visualSourceRange.getStart()); + removedRows.add(tr); + } + visualRowOrder.addAll(adjustedVisualTargetIndex, removedRows); + } + + { // Refresh the contents of the affected rows + final ListIterator iter = visualRowOrder + .listIterator(adjustedVisualTargetIndex); + for (int logicalIndex = logicalTargetIndex; logicalIndex < logicalTargetIndex + + visualSourceRange.length(); logicalIndex++) { + final TableRowElement tr = iter.next(); + refreshRow(tr, logicalIndex); + } + } + + { // Reposition the rows that were moved + double newRowTop = getRowTop(logicalTargetIndex); + + final ListIterator iter = visualRowOrder + .listIterator(adjustedVisualTargetIndex); + for (int i = 0; i < visualSourceRange.length(); i++) { + final TableRowElement tr = iter.next(); + setRowPosition(tr, 0, newRowTop); + + newRowTop += getDefaultRowHeight(); + newRowTop += spacerContainer + .getSpacerHeight(logicalTargetIndex + i); + } + } + } + + /** + * Adjust the scroll position and move the contained rows. + *

+ * The difference between using this method and simply scrolling is that + * this method "takes the rows and spacers with it" and renders them + * appropriately. The viewport may be scrolled any arbitrary amount, and + * the contents are moved appropriately, but always snapped into a + * plausible place. + *

+ *

+ *
Example 1
+ *
An Escalator with default row height 20px. Adjusting the scroll + * position with 7.5px will move the viewport 7.5px down, but leave the + * row where it is.
+ *
Example 2
+ *
An Escalator with default row height 20px. Adjusting the scroll + * position with 27.5px will move the viewport 27.5px down, and place + * the row at 20px.
+ *
+ * + * @param yDelta + * the delta of pixels by which to move the viewport and + * content. A positive value moves everything downwards, + * while a negative value moves everything upwards + */ + public void moveViewportAndContent(final double yDelta) { + + if (yDelta == 0) { + return; + } + + double newTop = tBodyScrollTop + yDelta; + verticalScrollbar.setScrollPos(newTop); + + final double defaultRowHeight = getDefaultRowHeight(); + double rowPxDelta = yDelta - (yDelta % defaultRowHeight); + int rowIndexDelta = (int) (yDelta / defaultRowHeight); + if (!WidgetUtil.pixelValuesEqual(rowPxDelta, 0)) { + + Collection spacers = spacerContainer + .getSpacersAfterPx(tBodyScrollTop, + SpacerInclusionStrategy.PARTIAL); + for (SpacerContainer.SpacerImpl spacer : spacers) { + spacer.setPositionDiff(0, rowPxDelta); + spacer.setRowIndex(spacer.getRow() + rowIndexDelta); + } + + for (TableRowElement tr : visualRowOrder) { + setRowPosition(tr, 0, getRowTop(tr) + rowPxDelta); + } + } + + setBodyScrollPosition(tBodyScrollLeft, newTop); + } + + /** + * Adds new physical escalator rows to the DOM at the given index if + * there's still a need for more escalator rows. + *

+ * If Escalator already is at (or beyond) max capacity, this method does + * nothing to the DOM. + * + * @param index + * the index at which to add new escalator rows. + * Note:It is assumed that the index is both the + * visual index and the logical index. + * @param numberOfRows + * the number of rows to add at index + * @return a list of the added rows + */ + private List fillAndPopulateEscalatorRowsIfNeeded( + final int index, final int numberOfRows) { + + final int escalatorRowsStillFit = getMaxEscalatorRowCapacity() + - getDomRowCount(); + final int escalatorRowsNeeded = Math.min(numberOfRows, + escalatorRowsStillFit); + + if (escalatorRowsNeeded > 0) { + + final List addedRows = paintInsertStaticRows( + index, escalatorRowsNeeded); + visualRowOrder.addAll(index, addedRows); + + double y = index * getDefaultRowHeight() + + spacerContainer.getSpacerHeightsSumUntilIndex(index); + for (int i = index; i < visualRowOrder.size(); i++) { + + final TableRowElement tr; + if (i - index < addedRows.size()) { + tr = addedRows.get(i - index); + } else { + tr = visualRowOrder.get(i); + } + + setRowPosition(tr, 0, y); + y += getDefaultRowHeight(); + y += spacerContainer.getSpacerHeight(i); + } + + return addedRows; + } else { + return Collections.emptyList(); + } + } + + private int getMaxEscalatorRowCapacity() { + final int maxEscalatorRowCapacity = (int) Math + .ceil(getHeightOfSection() / getDefaultRowHeight()) + 1; + + /* + * maxEscalatorRowCapacity can become negative if the headers and + * footers start to overlap. This is a crazy situation, but Vaadin + * blinks the components a lot, so it's feasible. + */ + return Math.max(0, maxEscalatorRowCapacity); + } + + @Override + protected void paintRemoveRows(final int index, final int numberOfRows) { + if (numberOfRows == 0) { + return; + } + + final Range viewportRange = getVisibleRowRange(); + final Range removedRowsRange = Range + .withLength(index, numberOfRows); + + /* + * Removing spacers as the very first step will correct the + * scrollbars and row offsets right away. + * + * TODO: actually, it kinda sounds like a Grid feature that a spacer + * would be associated with a particular row. Maybe it would be + * better to have a spacer separate from rows, and simply collapse + * them if they happen to end up on top of each other. This would + * probably make supporting the -1 row pretty easy, too. + */ + spacerContainer.paintRemoveSpacers(removedRowsRange); + + final Range[] partitions = removedRowsRange + .partitionWith(viewportRange); + final Range removedAbove = partitions[0]; + final Range removedLogicalInside = partitions[1]; + final Range removedVisualInside = convertToVisual(removedLogicalInside); + + /* + * TODO: extract the following if-block to a separate method. I'll + * leave this be inlined for now, to make linediff-based code + * reviewing easier. Probably will be moved in the following patch + * set. + */ + + /* + * Adjust scroll position in one of two scenarios: + * + * 1) Rows were removed above. Then we just need to adjust the + * scrollbar by the height of the removed rows. + * + * 2) There are no logical rows above, and at least the first (if + * not more) visual row is removed. Then we need to snap the scroll + * position to the first visible row (i.e. reset scroll position to + * absolute 0) + * + * The logic is optimized in such a way that the + * moveViewportAndContent is called only once, to avoid extra + * reflows, and thus the code might seem a bit obscure. + */ + final boolean firstVisualRowIsRemoved = !removedVisualInside + .isEmpty() && removedVisualInside.getStart() == 0; + + if (!removedAbove.isEmpty() || firstVisualRowIsRemoved) { + final double yDelta = removedAbove.length() + * getDefaultRowHeight(); + final double firstLogicalRowHeight = getDefaultRowHeight(); + final boolean removalScrollsToShowFirstLogicalRow = verticalScrollbar + .getScrollPos() - yDelta < firstLogicalRowHeight; + + if (removedVisualInside.isEmpty() + && (!removalScrollsToShowFirstLogicalRow || !firstVisualRowIsRemoved)) { + /* + * rows were removed from above the viewport, so all we need + * to do is to adjust the scroll position to account for the + * removed rows + */ + moveViewportAndContent(-yDelta); + } else if (removalScrollsToShowFirstLogicalRow) { + /* + * It seems like we've removed all rows from above, and also + * into the current viewport. This means we'll need to even + * out the scroll position to exactly 0 (i.e. adjust by the + * current negative scrolltop, presto!), so that it isn't + * aligned funnily + */ + moveViewportAndContent(-verticalScrollbar.getScrollPos()); + } + } + + // ranges evaluated, let's do things. + if (!removedVisualInside.isEmpty()) { + int escalatorRowCount = body.getDomRowCount(); + + /* + * remember: the rows have already been subtracted from the row + * count at this point + */ + int rowsLeft = getRowCount(); + if (rowsLeft < escalatorRowCount) { + int escalatorRowsToRemove = escalatorRowCount - rowsLeft; + for (int i = 0; i < escalatorRowsToRemove; i++) { + final TableRowElement tr = visualRowOrder + .remove(removedVisualInside.getStart()); + + paintRemoveRow(tr, index); + removeRowPosition(tr); + } + escalatorRowCount -= escalatorRowsToRemove; + + /* + * Because we're removing escalator rows, we don't have + * anything to scroll by. Let's make sure the viewport is + * scrolled to top, to render any rows possibly left above. + */ + body.setBodyScrollPosition(tBodyScrollLeft, 0); + + /* + * We might have removed some rows from the middle, so let's + * make sure we're not left with any holes. Also remember: + * visualIndex == logicalIndex applies now. + */ + final int dirtyRowsStart = removedLogicalInside.getStart(); + double y = getRowTop(dirtyRowsStart); + for (int i = dirtyRowsStart; i < escalatorRowCount; i++) { + final TableRowElement tr = visualRowOrder.get(i); + setRowPosition(tr, 0, y); + y += getDefaultRowHeight(); + y += spacerContainer.getSpacerHeight(i); + } + + /* + * this is how many rows appeared into the viewport from + * below + */ + final int rowsToUpdateDataOn = numberOfRows + - escalatorRowsToRemove; + final int start = Math.max(0, escalatorRowCount + - rowsToUpdateDataOn); + final int end = escalatorRowCount; + for (int i = start; i < end; i++) { + final TableRowElement tr = visualRowOrder.get(i); + refreshRow(tr, i); + } + } + + else { + // No escalator rows need to be removed. + + /* + * Two things (or a combination thereof) can happen: + * + * 1) We're scrolled to the bottom, the last rows are + * removed. SOLUTION: moveAndUpdateEscalatorRows the + * bottommost rows, and place them at the top to be + * refreshed. + * + * 2) We're scrolled somewhere in the middle, arbitrary rows + * are removed. SOLUTION: moveAndUpdateEscalatorRows the + * removed rows, and place them at the bottom to be + * refreshed. + * + * Since a combination can also happen, we need to handle + * this in a smart way, all while avoiding + * double-refreshing. + */ + + final double contentBottom = getRowCount() + * getDefaultRowHeight(); + final double viewportBottom = tBodyScrollTop + + getHeightOfSection(); + if (viewportBottom <= contentBottom) { + /* + * We're in the middle of the row container, everything + * is added to the bottom + */ + paintRemoveRowsAtMiddle(removedLogicalInside, + removedVisualInside, 0); + } + + else if (removedVisualInside.contains(0) + && numberOfRows >= visualRowOrder.size()) { + /* + * We're removing so many rows that the viewport is + * pushed up more than a screenful. This means we can + * simply scroll up and everything will work without a + * sweat. + */ + + double left = horizontalScrollbar.getScrollPos(); + double top = contentBottom - visualRowOrder.size() + * getDefaultRowHeight(); + setBodyScrollPosition(left, top); + + Range allEscalatorRows = Range.withLength(0, + visualRowOrder.size()); + int logicalTargetIndex = getRowCount() + - allEscalatorRows.length(); + moveAndUpdateEscalatorRows(allEscalatorRows, 0, + logicalTargetIndex); + + /* + * Scrolling the body to the correct location will be + * fixed automatically. Because the amount of rows is + * decreased, the viewport is pushed up as the scrollbar + * shrinks. So no need to do anything there. + * + * TODO [[optimize]]: This might lead to a double body + * refresh. Needs investigation. + */ + } + + else if (contentBottom + + (numberOfRows * getDefaultRowHeight()) + - viewportBottom < getDefaultRowHeight()) { + /* + * We're at the end of the row container, everything is + * added to the top. + */ + + /* + * FIXME [[spacer]]: above if-clause is coded to only + * work with default row heights - will not work with + * variable row heights + */ + + paintRemoveRowsAtBottom(removedLogicalInside, + removedVisualInside); + updateTopRowLogicalIndex(-removedLogicalInside.length()); + } + + else { + /* + * We're in a combination, where we need to both scroll + * up AND show new rows at the bottom. + * + * Example: Scrolled down to show the second to last + * row. Remove two. Viewport scrolls up, revealing the + * row above row. The last element collapses up and into + * view. + * + * Reminder: this use case handles only the case when + * there are enough escalator rows to still render a + * full view. I.e. all escalator rows will _always_ be + * populated + */ + /*- + * 1 1 |1| <- newly rendered + * |2| |2| |2| + * |3| ==> |*| ==> |5| <- newly rendered + * |4| |*| + * 5 5 + * + * 1 1 |1| <- newly rendered + * |2| |*| |4| + * |3| ==> |*| ==> |5| <- newly rendered + * |4| |4| + * 5 5 + */ + + /* + * STEP 1: + * + * reorganize deprecated escalator rows to bottom, but + * don't re-render anything yet + */ + /*- + * 1 1 1 + * |2| |*| |4| + * |3| ==> |*| ==> |*| + * |4| |4| |*| + * 5 5 5 + */ + double newTop = getRowTop(visualRowOrder + .get(removedVisualInside.getStart())); + for (int i = 0; i < removedVisualInside.length(); i++) { + final TableRowElement tr = visualRowOrder + .remove(removedVisualInside.getStart()); + visualRowOrder.addLast(tr); + } + + for (int i = removedVisualInside.getStart(); i < escalatorRowCount; i++) { + final TableRowElement tr = visualRowOrder.get(i); + setRowPosition(tr, 0, (int) newTop); + newTop += getDefaultRowHeight(); + newTop += spacerContainer.getSpacerHeight(i + + removedLogicalInside.getStart()); + } + + /* + * STEP 2: + * + * manually scroll + */ + /*- + * 1 |1| <-- newly rendered (by scrolling) + * |4| |4| + * |*| ==> |*| + * |*| + * 5 5 + */ + final double newScrollTop = contentBottom + - getHeightOfSection(); + setScrollTop(newScrollTop); + /* + * Manually call the scroll handler, so we get immediate + * effects in the escalator. + */ + scroller.onScroll(); + + /* + * Move the bottommost (n+1:th) escalator row to top, + * because scrolling up doesn't handle that for us + * automatically + */ + moveAndUpdateEscalatorRows( + Range.withOnly(escalatorRowCount - 1), + 0, + getLogicalRowIndex(visualRowOrder.getFirst()) - 1); + updateTopRowLogicalIndex(-1); + + /* + * STEP 3: + * + * update remaining escalator rows + */ + /*- + * |1| |1| + * |4| ==> |4| + * |*| |5| <-- newly rendered + * + * 5 + */ + + final int rowsScrolled = (int) (Math + .ceil((viewportBottom - contentBottom) + / getDefaultRowHeight())); + final int start = escalatorRowCount + - (removedVisualInside.length() - rowsScrolled); + final Range visualRefreshRange = Range.between(start, + escalatorRowCount); + final int logicalTargetIndex = getLogicalRowIndex(visualRowOrder + .getFirst()) + start; + // in-place move simply re-renders the rows. + moveAndUpdateEscalatorRows(visualRefreshRange, start, + logicalTargetIndex); + } + } + + fireRowVisibilityChangeEvent(); + sortDomElements(); + } + + updateTopRowLogicalIndex(-removedAbove.length()); + + /* + * this needs to be done after the escalator has been shrunk down, + * or it won't work correctly (due to setScrollTop invocation) + */ + scroller.recalculateScrollbarsForVirtualViewport(); + } + + private void paintRemoveRowsAtMiddle(final Range removedLogicalInside, + final Range removedVisualInside, final int logicalOffset) { + /*- + * : : : + * |2| |2| |2| + * |3| ==> |*| ==> |4| + * |4| |4| |6| <- newly rendered + * : : : + */ + + final int escalatorRowCount = visualRowOrder.size(); + + final int logicalTargetIndex = getLogicalRowIndex(visualRowOrder + .getLast()) + - (removedVisualInside.length() - 1) + + logicalOffset; + moveAndUpdateEscalatorRows(removedVisualInside, escalatorRowCount, + logicalTargetIndex); + + // move the surrounding rows to their correct places. + final ListIterator iterator = visualRowOrder + .listIterator(removedVisualInside.getStart()); + + double rowTop = getRowTop(removedLogicalInside.getStart() + + logicalOffset); + for (int i = removedVisualInside.getStart(); i < escalatorRowCount + - removedVisualInside.length(); i++) { + final TableRowElement tr = iterator.next(); + setRowPosition(tr, 0, rowTop); + rowTop += getDefaultRowHeight(); + rowTop += spacerContainer.getSpacerHeight(i + + removedLogicalInside.getStart()); + } + } + + private void paintRemoveRowsAtBottom(final Range removedLogicalInside, + final Range removedVisualInside) { + /*- + * : + * : : |4| <- newly rendered + * |5| |5| |5| + * |6| ==> |*| ==> |7| + * |7| |7| + */ + + final int logicalTargetIndex = getLogicalRowIndex(visualRowOrder + .getFirst()) - removedVisualInside.length(); + moveAndUpdateEscalatorRows(removedVisualInside, 0, + logicalTargetIndex); + + // move the surrounding rows to their correct places. + int firstUpdatedIndex = removedVisualInside.getEnd(); + final ListIterator iterator = visualRowOrder + .listIterator(firstUpdatedIndex); + + double rowTop = getRowTop(removedLogicalInside.getStart()); + int i = 0; + while (iterator.hasNext()) { + final TableRowElement tr = iterator.next(); + setRowPosition(tr, 0, rowTop); + rowTop += getDefaultRowHeight(); + rowTop += spacerContainer.getSpacerHeight(firstUpdatedIndex + + i++); + } + } + + @Override + protected int getLogicalRowIndex(final TableRowElement tr) { + assert tr.getParentNode() == root : "The given element isn't a row element in the body"; + int internalIndex = visualRowOrder.indexOf(tr); + return getTopRowLogicalIndex() + internalIndex; + } + + @Override + protected void recalculateSectionHeight() { + // NOOP for body, since it doesn't make any sense. + } + + /** + * Adjusts the row index and number to be relevant for the current + * virtual viewport. + *

+ * It converts a logical range of rows index to the matching visual + * range, truncating the resulting range with the viewport. + *

+ *

    + *
  • Escalator contains logical rows 0..100 + *
  • Current viewport showing logical rows 20..29 + *
  • convertToVisual([20..29]) → [0..9] + *
  • convertToVisual([15..24]) → [0..4] + *
  • convertToVisual([25..29]) → [5..9] + *
  • convertToVisual([26..39]) → [6..9] + *
  • convertToVisual([0..5]) → [0..-1] (empty) + *
  • convertToVisual([35..1]) → [0..-1] (empty) + *
  • convertToVisual([0..100]) → [0..9] + *
+ * + * @return a logical range converted to a visual range, truncated to the + * current viewport. The first visual row has the index 0. + */ + private Range convertToVisual(final Range logicalRange) { + + if (logicalRange.isEmpty()) { + return logicalRange; + } else if (visualRowOrder.isEmpty()) { + // empty range + return Range.withLength(0, 0); + } + + /* + * TODO [[spacer]]: these assumptions will be totally broken with + * spacers. + */ + final int maxEscalatorRows = getMaxEscalatorRowCapacity(); + final int currentTopRowIndex = getLogicalRowIndex(visualRowOrder + .getFirst()); + + final Range[] partitions = logicalRange.partitionWith(Range + .withLength(currentTopRowIndex, maxEscalatorRows)); + final Range insideRange = partitions[1]; + return insideRange.offsetBy(-currentTopRowIndex); + } + + @Override + protected String getCellElementTagName() { + return "td"; + } + + @Override + protected double getHeightOfSection() { + final int tableHeight = tableWrapper.getOffsetHeight(); + final double footerHeight = footer.getHeightOfSection(); + final double headerHeight = header.getHeightOfSection(); + + double heightOfSection = tableHeight - footerHeight - headerHeight; + return Math.max(0, heightOfSection); + } + + @Override + protected void refreshCells(Range logicalRowRange, Range colRange) { + Profiler.enter("Escalator.BodyRowContainer.refreshRows"); + + final Range visualRange = convertToVisual(logicalRowRange); + + if (!visualRange.isEmpty()) { + final int firstLogicalRowIndex = getLogicalRowIndex(visualRowOrder + .getFirst()); + for (int rowNumber = visualRange.getStart(); rowNumber < visualRange + .getEnd(); rowNumber++) { + refreshRow(visualRowOrder.get(rowNumber), + firstLogicalRowIndex + rowNumber, colRange); + } + } + + Profiler.leave("Escalator.BodyRowContainer.refreshRows"); + } + + @Override + protected TableRowElement getTrByVisualIndex(final int index) + throws IndexOutOfBoundsException { + if (index >= 0 && index < visualRowOrder.size()) { + return visualRowOrder.get(index); + } else { + throw new IndexOutOfBoundsException("No such visual index: " + + index); + } + } + + @Override + public TableRowElement getRowElement(int index) { + if (index < 0 || index >= getRowCount()) { + throw new IndexOutOfBoundsException("No such logical index: " + + index); + } + int visualIndex = index + - getLogicalRowIndex(visualRowOrder.getFirst()); + if (visualIndex >= 0 && visualIndex < visualRowOrder.size()) { + return super.getRowElement(visualIndex); + } else { + throw new IllegalStateException("Row with logical index " + + index + " is currently not available in the DOM"); + } + } + + private void setBodyScrollPosition(final double scrollLeft, + final double scrollTop) { + tBodyScrollLeft = scrollLeft; + tBodyScrollTop = scrollTop; + position.set(bodyElem, -tBodyScrollLeft, -tBodyScrollTop); + position.set(spacerDecoContainer, 0, -tBodyScrollTop); + } + + /** + * Make sure that there is a correct amount of escalator rows: Add more + * if needed, or remove any superfluous ones. + *

+ * This method should be called when e.g. the height of the Escalator + * changes. + *

+ * Note: This method will make sure that the escalator rows are + * placed in the proper places. By default new rows are added below, but + * if the content is scrolled down, the rows are populated on top + * instead. + */ + public void verifyEscalatorCount() { + /* + * This method indeed has a smell very similar to paintRemoveRows + * and paintInsertRows. + * + * Unfortunately, those the code can't trivially be shared, since + * there are some slight differences in the respective + * responsibilities. The "paint" methods fake the addition and + * removal of rows, and make sure to either push existing data out + * of view, or draw new data into view. Only in some special cases + * will the DOM element count change. + * + * This method, however, has the explicit responsibility to verify + * that when "something" happens, we still have the correct amount + * of escalator rows in the DOM, and if not, we make sure to modify + * that count. Only in some special cases do we need to take into + * account other things than simply modifying the DOM element count. + */ + + Profiler.enter("Escalator.BodyRowContainer.verifyEscalatorCount"); + + if (!isAttached()) { + return; + } + + final int maxEscalatorRows = getMaxEscalatorRowCapacity(); + final int neededEscalatorRows = Math.min(maxEscalatorRows, + body.getRowCount()); + final int neededEscalatorRowsDiff = neededEscalatorRows + - visualRowOrder.size(); + + if (neededEscalatorRowsDiff > 0) { + // needs more + + /* + * This is a workaround for the issue where we might be scrolled + * to the bottom, and the widget expands beyond the content + * range + */ + + final int index = visualRowOrder.size(); + final int nextLastLogicalIndex; + if (!visualRowOrder.isEmpty()) { + nextLastLogicalIndex = getLogicalRowIndex(visualRowOrder + .getLast()) + 1; + } else { + nextLastLogicalIndex = 0; + } + + final boolean contentWillFit = nextLastLogicalIndex < getRowCount() + - neededEscalatorRowsDiff; + if (contentWillFit) { + final List addedRows = fillAndPopulateEscalatorRowsIfNeeded( + index, neededEscalatorRowsDiff); + + /* + * Since fillAndPopulateEscalatorRowsIfNeeded operates on + * the assumption that index == visual index == logical + * index, we thank for the added escalator rows, but since + * they're painted in the wrong CSS position, we need to + * move them to their actual locations. + * + * Note: this is the second (see body.paintInsertRows) + * occasion where fillAndPopulateEscalatorRowsIfNeeded would + * behave "more correctly" if it only would add escalator + * rows to the DOM and appropriate bookkeping, and not + * actually populate them :/ + */ + moveAndUpdateEscalatorRows( + Range.withLength(index, addedRows.size()), index, + nextLastLogicalIndex); + } else { + /* + * TODO [[optimize]] + * + * We're scrolled so far down that all rows can't be simply + * appended at the end, since we might start displaying + * escalator rows that don't exist. To avoid the mess that + * is body.paintRemoveRows, this is a dirty hack that dumbs + * the problem down to a more basic and already-solved + * problem: + * + * 1) scroll all the way up 2) add the missing escalator + * rows 3) scroll back to the original position. + * + * Letting the browser scroll back to our original position + * will automatically solve any possible overflow problems, + * since the browser will not allow us to scroll beyond the + * actual content. + */ + + final double oldScrollTop = getScrollTop(); + setScrollTop(0); + scroller.onScroll(); + fillAndPopulateEscalatorRowsIfNeeded(index, + neededEscalatorRowsDiff); + setScrollTop(oldScrollTop); + scroller.onScroll(); + } + } + + else if (neededEscalatorRowsDiff < 0) { + // needs less + + final ListIterator iter = visualRowOrder + .listIterator(visualRowOrder.size()); + for (int i = 0; i < -neededEscalatorRowsDiff; i++) { + final Element last = iter.previous(); + last.removeFromParent(); + iter.remove(); + } + + /* + * If we were scrolled to the bottom so that we didn't have an + * extra escalator row at the bottom, we'll probably end up with + * blank space at the bottom of the escalator, and one extra row + * above the header. + * + * Experimentation idea #1: calculate "scrollbottom" vs content + * bottom and remove one row from top, rest from bottom. This + * FAILED, since setHeight has already happened, thus we never + * will detect ourselves having been scrolled all the way to the + * bottom. + */ + + if (!visualRowOrder.isEmpty()) { + final double firstRowTop = getRowTop(visualRowOrder + .getFirst()); + final double firstRowMinTop = tBodyScrollTop + - getDefaultRowHeight(); + if (firstRowTop < firstRowMinTop) { + final int newLogicalIndex = getLogicalRowIndex(visualRowOrder + .getLast()) + 1; + moveAndUpdateEscalatorRows(Range.withOnly(0), + visualRowOrder.size(), newLogicalIndex); + } + } + } + + if (neededEscalatorRowsDiff != 0) { + fireRowVisibilityChangeEvent(); + } + + Profiler.leave("Escalator.BodyRowContainer.verifyEscalatorCount"); + } + + @Override + protected void reapplyDefaultRowHeights() { + if (visualRowOrder.isEmpty()) { + return; + } + + Profiler.enter("Escalator.BodyRowContainer.reapplyDefaultRowHeights"); + + /* step 1: resize and reposition rows */ + for (int i = 0; i < visualRowOrder.size(); i++) { + TableRowElement tr = visualRowOrder.get(i); + reapplyRowHeight(tr, getDefaultRowHeight()); + + final int logicalIndex = getTopRowLogicalIndex() + i; + setRowPosition(tr, 0, logicalIndex * getDefaultRowHeight()); + } + + /* + * step 2: move scrollbar so that it corresponds to its previous + * place + */ + + /* + * This ratio needs to be calculated with the scrollsize (not max + * scroll position) in order to align the top row with the new + * scroll position. + */ + double scrollRatio = verticalScrollbar.getScrollPos() + / verticalScrollbar.getScrollSize(); + scroller.recalculateScrollbarsForVirtualViewport(); + verticalScrollbar.setScrollPos((int) (getDefaultRowHeight() + * getRowCount() * scrollRatio)); + setBodyScrollPosition(horizontalScrollbar.getScrollPos(), + verticalScrollbar.getScrollPos()); + scroller.onScroll(); + + /* step 3: make sure we have the correct amount of escalator rows. */ + verifyEscalatorCount(); + + int logicalLogical = (int) (getRowTop(visualRowOrder.getFirst()) / getDefaultRowHeight()); + setTopRowLogicalIndex(logicalLogical); + + Profiler.leave("Escalator.BodyRowContainer.reapplyDefaultRowHeights"); + } + + /** + * Sorts the rows in the DOM to correspond to the visual order. + * + * @see #visualRowOrder + */ + private void sortDomElements() { + final String profilingName = "Escalator.BodyRowContainer.sortDomElements"; + Profiler.enter(profilingName); + + /* + * Focus is lost from an element if that DOM element is (or any of + * its parents are) removed from the document. Therefore, we sort + * everything around that row instead. + */ + final TableRowElement focusedRow = getRowWithFocus(); + + if (focusedRow != null) { + assert focusedRow.getParentElement() == root : "Trying to sort around a row that doesn't exist in body"; + assert visualRowOrder.contains(focusedRow) + || body.spacerContainer.isSpacer(focusedRow) : "Trying to sort around a row that doesn't exist in visualRowOrder or is not a spacer."; + } + + /* + * Two cases handled simultaneously: + * + * 1) No focus on rows. We iterate visualRowOrder backwards, and + * take the respective element in the DOM, and place it as the first + * child in the body element. Then we take the next-to-last from + * visualRowOrder, and put that first, pushing the previous row as + * the second child. And so on... + * + * 2) Focus on some row within Escalator body. Again, we iterate + * visualRowOrder backwards. This time, we use the focused row as a + * pivot: Instead of placing rows from the bottom of visualRowOrder + * and placing it first, we place it underneath the focused row. + * Once we hit the focused row, we don't move it (to not reset + * focus) but change sorting mode. After that, we place all rows as + * the first child. + */ + + List orderedBodyRows = new ArrayList( + visualRowOrder); + Map spacers = body.spacerContainer + .getSpacers(); + + /* + * Start at -1 to include a spacer that is rendered above the + * viewport, but its parent row is still not shown + */ + for (int i = -1; i < visualRowOrder.size(); i++) { + SpacerContainer.SpacerImpl spacer = spacers.remove(Integer + .valueOf(getTopRowLogicalIndex() + i)); + + if (spacer != null) { + orderedBodyRows.add(i + 1, spacer.getRootElement()); + spacer.show(); + } + } + /* + * At this point, invisible spacers aren't reordered, so their + * position in the DOM will remain undefined. + */ + + // If a spacer was not reordered, it means that it's out of view. + for (SpacerContainer.SpacerImpl unmovedSpacer : spacers.values()) { + unmovedSpacer.hide(); + } + + /* + * If we have a focused row, start in the mode where we put + * everything underneath that row. Otherwise, all rows are placed as + * first child. + */ + boolean insertFirst = (focusedRow == null); + + final ListIterator i = orderedBodyRows + .listIterator(orderedBodyRows.size()); + while (i.hasPrevious()) { + TableRowElement tr = i.previous(); + + if (tr == focusedRow) { + insertFirst = true; + } else if (insertFirst) { + root.insertFirst(tr); + } else { + root.insertAfter(tr, focusedRow); + } + } + + Profiler.leave(profilingName); + } + + /** + * Get the {@literal } row that contains (or has) focus. + * + * @return The {@literal } row that contains a focused DOM + * element, or null if focus is outside of a body + * row. + */ + private TableRowElement getRowWithFocus() { + TableRowElement rowContainingFocus = null; + + final Element focusedElement = WidgetUtil.getFocusedElement(); + + if (focusedElement != null && root.isOrHasChild(focusedElement)) { + Element e = focusedElement; + + while (e != null && e != root) { + /* + * You never know if there's several tables embedded in a + * cell... We'll take the deepest one. + */ + if (TableRowElement.is(e)) { + rowContainingFocus = TableRowElement.as(e); + } + e = e.getParentElement(); + } + } + + return rowContainingFocus; + } + + @Override + public Cell getCell(Element element) { + Cell cell = super.getCell(element); + if (cell == null) { + return null; + } + + // Convert DOM coordinates to logical coordinates for rows + TableRowElement rowElement = (TableRowElement) cell.getElement() + .getParentElement(); + return new Cell(getLogicalRowIndex(rowElement), cell.getColumn(), + cell.getElement()); + } + + @Override + public void setSpacer(int rowIndex, double height) + throws IllegalArgumentException { + spacerContainer.setSpacer(rowIndex, height); + } + + @Override + public void setSpacerUpdater(SpacerUpdater spacerUpdater) + throws IllegalArgumentException { + spacerContainer.setSpacerUpdater(spacerUpdater); + } + + @Override + public SpacerUpdater getSpacerUpdater() { + return spacerContainer.getSpacerUpdater(); + } + + /** + * Calculates the correct top position of a row at a logical + * index, regardless if there is one there or not. + *

+ * A correct result requires that both {@link #getDefaultRowHeight()} is + * consistent, and the placement and height of all spacers above the + * given logical index are consistent. + * + * @param logicalIndex + * the logical index of the row for which to calculate the + * top position + * @return the position at which to place a row in {@code logicalIndex} + * @see #getRowTop(TableRowElement) + */ + private double getRowTop(int logicalIndex) { + double top = spacerContainer + .getSpacerHeightsSumUntilIndex(logicalIndex); + return top + (logicalIndex * getDefaultRowHeight()); + } + + public void shiftRowPositions(int row, double diff) { + for (TableRowElement tr : getVisibleRowsAfter(row)) { + setRowPosition(tr, 0, getRowTop(tr) + diff); + } + } + + private List getVisibleRowsAfter(int logicalRow) { + Range visibleRowLogicalRange = getVisibleRowRange(); + + boolean allRowsAreInView = logicalRow < visibleRowLogicalRange + .getStart(); + boolean noRowsAreInView = logicalRow >= visibleRowLogicalRange + .getEnd() - 1; + + if (allRowsAreInView) { + return Collections.unmodifiableList(visualRowOrder); + } else if (noRowsAreInView) { + return Collections.emptyList(); + } else { + int fromIndex = (logicalRow - visibleRowLogicalRange.getStart()) + 1; + int toIndex = visibleRowLogicalRange.length(); + List sublist = visualRowOrder.subList( + fromIndex, toIndex); + return Collections.unmodifiableList(sublist); + } + } + + @Override + public int getDomRowCount() { + return root.getChildCount() + - spacerContainer.getSpacersInDom().size(); + } + + @Override + protected boolean rowCanBeFrozen(TableRowElement tr) { + return visualRowOrder.contains(tr); + } + + void reapplySpacerWidths() { + spacerContainer.reapplySpacerWidths(); + } + + void scrollToSpacer(int spacerIndex, ScrollDestination destination, + int padding) { + spacerContainer.scrollToSpacer(spacerIndex, destination, padding); + } + } + + private class ColumnConfigurationImpl implements ColumnConfiguration { + public class Column { + public static final double DEFAULT_COLUMN_WIDTH_PX = 100; + + private double definedWidth = -1; + private double calculatedWidth = DEFAULT_COLUMN_WIDTH_PX; + private boolean measuringRequested = false; + + public void setWidth(double px) { + definedWidth = px; + + if (px < 0) { + if (isAttached()) { + calculateWidth(); + } else { + /* + * the column's width is calculated at Escalator.onLoad + * via measureAndSetWidthIfNeeded! + */ + measuringRequested = true; + } + } else { + calculatedWidth = px; + } + } + + public double getDefinedWidth() { + return definedWidth; + } + + /** + * Returns the actual width in the DOM. + * + * @return the width in pixels in the DOM. Returns -1 if the column + * needs measuring, but has not been yet measured + */ + public double getCalculatedWidth() { + /* + * This might return an untrue value (e.g. during init/onload), + * since we haven't had a proper chance to actually calculate + * widths yet. + * + * This is fixed during Escalator.onLoad, by the call to + * "measureAndSetWidthIfNeeded", which fixes "everything". + */ + if (!measuringRequested) { + return calculatedWidth; + } else { + return -1; + } + } + + /** + * Checks if the column needs measuring, and then measures it. + *

+ * Called by {@link Escalator#onLoad()}. + */ + public boolean measureAndSetWidthIfNeeded() { + assert isAttached() : "Column.measureAndSetWidthIfNeeded() was called even though Escalator was not attached!"; + + if (measuringRequested) { + measuringRequested = false; + setWidth(definedWidth); + return true; + } + return false; + } + + private void calculateWidth() { + calculatedWidth = getMaxCellWidth(columns.indexOf(this)); + } + } + + private final List columns = new ArrayList(); + private int frozenColumns = 0; + + /* + * TODO: this is a bit of a duplicate functionality with the + * Column.calculatedWidth caching. Probably should use one or the other, + * not both + */ + /** + * A cached array of all the calculated column widths. + * + * @see #getCalculatedColumnWidths() + */ + private double[] widthsArray = null; + + /** + * {@inheritDoc} + *

+ * Implementation detail: This method does no DOM modifications + * (i.e. is very cheap to call) if there are no rows in the DOM when + * this method is called. + * + * @see #hasSomethingInDom() + */ + @Override + public void removeColumns(final int index, final int numberOfColumns) { + // Validate + assertArgumentsAreValidAndWithinRange(index, numberOfColumns); + + // Move the horizontal scrollbar to the left, if removed columns are + // to the left of the viewport + removeColumnsAdjustScrollbar(index, numberOfColumns); + + // Remove from DOM + header.paintRemoveColumns(index, numberOfColumns); + body.paintRemoveColumns(index, numberOfColumns); + footer.paintRemoveColumns(index, numberOfColumns); + + // Remove from bookkeeping + flyweightRow.removeCells(index, numberOfColumns); + columns.subList(index, index + numberOfColumns).clear(); + + // Adjust frozen columns + if (index < getFrozenColumnCount()) { + if (index + numberOfColumns < frozenColumns) { + /* + * Last removed column was frozen, meaning that all removed + * columns were frozen. Just decrement the number of frozen + * columns accordingly. + */ + frozenColumns -= numberOfColumns; + } else { + /* + * If last removed column was not frozen, we have removed + * columns beyond the frozen range, so all remaining frozen + * columns are to the left of the removed columns. + */ + frozenColumns = index; + } + } + + scroller.recalculateScrollbarsForVirtualViewport(); + body.verifyEscalatorCount(); + + if (getColumnConfiguration().getColumnCount() > 0) { + reapplyRowWidths(header); + reapplyRowWidths(body); + reapplyRowWidths(footer); + } + + /* + * Colspans make any kind of automatic clever content re-rendering + * impossible: As soon as anything has colspans, removing one might + * reveal further colspans, modifying the DOM structure once again, + * ending in a cascade of updates. Because we don't know how the + * data is updated. + * + * So, instead, we don't do anything. The client code is responsible + * for re-rendering the content (if so desired). Everything Just + * Works (TM) if colspans aren't used. + */ + } + + private void reapplyRowWidths(AbstractRowContainer container) { + if (container.getRowCount() > 0) { + container.reapplyRowWidths(); + } + } + + private void removeColumnsAdjustScrollbar(int index, int numberOfColumns) { + if (horizontalScrollbar.getOffsetSize() >= horizontalScrollbar + .getScrollSize()) { + return; + } + + double leftPosOfFirstColumnToRemove = getCalculatedColumnsWidth(Range + .between(0, index)); + double widthOfColumnsToRemove = getCalculatedColumnsWidth(Range + .withLength(index, numberOfColumns)); + + double scrollLeft = horizontalScrollbar.getScrollPos(); + + if (scrollLeft <= leftPosOfFirstColumnToRemove) { + /* + * viewport is scrolled to the left of the first removed column, + * so there's no need to adjust anything + */ + return; + } + + double adjustedScrollLeft = Math.max(leftPosOfFirstColumnToRemove, + scrollLeft - widthOfColumnsToRemove); + horizontalScrollbar.setScrollPos(adjustedScrollLeft); + } + + /** + * Calculate the width of a row, as the sum of columns' widths. + * + * @return the width of a row, in pixels + */ + public double calculateRowWidth() { + return getCalculatedColumnsWidth(Range.between(0, getColumnCount())); + } + + private void assertArgumentsAreValidAndWithinRange(final int index, + final int numberOfColumns) { + if (numberOfColumns < 1) { + throw new IllegalArgumentException( + "Number of columns can't be less than 1 (was " + + numberOfColumns + ")"); + } + + if (index < 0 || index + numberOfColumns > getColumnCount()) { + throw new IndexOutOfBoundsException("The given " + + "column range (" + index + ".." + + (index + numberOfColumns) + + ") was outside of the current " + + "number of columns (" + getColumnCount() + ")"); + } + } + + /** + * {@inheritDoc} + *

+ * Implementation detail: This method does no DOM modifications + * (i.e. is very cheap to call) if there is no data for rows when this + * method is called. + * + * @see #hasColumnAndRowData() + */ + @Override + public void insertColumns(final int index, final int numberOfColumns) { + // Validate + if (index < 0 || index > getColumnCount()) { + throw new IndexOutOfBoundsException("The given index(" + index + + ") was outside of the current number of columns (0.." + + getColumnCount() + ")"); + } + + if (numberOfColumns < 1) { + throw new IllegalArgumentException( + "Number of columns must be 1 or greater (was " + + numberOfColumns); + } + + // Add to bookkeeping + flyweightRow.addCells(index, numberOfColumns); + for (int i = 0; i < numberOfColumns; i++) { + columns.add(index, new Column()); + } + + // Adjust frozen columns + boolean frozen = index < frozenColumns; + if (frozen) { + frozenColumns += numberOfColumns; + } + + // this needs to be before the scrollbar adjustment. + boolean scrollbarWasNeeded = horizontalScrollbar.getOffsetSize() < horizontalScrollbar + .getScrollSize(); + scroller.recalculateScrollbarsForVirtualViewport(); + boolean scrollbarIsNowNeeded = horizontalScrollbar.getOffsetSize() < horizontalScrollbar + .getScrollSize(); + if (!scrollbarWasNeeded && scrollbarIsNowNeeded) { + body.verifyEscalatorCount(); + } + + // Add to DOM + header.paintInsertColumns(index, numberOfColumns, frozen); + body.paintInsertColumns(index, numberOfColumns, frozen); + footer.paintInsertColumns(index, numberOfColumns, frozen); + + // fix initial width + if (header.getRowCount() > 0 || body.getRowCount() > 0 + || footer.getRowCount() > 0) { + + Map colWidths = new HashMap(); + Double width = Double.valueOf(Column.DEFAULT_COLUMN_WIDTH_PX); + for (int i = index; i < index + numberOfColumns; i++) { + Integer col = Integer.valueOf(i); + colWidths.put(col, width); + } + getColumnConfiguration().setColumnWidths(colWidths); + } + + // Adjust scrollbar + double pixelsToInsertedColumn = columnConfiguration + .getCalculatedColumnsWidth(Range.withLength(0, index)); + final boolean columnsWereAddedToTheLeftOfViewport = scroller.lastScrollLeft > pixelsToInsertedColumn; + + if (columnsWereAddedToTheLeftOfViewport) { + double insertedColumnsWidth = columnConfiguration + .getCalculatedColumnsWidth(Range.withLength(index, + numberOfColumns)); + horizontalScrollbar.setScrollPos(scroller.lastScrollLeft + + insertedColumnsWidth); + } + + /* + * Colspans make any kind of automatic clever content re-rendering + * impossible: As soon as anything has colspans, adding one might + * affect surrounding colspans, modifying the DOM structure once + * again, ending in a cascade of updates. Because we don't know how + * the data is updated. + * + * So, instead, we don't do anything. The client code is responsible + * for re-rendering the content (if so desired). Everything Just + * Works (TM) if colspans aren't used. + */ + } + + @Override + public int getColumnCount() { + return columns.size(); + } + + @Override + public void setFrozenColumnCount(int count) + throws IllegalArgumentException { + if (count < 0 || count > getColumnCount()) { + throw new IllegalArgumentException( + "count must be between 0 and the current number of columns (" + + getColumnCount() + ")"); + } + int oldCount = frozenColumns; + if (count == oldCount) { + return; + } + + frozenColumns = count; + + if (hasSomethingInDom()) { + // Are we freezing or unfreezing? + boolean frozen = count > oldCount; + + int firstAffectedCol; + int firstUnaffectedCol; + + if (frozen) { + firstAffectedCol = oldCount; + firstUnaffectedCol = count; + } else { + firstAffectedCol = count; + firstUnaffectedCol = oldCount; + } + + if (oldCount > 0) { + header.setColumnLastFrozen(oldCount - 1, false); + body.setColumnLastFrozen(oldCount - 1, false); + footer.setColumnLastFrozen(oldCount - 1, false); + } + if (count > 0) { + header.setColumnLastFrozen(count - 1, true); + body.setColumnLastFrozen(count - 1, true); + footer.setColumnLastFrozen(count - 1, true); + } + + for (int col = firstAffectedCol; col < firstUnaffectedCol; col++) { + header.setColumnFrozen(col, frozen); + body.setColumnFrozen(col, frozen); + footer.setColumnFrozen(col, frozen); + } + } + + scroller.recalculateScrollbarsForVirtualViewport(); + } + + @Override + public int getFrozenColumnCount() { + return frozenColumns; + } + + @Override + public void setColumnWidth(int index, double px) + throws IllegalArgumentException { + setColumnWidths(Collections.singletonMap(Integer.valueOf(index), + Double.valueOf(px))); + } + + @Override + public void setColumnWidths(Map indexWidthMap) + throws IllegalArgumentException { + + if (indexWidthMap == null) { + throw new IllegalArgumentException("indexWidthMap was null"); + } + + if (indexWidthMap.isEmpty()) { + return; + } + + for (Entry entry : indexWidthMap.entrySet()) { + int index = entry.getKey().intValue(); + double width = entry.getValue().doubleValue(); + + checkValidColumnIndex(index); + + // Not all browsers will accept any fractional size.. + width = WidgetUtil.roundSizeDown(width); + columns.get(index).setWidth(width); + + } + + widthsArray = null; + header.reapplyColumnWidths(); + body.reapplyColumnWidths(); + footer.reapplyColumnWidths(); + + recalculateElementSizes(); + } + + private void checkValidColumnIndex(int index) + throws IllegalArgumentException { + if (!Range.withLength(0, getColumnCount()).contains(index)) { + throw new IllegalArgumentException("The given column index (" + + index + ") does not exist"); + } + } + + @Override + public double getColumnWidth(int index) throws IllegalArgumentException { + checkValidColumnIndex(index); + return columns.get(index).getDefinedWidth(); + } + + @Override + public double getColumnWidthActual(int index) { + return columns.get(index).getCalculatedWidth(); + } + + private double getMaxCellWidth(int colIndex) + throws IllegalArgumentException { + double headerWidth = header.measureMinCellWidth(colIndex, true); + double bodyWidth = body.measureMinCellWidth(colIndex, true); + double footerWidth = footer.measureMinCellWidth(colIndex, true); + + double maxWidth = Math.max(headerWidth, + Math.max(bodyWidth, footerWidth)); + assert maxWidth >= 0 : "Got a negative max width for a column, which should be impossible."; + return maxWidth; + } + + private double getMinCellWidth(int colIndex) + throws IllegalArgumentException { + double headerWidth = header.measureMinCellWidth(colIndex, false); + double bodyWidth = body.measureMinCellWidth(colIndex, false); + double footerWidth = footer.measureMinCellWidth(colIndex, false); + + double minWidth = Math.max(headerWidth, + Math.max(bodyWidth, footerWidth)); + assert minWidth >= 0 : "Got a negative max width for a column, which should be impossible."; + return minWidth; + } + + /** + * Calculates the width of the columns in a given range. + * + * @param columns + * the columns to calculate + * @return the total width of the columns in the given + * columns + */ + double getCalculatedColumnsWidth(final Range columns) { + /* + * This is an assert instead of an exception, since this is an + * internal method. + */ + assert columns.isSubsetOf(Range.between(0, getColumnCount())) : "Range " + + "was outside of current column range (i.e.: " + + Range.between(0, getColumnCount()) + + ", but was given :" + + columns; + + double sum = 0; + for (int i = columns.getStart(); i < columns.getEnd(); i++) { + double columnWidthActual = getColumnWidthActual(i); + sum += columnWidthActual; + } + return sum; + } + + double[] getCalculatedColumnWidths() { + if (widthsArray == null || widthsArray.length != getColumnCount()) { + widthsArray = new double[getColumnCount()]; + for (int i = 0; i < columns.size(); i++) { + widthsArray[i] = columns.get(i).getCalculatedWidth(); + } + } + return widthsArray; + } + + @Override + public void refreshColumns(int index, int numberOfColumns) + throws IndexOutOfBoundsException, IllegalArgumentException { + if (numberOfColumns < 1) { + throw new IllegalArgumentException( + "Number of columns must be 1 or greater (was " + + numberOfColumns + ")"); + } + + if (index < 0 || index + numberOfColumns > getColumnCount()) { + throw new IndexOutOfBoundsException("The given " + + "column range (" + index + ".." + + (index + numberOfColumns) + + ") was outside of the current number of columns (" + + getColumnCount() + ")"); + } + + header.refreshColumns(index, numberOfColumns); + body.refreshColumns(index, numberOfColumns); + footer.refreshColumns(index, numberOfColumns); + } + } + + /** + * A decision on how to measure a spacer when it is partially within a + * designated range. + *

+ * The meaning of each value may differ depending on the context it is being + * used in. Check that particular method's JavaDoc. + */ + private enum SpacerInclusionStrategy { + /** A representation of "the entire spacer". */ + COMPLETE, + + /** A representation of "a partial spacer". */ + PARTIAL, + + /** A representation of "no spacer at all". */ + NONE + } + + private class SpacerContainer { + + /** This is used mainly for testing purposes */ + private static final String SPACER_LOGICAL_ROW_PROPERTY = "vLogicalRow"; + + private final class SpacerImpl implements Spacer { + private TableCellElement spacerElement; + private TableRowElement root; + private DivElement deco; + private int rowIndex; + private double height = -1; + private boolean domHasBeenSetup = false; + private double decoHeight; + private double defaultCellBorderBottomSize = -1; + + public SpacerImpl(int rowIndex) { + this.rowIndex = rowIndex; + + root = TableRowElement.as(DOM.createTR()); + spacerElement = TableCellElement.as(DOM.createTD()); + root.appendChild(spacerElement); + root.setPropertyInt(SPACER_LOGICAL_ROW_PROPERTY, rowIndex); + deco = DivElement.as(DOM.createDiv()); + } + + public void setPositionDiff(double x, double y) { + setPosition(getLeft() + x, getTop() + y); + } + + public void setupDom(double height) { + assert !domHasBeenSetup : "DOM can't be set up twice."; + assert RootPanel.get().getElement().isOrHasChild(root) : "Root element should've been attached to the DOM by now."; + domHasBeenSetup = true; + + getRootElement().getStyle().setWidth(getInnerWidth(), Unit.PX); + setHeight(height); + + spacerElement.setColSpan(getColumnConfiguration() + .getColumnCount()); + + setStylePrimaryName(getStylePrimaryName()); + } + + public TableRowElement getRootElement() { + return root; + } + + @Override + public Element getDecoElement() { + return deco; + } + + public void setPosition(double x, double y) { + positions.set(getRootElement(), x, y); + positions + .set(getDecoElement(), 0, y - getSpacerDecoTopOffset()); + } + + private double getSpacerDecoTopOffset() { + return getBody().getDefaultRowHeight(); + } + + public void setStylePrimaryName(String style) { + UIObject.setStylePrimaryName(root, style + "-spacer"); + UIObject.setStylePrimaryName(deco, style + "-spacer-deco"); + } + + public void setHeight(double height) { + + assert height >= 0 : "Height must be more >= 0 (was " + height + + ")"; + + final double heightDiff = height - Math.max(0, this.height); + final double oldHeight = this.height; + + this.height = height; + + // since the spacer might be rendered on top of the previous + // rows border (done with css), need to increase height the + // amount of the border thickness + if (defaultCellBorderBottomSize < 0) { + defaultCellBorderBottomSize = WidgetUtil + .getBorderBottomThickness(body.getRowElement( + getVisibleRowRange().getStart()) + .getFirstChildElement()); + } + root.getStyle().setHeight(height + defaultCellBorderBottomSize, + Unit.PX); + + // move the visible spacers getRow row onwards. + shiftSpacerPositionsAfterRow(getRow(), heightDiff); + + /* + * If we're growing, we'll adjust the scroll size first, then + * adjust scrolling. If we're shrinking, we do it after the + * second if-clause. + */ + boolean spacerIsGrowing = heightDiff > 0; + if (spacerIsGrowing) { + verticalScrollbar.setScrollSize(verticalScrollbar + .getScrollSize() + heightDiff); + } + + /* + * Don't modify the scrollbars if we're expanding the -1 spacer + * while we're scrolled to the top. + */ + boolean minusOneSpacerException = spacerIsGrowing + && getRow() == -1 && body.getTopRowLogicalIndex() == 0; + + boolean viewportNeedsScrolling = getRow() < body + .getTopRowLogicalIndex() && !minusOneSpacerException; + if (viewportNeedsScrolling) { + + /* + * We can't use adjustScrollPos here, probably because of a + * bookkeeping-related race condition. + * + * This particular situation is easier, however, since we + * know exactly how many pixels we need to move (heightDiff) + * and all elements below the spacer always need to move + * that pixel amount. + */ + + for (TableRowElement row : body.visualRowOrder) { + body.setRowPosition(row, 0, body.getRowTop(row) + + heightDiff); + } + + double top = getTop(); + double bottom = top + oldHeight; + double scrollTop = verticalScrollbar.getScrollPos(); + + boolean viewportTopIsAtMidSpacer = top < scrollTop + && scrollTop < bottom; + + final double moveDiff; + if (viewportTopIsAtMidSpacer && !spacerIsGrowing) { + + /* + * If the scroll top is in the middle of the modified + * spacer, we want to scroll the viewport up as usual, + * but we don't want to scroll past the top of it. + * + * Math.max ensures this (remember: the result is going + * to be negative). + */ + + moveDiff = Math.max(heightDiff, top - scrollTop); + } else { + moveDiff = heightDiff; + } + body.setBodyScrollPosition(tBodyScrollLeft, tBodyScrollTop + + moveDiff); + verticalScrollbar.setScrollPosByDelta(moveDiff); + + } else { + body.shiftRowPositions(getRow(), heightDiff); + } + + if (!spacerIsGrowing) { + verticalScrollbar.setScrollSize(verticalScrollbar + .getScrollSize() + heightDiff); + } + + updateDecoratorGeometry(height); + } + + /** Resizes and places the decorator. */ + private void updateDecoratorGeometry(double detailsHeight) { + Style style = deco.getStyle(); + decoHeight = detailsHeight + getBody().getDefaultRowHeight(); + style.setHeight(decoHeight, Unit.PX); + } + + @Override + public Element getElement() { + return spacerElement; + } + + @Override + public int getRow() { + return rowIndex; + } + + public double getHeight() { + assert height >= 0 : "Height was not previously set by setHeight."; + return height; + } + + public double getTop() { + return positions.getTop(getRootElement()); + } + + public double getLeft() { + return positions.getLeft(getRootElement()); + } + + /** + * Sets a new row index for this spacer. Also updates the bookeeping + * at {@link SpacerContainer#rowIndexToSpacer}. + */ + @SuppressWarnings("boxing") + public void setRowIndex(int rowIndex) { + SpacerImpl spacer = rowIndexToSpacer.remove(this.rowIndex); + assert this == spacer : "trying to move an unexpected spacer."; + this.rowIndex = rowIndex; + root.setPropertyInt(SPACER_LOGICAL_ROW_PROPERTY, rowIndex); + rowIndexToSpacer.put(this.rowIndex, this); + } + + /** + * Updates the spacer's visibility parameters, based on whether it + * is being currently visible or not. + */ + public void updateVisibility() { + if (isInViewport()) { + show(); + } else { + hide(); + } + } + + private boolean isInViewport() { + int top = (int) Math.ceil(getTop()); + int height = (int) Math.floor(getHeight()); + Range location = Range.withLength(top, height); + return getViewportPixels().intersects(location); + } + + public void show() { + getRootElement().getStyle().clearDisplay(); + getDecoElement().getStyle().clearDisplay(); + } + + public void hide() { + getRootElement().getStyle().setDisplay(Display.NONE); + getDecoElement().getStyle().setDisplay(Display.NONE); + } + + /** + * Crop the decorator element so that it doesn't overlap the header + * and footer sections. + * + * @param bodyTop + * the top cordinate of the escalator body + * @param bodyBottom + * the bottom cordinate of the escalator body + * @param decoWidth + * width of the deco + */ + private void updateDecoClip(final double bodyTop, + final double bodyBottom, final double decoWidth) { + final int top = deco.getAbsoluteTop(); + final int bottom = deco.getAbsoluteBottom(); + /* + * FIXME + * + * Height and its use is a workaround for the issue where + * coordinates of the deco are not calculated yet. This will + * prevent a deco from being displayed when it's added to DOM + */ + final int height = bottom - top; + if (top < bodyTop || bottom > bodyBottom) { + final double topClip = Math.max(0.0D, bodyTop - top); + final double bottomClip = height + - Math.max(0.0D, bottom - bodyBottom); + // TODO [optimize] not sure how GWT compiles this + final String clip = new StringBuilder("rect(") + .append(topClip).append("px,").append(decoWidth) + .append("px,").append(bottomClip).append("px,0)") + .toString(); + deco.getStyle().setProperty("clip", clip); + } else { + deco.getStyle().setProperty("clip", "auto"); + } + } + } + + private final TreeMap rowIndexToSpacer = new TreeMap(); + + private SpacerUpdater spacerUpdater = SpacerUpdater.NULL; + + private final ScrollHandler spacerScroller = new ScrollHandler() { + private double prevScrollX = 0; + + @Override + public void onScroll(ScrollEvent event) { + if (WidgetUtil.pixelValuesEqual(getScrollLeft(), prevScrollX)) { + return; + } + + prevScrollX = getScrollLeft(); + for (SpacerImpl spacer : rowIndexToSpacer.values()) { + spacer.setPosition(prevScrollX, spacer.getTop()); + } + } + }; + private HandlerRegistration spacerScrollerRegistration; + + /** Width of the spacers' decos. Calculated once then cached. */ + private double spacerDecoWidth = 0.0D; + + public void setSpacer(int rowIndex, double height) + throws IllegalArgumentException { + + if (rowIndex < -1 || rowIndex >= getBody().getRowCount()) { + throw new IllegalArgumentException("invalid row index: " + + rowIndex + ", while the body only has " + + getBody().getRowCount() + " rows."); + } + + if (height >= 0) { + if (!spacerExists(rowIndex)) { + insertNewSpacer(rowIndex, height); + } else { + updateExistingSpacer(rowIndex, height); + } + } else if (spacerExists(rowIndex)) { + removeSpacer(rowIndex); + } + + updateSpacerDecosVisibility(); + } + + /** Checks if a given element is a spacer element */ + public boolean isSpacer(Element row) { + + /* + * If this needs optimization, we could do a more heuristic check + * based on stylenames and stuff, instead of iterating through the + * map. + */ + + for (SpacerImpl spacer : rowIndexToSpacer.values()) { + if (spacer.getRootElement().equals(row)) { + return true; + } + } + + return false; + } + + @SuppressWarnings("boxing") + void scrollToSpacer(int spacerIndex, ScrollDestination destination, + int padding) { + + assert !destination.equals(ScrollDestination.MIDDLE) + || padding != 0 : "destination/padding check should be done before this method"; + + if (!rowIndexToSpacer.containsKey(spacerIndex)) { + throw new IllegalArgumentException("No spacer open at index " + + spacerIndex); + } + + SpacerImpl spacer = rowIndexToSpacer.get(spacerIndex); + double targetStartPx = spacer.getTop(); + double targetEndPx = targetStartPx + spacer.getHeight(); + + Range viewportPixels = getViewportPixels(); + double viewportStartPx = viewportPixels.getStart(); + double viewportEndPx = viewportPixels.getEnd(); + + double scrollTop = getScrollPos(destination, targetStartPx, + targetEndPx, viewportStartPx, viewportEndPx, padding); + + setScrollTop(scrollTop); + } + + public void reapplySpacerWidths() { + // FIXME #16266 , spacers get couple pixels too much because borders + final double width = getInnerWidth() - spacerDecoWidth; + for (SpacerImpl spacer : rowIndexToSpacer.values()) { + spacer.getRootElement().getStyle().setWidth(width, Unit.PX); + } + } + + public void paintRemoveSpacers(Range removedRowsRange) { + removeSpacers(removedRowsRange); + shiftSpacersByRows(removedRowsRange.getStart(), + -removedRowsRange.length()); + } + + @SuppressWarnings("boxing") + public void removeSpacers(Range removedRange) { + + Map removedSpacers = rowIndexToSpacer + .subMap(removedRange.getStart(), true, + removedRange.getEnd(), false); + + if (removedSpacers.isEmpty()) { + return; + } + + for (SpacerImpl spacer : removedSpacers.values()) { + /* + * [[optimization]] TODO: Each invocation of the setHeight + * method has a cascading effect in the DOM. if this proves to + * be slow, the DOM offset could be updated as a batch. + */ + + destroySpacerContent(spacer); + spacer.setHeight(0); // resets row offsets + spacer.getRootElement().removeFromParent(); + spacer.getDecoElement().removeFromParent(); + } + + removedSpacers.clear(); + + if (rowIndexToSpacer.isEmpty()) { + assert spacerScrollerRegistration != null : "Spacer scroller registration was null"; + spacerScrollerRegistration.removeHandler(); + spacerScrollerRegistration = null; + } + } + + public Map getSpacers() { + return new HashMap(rowIndexToSpacer); + } + + /** + * Calculates the sum of all spacers. + * + * @return sum of all spacers, or 0 if no spacers present + */ + public double getSpacerHeightsSum() { + return getHeights(rowIndexToSpacer.values()); + } + + /** + * Calculates the sum of all spacers from one row index onwards. + * + * @param logicalRowIndex + * the spacer to include as the first calculated spacer + * @return the sum of all spacers from {@code logicalRowIndex} and + * onwards, or 0 if no suitable spacers were found + */ + @SuppressWarnings("boxing") + public Collection getSpacersForRowAndAfter( + int logicalRowIndex) { + return new ArrayList(rowIndexToSpacer.tailMap( + logicalRowIndex, true).values()); + } + + /** + * Get all spacers from one pixel point onwards. + *

+ * + * In this method, the {@link SpacerInclusionStrategy} has the following + * meaning when a spacer lies in the middle of either pixel argument: + *

+ *
{@link SpacerInclusionStrategy#COMPLETE COMPLETE} + *
include the spacer + *
{@link SpacerInclusionStrategy#PARTIAL PARTIAL} + *
include the spacer + *
{@link SpacerInclusionStrategy#NONE NONE} + *
ignore the spacer + *
+ * + * @param px + * the pixel point after which to return all spacers + * @param strategy + * the inclusion strategy regarding the {@code px} + * @return a collection of the spacers that exist after {@code px} + */ + public Collection getSpacersAfterPx(final double px, + final SpacerInclusionStrategy strategy) { + + ArrayList spacers = new ArrayList( + rowIndexToSpacer.values()); + + for (int i = 0; i < spacers.size(); i++) { + SpacerImpl spacer = spacers.get(i); + + double top = spacer.getTop(); + double bottom = top + spacer.getHeight(); + + if (top > px) { + return spacers.subList(i, spacers.size()); + } else if (bottom > px) { + if (strategy == SpacerInclusionStrategy.NONE) { + return spacers.subList(i + 1, spacers.size()); + } else { + return spacers.subList(i, spacers.size()); + } + } + } + + return Collections.emptySet(); + } + + /** + * Gets the spacers currently rendered in the DOM. + * + * @return an unmodifiable (but live) collection of the spacers + * currently in the DOM + */ + public Collection getSpacersInDom() { + return Collections + .unmodifiableCollection(rowIndexToSpacer.values()); + } + + /** + * Gets the amount of pixels occupied by spacers between two pixel + * points. + *

+ * In this method, the {@link SpacerInclusionStrategy} has the following + * meaning when a spacer lies in the middle of either pixel argument: + *

+ *
{@link SpacerInclusionStrategy#COMPLETE COMPLETE} + *
take the entire spacer into account + *
{@link SpacerInclusionStrategy#PARTIAL PARTIAL} + *
take only the visible area into account + *
{@link SpacerInclusionStrategy#NONE NONE} + *
ignore that spacer + *
+ * + * @param rangeTop + * the top pixel point + * @param topInclusion + * the inclusion strategy regarding {@code rangeTop}. + * @param rangeBottom + * the bottom pixel point + * @param bottomInclusion + * the inclusion strategy regarding {@code rangeBottom}. + * @return the pixels occupied by spacers between {@code rangeTop} and + * {@code rangeBottom} + */ + public double getSpacerHeightsSumBetweenPx(double rangeTop, + SpacerInclusionStrategy topInclusion, double rangeBottom, + SpacerInclusionStrategy bottomInclusion) { + + assert rangeTop <= rangeBottom : "rangeTop must be less than rangeBottom"; + + double heights = 0; + + /* + * TODO [[optimize]]: this might be somewhat inefficient (due to + * iterator-based scanning, instead of using the treemap's search + * functionalities). But it should be easy to write, read, verify + * and maintain. + */ + for (SpacerImpl spacer : rowIndexToSpacer.values()) { + double top = spacer.getTop(); + double height = spacer.getHeight(); + double bottom = top + height; + + /* + * If we happen to implement a DoubleRange (in addition to the + * int-based Range) at some point, the following logic should + * probably be converted into using the + * Range.partitionWith-equivalent. + */ + + boolean topIsAboveRange = top < rangeTop; + boolean topIsInRange = rangeTop <= top && top <= rangeBottom; + boolean topIsBelowRange = rangeBottom < top; + + boolean bottomIsAboveRange = bottom < rangeTop; + boolean bottomIsInRange = rangeTop <= bottom + && bottom <= rangeBottom; + boolean bottomIsBelowRange = rangeBottom < bottom; + + assert topIsAboveRange ^ topIsBelowRange ^ topIsInRange : "Bad top logic"; + assert bottomIsAboveRange ^ bottomIsBelowRange + ^ bottomIsInRange : "Bad bottom logic"; + + if (bottomIsAboveRange) { + continue; + } else if (topIsBelowRange) { + return heights; + } + + else if (topIsAboveRange && bottomIsInRange) { + switch (topInclusion) { + case PARTIAL: + heights += bottom - rangeTop; + break; + case COMPLETE: + heights += height; + break; + default: + break; + } + } + + else if (topIsAboveRange && bottomIsBelowRange) { + + /* + * Here we arbitrarily decide that the top inclusion will + * have the honor of overriding the bottom inclusion if + * happens to be a conflict of interests. + */ + switch (topInclusion) { + case NONE: + return 0; + case COMPLETE: + return height; + case PARTIAL: + return rangeBottom - rangeTop; + default: + throw new IllegalArgumentException( + "Unexpected inclusion state :" + topInclusion); + } + + } else if (topIsInRange && bottomIsInRange) { + heights += height; + } + + else if (topIsInRange && bottomIsBelowRange) { + switch (bottomInclusion) { + case PARTIAL: + heights += rangeBottom - top; + break; + case COMPLETE: + heights += height; + break; + default: + break; + } + + return heights; + } + + else { + assert false : "Unnaccounted-for situation"; + } + } + + return heights; + } + + /** + * Gets the amount of pixels occupied by spacers from the top until a + * certain spot from the top of the body. + * + * @param px + * pixels counted from the top + * @return the pixels occupied by spacers up until {@code px} + */ + public double getSpacerHeightsSumUntilPx(double px) { + return getSpacerHeightsSumBetweenPx(0, + SpacerInclusionStrategy.PARTIAL, px, + SpacerInclusionStrategy.PARTIAL); + } + + /** + * Gets the amount of pixels occupied by spacers until a logical row + * index. + * + * @param logicalIndex + * a logical row index + * @return the pixels occupied by spacers up until {@code logicalIndex} + */ + @SuppressWarnings("boxing") + public double getSpacerHeightsSumUntilIndex(int logicalIndex) { + return getHeights(rowIndexToSpacer.headMap(logicalIndex, false) + .values()); + } + + private double getHeights(Collection spacers) { + double heights = 0; + for (SpacerImpl spacer : spacers) { + heights += spacer.getHeight(); + } + return heights; + } + + /** + * Gets the height of the spacer for a row index. + * + * @param rowIndex + * the index of the row where the spacer should be + * @return the height of the spacer at index {@code rowIndex}, or 0 if + * there is no spacer there + */ + public double getSpacerHeight(int rowIndex) { + SpacerImpl spacer = getSpacer(rowIndex); + if (spacer != null) { + return spacer.getHeight(); + } else { + return 0; + } + } + + private boolean spacerExists(int rowIndex) { + return rowIndexToSpacer.containsKey(Integer.valueOf(rowIndex)); + } + + @SuppressWarnings("boxing") + private void insertNewSpacer(int rowIndex, double height) { + + if (spacerScrollerRegistration == null) { + spacerScrollerRegistration = addScrollHandler(spacerScroller); + } + + final SpacerImpl spacer = new SpacerImpl(rowIndex); + + rowIndexToSpacer.put(rowIndex, spacer); + // set the position before adding it to DOM + positions.set(spacer.getRootElement(), getScrollLeft(), + calculateSpacerTop(rowIndex)); + + TableRowElement spacerRoot = spacer.getRootElement(); + spacerRoot.getStyle().setWidth( + columnConfiguration.calculateRowWidth(), Unit.PX); + body.getElement().appendChild(spacerRoot); + spacer.setupDom(height); + // set the deco position, requires that spacer is in the DOM + positions.set(spacer.getDecoElement(), 0, + spacer.getTop() - spacer.getSpacerDecoTopOffset()); + + spacerDecoContainer.appendChild(spacer.getDecoElement()); + if (spacerDecoContainer.getParentElement() == null) { + getElement().appendChild(spacerDecoContainer); + // calculate the spacer deco width, it won't change + spacerDecoWidth = WidgetUtil + .getRequiredWidthBoundingClientRectDouble(spacer + .getDecoElement()); + } + + initSpacerContent(spacer); + + body.sortDomElements(); + } + + private void updateExistingSpacer(int rowIndex, double newHeight) { + getSpacer(rowIndex).setHeight(newHeight); + } + + public SpacerImpl getSpacer(int rowIndex) { + return rowIndexToSpacer.get(Integer.valueOf(rowIndex)); + } + + private void removeSpacer(int rowIndex) { + removeSpacers(Range.withOnly(rowIndex)); + } + + public void setStylePrimaryName(String style) { + for (SpacerImpl spacer : rowIndexToSpacer.values()) { + spacer.setStylePrimaryName(style); + } + } + + public void setSpacerUpdater(SpacerUpdater spacerUpdater) + throws IllegalArgumentException { + if (spacerUpdater == null) { + throw new IllegalArgumentException( + "spacer updater cannot be null"); + } + + destroySpacerContent(rowIndexToSpacer.values()); + this.spacerUpdater = spacerUpdater; + initSpacerContent(rowIndexToSpacer.values()); + } + + public SpacerUpdater getSpacerUpdater() { + return spacerUpdater; + } + + private void destroySpacerContent(Iterable spacers) { + for (SpacerImpl spacer : spacers) { + destroySpacerContent(spacer); + } + } + + private void destroySpacerContent(SpacerImpl spacer) { + assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator before detaching"; + assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator before detaching"; + spacerUpdater.destroy(spacer); + assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator before detaching"; + assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator before detaching"; + } + + private void initSpacerContent(Iterable spacers) { + for (SpacerImpl spacer : spacers) { + initSpacerContent(spacer); + } + } + + private void initSpacerContent(SpacerImpl spacer) { + assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator before attaching"; + assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator before attaching"; + spacerUpdater.init(spacer); + assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator during attaching"; + assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator during attaching"; + + spacer.updateVisibility(); + } + + public String getSubPartName(Element subElement) { + for (SpacerImpl spacer : rowIndexToSpacer.values()) { + if (spacer.getRootElement().isOrHasChild(subElement)) { + return "spacer[" + spacer.getRow() + "]"; + } + } + return null; + } + + public Element getSubPartElement(int index) { + SpacerImpl spacer = rowIndexToSpacer.get(Integer.valueOf(index)); + if (spacer != null) { + return spacer.getElement(); + } else { + return null; + } + } + + private double calculateSpacerTop(int logicalIndex) { + return body.getRowTop(logicalIndex) + body.getDefaultRowHeight(); + } + + @SuppressWarnings("boxing") + private void shiftSpacerPositionsAfterRow(int changedRowIndex, + double diffPx) { + for (SpacerImpl spacer : rowIndexToSpacer.tailMap(changedRowIndex, + false).values()) { + spacer.setPositionDiff(0, diffPx); + } + } + + /** + * Shifts spacers at and after a specific row by an amount of rows. + *

+ * This moves both their associated row index and also their visual + * placement. + *

+ * Note: This method does not check for the validity of any + * arguments. + * + * @param index + * the index of first row to move + * @param numberOfRows + * the number of rows to shift the spacers with. A positive + * value is downwards, a negative value is upwards. + */ + public void shiftSpacersByRows(int index, int numberOfRows) { + final double pxDiff = numberOfRows * body.getDefaultRowHeight(); + for (SpacerContainer.SpacerImpl spacer : getSpacersForRowAndAfter(index)) { + spacer.setPositionDiff(0, pxDiff); + spacer.setRowIndex(spacer.getRow() + numberOfRows); + } + } + + private void updateSpacerDecosVisibility() { + final Range visibleRowRange = getVisibleRowRange(); + Collection visibleSpacers = rowIndexToSpacer.subMap( + visibleRowRange.getStart() - 1, + visibleRowRange.getEnd() + 1).values(); + if (!visibleSpacers.isEmpty()) { + final double top = tableWrapper.getAbsoluteTop() + + header.getHeightOfSection(); + final double bottom = tableWrapper.getAbsoluteBottom() + - footer.getHeightOfSection(); + for (SpacerImpl spacer : visibleSpacers) { + spacer.updateDecoClip(top, bottom, spacerDecoWidth); + } + } + } + } + + private class ElementPositionBookkeeper { + /** + * A map containing cached values of an element's current top position. + */ + private final Map elementTopPositionMap = new HashMap(); + private final Map elementLeftPositionMap = new HashMap(); + + public void set(final Element e, final double x, final double y) { + assert e != null : "Element was null"; + position.set(e, x, y); + elementTopPositionMap.put(e, Double.valueOf(y)); + elementLeftPositionMap.put(e, Double.valueOf(x)); + } + + public double getTop(final Element e) { + Double top = elementTopPositionMap.get(e); + if (top == null) { + throw new IllegalArgumentException("Element " + e + + " was not found in the position bookkeeping"); + } + return top.doubleValue(); + } + + public double getLeft(final Element e) { + Double left = elementLeftPositionMap.get(e); + if (left == null) { + throw new IllegalArgumentException("Element " + e + + " was not found in the position bookkeeping"); + } + return left.doubleValue(); + } + + public void remove(Element e) { + elementTopPositionMap.remove(e); + elementLeftPositionMap.remove(e); + } + } + + /** + * Utility class for parsing and storing SubPart request string attributes + * for Grid and Escalator. + * + * @since 7.5.0 + */ + public static class SubPartArguments { + private String type; + private int[] indices; + + private SubPartArguments(String type, int[] indices) { + /* + * The constructor is private so that no third party would by + * mistake start using this parsing scheme, since it's not official + * by TestBench (yet?). + */ + + this.type = type; + this.indices = indices; + } + + public String getType() { + return type; + } + + public int getIndicesLength() { + return indices.length; + } + + public int getIndex(int i) { + return indices[i]; + } + + public int[] getIndices() { + return Arrays.copyOf(indices, indices.length); + } + + static SubPartArguments create(String subPart) { + String[] splitArgs = subPart.split("\\["); + String type = splitArgs[0]; + int[] indices = new int[splitArgs.length - 1]; + for (int i = 0; i < indices.length; ++i) { + String tmp = splitArgs[i + 1]; + indices[i] = Integer.parseInt(tmp.substring(0, + tmp.indexOf("]", 1))); + } + return new SubPartArguments(type, indices); + } + } + + // abs(atan(y/x))*(180/PI) = n deg, x = 1, solve y + /** + * The solution to + * |tan-1(x)|×(180/π) = 30 + * . + *

+ * This constant is placed in the Escalator class, instead of an inner + * class, since even mathematical expressions aren't allowed in non-static + * inner classes for constants. + */ + private static final double RATIO_OF_30_DEGREES = 1 / Math.sqrt(3); + /** + * The solution to + * |tan-1(x)|×(180/π) = 40 + * . + *

+ * This constant is placed in the Escalator class, instead of an inner + * class, since even mathematical expressions aren't allowed in non-static + * inner classes for constants. + */ + private static final double RATIO_OF_40_DEGREES = Math.tan(2 * Math.PI / 9); + + private static final String DEFAULT_WIDTH = "500.0px"; + private static final String DEFAULT_HEIGHT = "400.0px"; + + private FlyweightRow flyweightRow = new FlyweightRow(); + + /** The {@code } tag. */ + private final TableSectionElement headElem = TableSectionElement.as(DOM + .createTHead()); + /** The {@code } tag. */ + private final TableSectionElement bodyElem = TableSectionElement.as(DOM + .createTBody()); + /** The {@code } tag. */ + private final TableSectionElement footElem = TableSectionElement.as(DOM + .createTFoot()); + + /** + * TODO: investigate whether this field is now unnecessary, as + * {@link ScrollbarBundle} now caches its values. + * + * @deprecated maybe... + */ + @Deprecated + private double tBodyScrollTop = 0; + + /** + * TODO: investigate whether this field is now unnecessary, as + * {@link ScrollbarBundle} now caches its values. + * + * @deprecated maybe... + */ + @Deprecated + private double tBodyScrollLeft = 0; + + private final VerticalScrollbarBundle verticalScrollbar = new VerticalScrollbarBundle(); + private final HorizontalScrollbarBundle horizontalScrollbar = new HorizontalScrollbarBundle(); + + private final HeaderRowContainer header = new HeaderRowContainer(headElem); + private final BodyRowContainerImpl body = new BodyRowContainerImpl(bodyElem); + private final FooterRowContainer footer = new FooterRowContainer(footElem); + + private final Scroller scroller = new Scroller(); + + private final ColumnConfigurationImpl columnConfiguration = new ColumnConfigurationImpl(); + private final DivElement tableWrapper; + + private final DivElement horizontalScrollbarDeco = DivElement.as(DOM + .createDiv()); + private final DivElement headerDeco = DivElement.as(DOM.createDiv()); + private final DivElement footerDeco = DivElement.as(DOM.createDiv()); + private final DivElement spacerDecoContainer = DivElement.as(DOM + .createDiv()); + + private PositionFunction position; + + /** The cached width of the escalator, in pixels. */ + private double widthOfEscalator = 0; + /** The cached height of the escalator, in pixels. */ + private double heightOfEscalator = 0; + + /** The height of Escalator in terms of body rows. */ + private double heightByRows = 10.0d; + + /** The height of Escalator, as defined by {@link #setHeight(String)} */ + private String heightByCss = ""; + + private HeightMode heightMode = HeightMode.CSS; + + private boolean layoutIsScheduled = false; + private ScheduledCommand layoutCommand = new ScheduledCommand() { + @Override + public void execute() { + recalculateElementSizes(); + layoutIsScheduled = false; + } + }; + + private final ElementPositionBookkeeper positions = new ElementPositionBookkeeper(); + + /** + * Creates a new Escalator widget instance. + */ + public Escalator() { + + detectAndApplyPositionFunction(); + getLogger().info( + "Using " + position.getClass().getSimpleName() + + " for position"); + + final Element root = DOM.createDiv(); + setElement(root); + + setupScrollbars(root); + + tableWrapper = DivElement.as(DOM.createDiv()); + + root.appendChild(tableWrapper); + + final Element table = DOM.createTable(); + tableWrapper.appendChild(table); + + table.appendChild(headElem); + table.appendChild(bodyElem); + table.appendChild(footElem); + + Style hCornerStyle = headerDeco.getStyle(); + hCornerStyle.setWidth(verticalScrollbar.getScrollbarThickness(), + Unit.PX); + hCornerStyle.setDisplay(Display.NONE); + root.appendChild(headerDeco); + + Style fCornerStyle = footerDeco.getStyle(); + fCornerStyle.setWidth(verticalScrollbar.getScrollbarThickness(), + Unit.PX); + fCornerStyle.setDisplay(Display.NONE); + root.appendChild(footerDeco); + + Style hWrapperStyle = horizontalScrollbarDeco.getStyle(); + hWrapperStyle.setDisplay(Display.NONE); + hWrapperStyle.setHeight(horizontalScrollbar.getScrollbarThickness(), + Unit.PX); + root.appendChild(horizontalScrollbarDeco); + + setStylePrimaryName("v-escalator"); + + spacerDecoContainer.setAttribute("aria-hidden", "true"); + + // init default dimensions + setHeight(null); + setWidth(null); + } + + private void setupScrollbars(final Element root) { + + ScrollHandler scrollHandler = new ScrollHandler() { + @Override + public void onScroll(ScrollEvent event) { + scroller.onScroll(); + fireEvent(new ScrollEvent()); + } + }; + + int scrollbarThickness = WidgetUtil.getNativeScrollbarSize(); + if (BrowserInfo.get().isIE()) { + /* + * IE refuses to scroll properly if the DIV isn't at least one pixel + * larger than the scrollbar controls themselves. But, probably + * because of subpixel rendering, in Grid, one pixel isn't enough, + * so we'll add two instead. + */ + if (BrowserInfo.get().isIE9()) { + scrollbarThickness += 2; + } else { + scrollbarThickness += 1; + } + } + + root.appendChild(verticalScrollbar.getElement()); + verticalScrollbar.addScrollHandler(scrollHandler); + verticalScrollbar.setScrollbarThickness(scrollbarThickness); + + if (BrowserInfo.get().isIE8()) { + /* + * IE8 will have to compensate for a misalignment where it pops the + * scrollbar outside of its box. See Bug 3 in + * http://edskes.net/ie/ie8overflowandexpandingboxbugs.htm + */ + Style vScrollStyle = verticalScrollbar.getElement().getStyle(); + vScrollStyle.setRight( + verticalScrollbar.getScrollbarThickness() - 1, Unit.PX); + } + + root.appendChild(horizontalScrollbar.getElement()); + horizontalScrollbar.addScrollHandler(scrollHandler); + horizontalScrollbar.setScrollbarThickness(scrollbarThickness); + horizontalScrollbar + .addVisibilityHandler(new ScrollbarBundle.VisibilityHandler() { + + private boolean queued = false; + + @Override + public void visibilityChanged( + ScrollbarBundle.VisibilityChangeEvent event) { + if (queued) { + return; + } + queued = true; + + /* + * We either lost or gained a scrollbar. In any case, we + * need to change the height, if it's defined by rows. + */ + Scheduler.get().scheduleFinally(new ScheduledCommand() { + + @Override + public void execute() { + applyHeightByRows(); + queued = false; + } + }); + } + }); + + /* + * Because of all the IE hacks we've done above, we now have scrollbars + * hiding underneath a lot of DOM elements. + * + * This leads to problems with OSX (and many touch-only devices) when + * scrollbars are only shown when scrolling, as the scrollbar elements + * are hidden underneath everything. We trust that the scrollbars behave + * properly in these situations and simply pop them out with a bit of + * z-indexing. + */ + if (WidgetUtil.getNativeScrollbarSize() == 0) { + verticalScrollbar.getElement().getStyle().setZIndex(90); + horizontalScrollbar.getElement().getStyle().setZIndex(90); + } + } + + @Override + protected void onLoad() { + super.onLoad(); + + header.autodetectRowHeightLater(); + body.autodetectRowHeightLater(); + footer.autodetectRowHeightLater(); + + header.paintInsertRows(0, header.getRowCount()); + footer.paintInsertRows(0, footer.getRowCount()); + + // recalculateElementSizes(); + + Scheduler.get().scheduleDeferred(new Command() { + @Override + public void execute() { + /* + * Not a faintest idea why we have to defer this call, but + * unless it is deferred, the size of the escalator will be 0x0 + * after it is first detached and then reattached to the DOM. + * This only applies to a bare Escalator; inside a Grid + * everything works fine either way. + * + * The three autodetectRowHeightLater calls above seem obvious + * suspects at first. However, they don't seem to have anything + * to do with the issue, as they are no-ops in the + * detach-reattach case. + */ + recalculateElementSizes(); + } + }); + + /* + * Note: There's no need to explicitly insert rows into the body. + * + * recalculateElementSizes will recalculate the height of the body. This + * has the side-effect that as the body's size grows bigger (i.e. from 0 + * to its actual height), more escalator rows are populated. Those + * escalator rows are then immediately rendered. This, in effect, is the + * same thing as inserting those rows. + * + * In fact, having an extra paintInsertRows here would lead to duplicate + * rows. + */ + + boolean columnsChanged = false; + for (ColumnConfigurationImpl.Column column : columnConfiguration.columns) { + boolean columnChanged = column.measureAndSetWidthIfNeeded(); + if (columnChanged) { + columnsChanged = true; + } + } + if (columnsChanged) { + header.reapplyColumnWidths(); + body.reapplyColumnWidths(); + footer.reapplyColumnWidths(); + } + + verticalScrollbar.onLoad(); + horizontalScrollbar.onLoad(); + + scroller.attachScrollListener(verticalScrollbar.getElement()); + scroller.attachScrollListener(horizontalScrollbar.getElement()); + scroller.attachMousewheelListener(getElement()); + scroller.attachTouchListeners(getElement()); + } + + @Override + protected void onUnload() { + + scroller.detachScrollListener(verticalScrollbar.getElement()); + scroller.detachScrollListener(horizontalScrollbar.getElement()); + scroller.detachMousewheelListener(getElement()); + scroller.detachTouchListeners(getElement()); + + /* + * We can call paintRemoveRows here, because static ranges are simple to + * remove. + */ + header.paintRemoveRows(0, header.getRowCount()); + footer.paintRemoveRows(0, footer.getRowCount()); + + /* + * We can't call body.paintRemoveRows since it relies on rowCount to be + * updated correctly. Since it isn't, we'll simply and brutally rip out + * the DOM elements (in an elegant way, of course). + */ + int rowsToRemove = body.getDomRowCount(); + for (int i = 0; i < rowsToRemove; i++) { + int index = rowsToRemove - i - 1; + TableRowElement tr = bodyElem.getRows().getItem(index); + body.paintRemoveRow(tr, index); + positions.remove(tr); + } + body.visualRowOrder.clear(); + body.setTopRowLogicalIndex(0); + + super.onUnload(); + } + + private void detectAndApplyPositionFunction() { + /* + * firefox has a bug in its translate operation, showing white space + * when adjusting the scrollbar in BodyRowContainer.paintInsertRows + */ + if (Window.Navigator.getUserAgent().contains("Firefox")) { + position = new AbsolutePosition(); + return; + } + + final Style docStyle = Document.get().getBody().getStyle(); + if (hasProperty(docStyle, "transform")) { + if (hasProperty(docStyle, "transformStyle")) { + position = new Translate3DPosition(); + } else { + position = new TranslatePosition(); + } + } else if (hasProperty(docStyle, "webkitTransform")) { + position = new WebkitTranslate3DPosition(); + } else { + position = new AbsolutePosition(); + } + } + + private Logger getLogger() { + return Logger.getLogger(getClass().getName()); + } + + private static native boolean hasProperty(Style style, String name) + /*-{ + return style[name] !== undefined; + }-*/; + + /** + * Check whether there are both columns and any row data (for either + * headers, body or footer). + * + * @return true iff header, body or footer has rows && there + * are columns + */ + private boolean hasColumnAndRowData() { + return (header.getRowCount() > 0 || body.getRowCount() > 0 || footer + .getRowCount() > 0) && columnConfiguration.getColumnCount() > 0; + } + + /** + * Check whether there are any cells in the DOM. + * + * @return true iff header, body or footer has any child + * elements + */ + private boolean hasSomethingInDom() { + return headElem.hasChildNodes() || bodyElem.hasChildNodes() + || footElem.hasChildNodes(); + } + + /** + * Returns the row container for the header in this Escalator. + * + * @return the header. Never null + */ + public RowContainer getHeader() { + return header; + } + + /** + * Returns the row container for the body in this Escalator. + * + * @return the body. Never null + */ + public BodyRowContainer getBody() { + return body; + } + + /** + * Returns the row container for the footer in this Escalator. + * + * @return the footer. Never null + */ + public RowContainer getFooter() { + return footer; + } + + /** + * Returns the configuration object for the columns in this Escalator. + * + * @return the configuration object for the columns in this Escalator. Never + * null + */ + public ColumnConfiguration getColumnConfiguration() { + return columnConfiguration; + } + + @Override + public void setWidth(final String width) { + if (width != null && !width.isEmpty()) { + super.setWidth(width); + } else { + super.setWidth(DEFAULT_WIDTH); + } + + recalculateElementSizes(); + } + + /** + * {@inheritDoc} + *

+ * If Escalator is currently not in {@link HeightMode#CSS}, the given value + * is remembered, and applied once the mode is applied. + * + * @see #setHeightMode(HeightMode) + */ + @Override + public void setHeight(String height) { + /* + * TODO remove method once RequiresResize and the Vaadin layoutmanager + * listening mechanisms are implemented + */ + + if (height != null && !height.isEmpty()) { + heightByCss = height; + } else { + heightByCss = DEFAULT_HEIGHT; + } + + if (getHeightMode() == HeightMode.CSS) { + setHeightInternal(height); + } + } + + private void setHeightInternal(final String height) { + final int escalatorRowsBefore = body.visualRowOrder.size(); + + if (height != null && !height.isEmpty()) { + super.setHeight(height); + } else { + super.setHeight(DEFAULT_HEIGHT); + } + + recalculateElementSizes(); + + if (escalatorRowsBefore != body.visualRowOrder.size()) { + fireRowVisibilityChangeEvent(); + } + } + + /** + * Returns the vertical scroll offset. Note that this is not necessarily the + * same as the {@code scrollTop} attribute in the DOM. + * + * @return the logical vertical scroll offset + */ + public double getScrollTop() { + return verticalScrollbar.getScrollPos(); + } + + /** + * Sets the vertical scroll offset. Note that this will not necessarily + * become the same as the {@code scrollTop} attribute in the DOM. + * + * @param scrollTop + * the number of pixels to scroll vertically + */ + public void setScrollTop(final double scrollTop) { + verticalScrollbar.setScrollPos(scrollTop); + } + + /** + * Returns the logical horizontal scroll offset. Note that this is not + * necessarily the same as the {@code scrollLeft} attribute in the DOM. + * + * @return the logical horizontal scroll offset + */ + public double getScrollLeft() { + return horizontalScrollbar.getScrollPos(); + } + + /** + * Sets the logical horizontal scroll offset. Note that will not necessarily + * become the same as the {@code scrollLeft} attribute in the DOM. + * + * @param scrollLeft + * the number of pixels to scroll horizontally + */ + public void setScrollLeft(final double scrollLeft) { + horizontalScrollbar.setScrollPos(scrollLeft); + } + + /** + * Returns the scroll width for the escalator. Note that this is not + * necessary the same as {@code Element.scrollWidth} in the DOM. + * + * @since 7.5.0 + * @return the scroll width in pixels + */ + public double getScrollWidth() { + return horizontalScrollbar.getScrollSize(); + } + + /** + * Returns the scroll height for the escalator. Note that this is not + * necessary the same as {@code Element.scrollHeight} in the DOM. + * + * @since 7.5.0 + * @return the scroll height in pixels + */ + public double getScrollHeight() { + return verticalScrollbar.getScrollSize(); + } + + /** + * Scrolls the body horizontally so that the column at the given index is + * visible and there is at least {@code padding} pixels in the direction of + * the given scroll destination. + * + * @param columnIndex + * the index of the column to scroll to + * @param destination + * where the column should be aligned visually after scrolling + * @param padding + * the number pixels to place between the scrolled-to column and + * the viewport edge. + * @throws IndexOutOfBoundsException + * if {@code columnIndex} is not a valid index for an existing + * column + * @throws IllegalArgumentException + * if {@code destination} is {@link ScrollDestination#MIDDLE} + * and padding is nonzero; or if the indicated column is frozen; + * or if {@code destination == null} + */ + public void scrollToColumn(final int columnIndex, + final ScrollDestination destination, final int padding) + throws IndexOutOfBoundsException, IllegalArgumentException { + validateScrollDestination(destination, padding); + verifyValidColumnIndex(columnIndex); + + if (columnIndex < columnConfiguration.frozenColumns) { + throw new IllegalArgumentException("The given column index " + + columnIndex + " is frozen."); + } + + scroller.scrollToColumn(columnIndex, destination, padding); + } + + private void verifyValidColumnIndex(final int columnIndex) + throws IndexOutOfBoundsException { + if (columnIndex < 0 + || columnIndex >= columnConfiguration.getColumnCount()) { + throw new IndexOutOfBoundsException("The given column index " + + columnIndex + " does not exist."); + } + } + + /** + * Scrolls the body vertically so that the row at the given index is visible + * and there is at least {@literal padding} pixels to the given scroll + * destination. + * + * @param rowIndex + * the index of the logical row to scroll to + * @param destination + * where the row should be aligned visually after scrolling + * @param padding + * the number pixels to place between the scrolled-to row and the + * viewport edge. + * @throws IndexOutOfBoundsException + * if {@code rowIndex} is not a valid index for an existing row + * @throws IllegalArgumentException + * if {@code destination} is {@link ScrollDestination#MIDDLE} + * and padding is nonzero; or if {@code destination == null} + * @see #scrollToRowAndSpacer(int, ScrollDestination, int) + * @see #scrollToSpacer(int, ScrollDestination, int) + */ + public void scrollToRow(final int rowIndex, + final ScrollDestination destination, final int padding) + throws IndexOutOfBoundsException, IllegalArgumentException { + Scheduler.get().scheduleFinally(new ScheduledCommand() { + @Override + public void execute() { + validateScrollDestination(destination, padding); + verifyValidRowIndex(rowIndex); + scroller.scrollToRow(rowIndex, destination, padding); + } + }); + } + + private void verifyValidRowIndex(final int rowIndex) { + if (rowIndex < 0 || rowIndex >= body.getRowCount()) { + throw new IndexOutOfBoundsException("The given row index " + + rowIndex + " does not exist."); + } + } + + /** + * Scrolls the body vertically so that the spacer at the given row index is + * visible and there is at least {@literal padding} pixesl to the given + * scroll destination. + * + * @since 7.5.0 + * @param spacerIndex + * the row index of the spacer to scroll to + * @param destination + * where the spacer should be aligned visually after scrolling + * @param padding + * the number of pixels to place between the scrolled-to spacer + * and the viewport edge + * @throws IllegalArgumentException + * if {@code spacerIndex} is not an opened spacer; or if + * {@code destination} is {@link ScrollDestination#MIDDLE} and + * padding is nonzero; or if {@code destination == null} + * @see #scrollToRow(int, ScrollDestination, int) + * @see #scrollToRowAndSpacer(int, ScrollDestination, int) + */ + public void scrollToSpacer(final int spacerIndex, + ScrollDestination destination, final int padding) + throws IllegalArgumentException { + validateScrollDestination(destination, padding); + body.scrollToSpacer(spacerIndex, destination, padding); + } + + /** + * Scrolls vertically to a row and the spacer below it. + *

+ * If a spacer is not open at that index, this method behaves like + * {@link #scrollToRow(int, ScrollDestination, int)} + * + * @since 7.5.0 + * @param rowIndex + * the index of the logical row to scroll to. -1 takes the + * topmost spacer into account as well. + * @param destination + * where the row should be aligned visually after scrolling + * @param padding + * the number pixels to place between the scrolled-to row and the + * viewport edge. + * @see #scrollToRow(int, ScrollDestination, int) + * @see #scrollToSpacer(int, ScrollDestination, int) + * @throws IllegalArgumentException + * if {@code destination} is {@link ScrollDestination#MIDDLE} + * and {@code padding} is not zero; or if {@code rowIndex} is + * not a valid row index, or -1; or if + * {@code destination == null}; or if {@code rowIndex == -1} and + * there is no spacer open at that index. + */ + public void scrollToRowAndSpacer(final int rowIndex, + final ScrollDestination destination, final int padding) + throws IllegalArgumentException { + Scheduler.get().scheduleFinally(new ScheduledCommand() { + @Override + public void execute() { + validateScrollDestination(destination, padding); + if (rowIndex != -1) { + verifyValidRowIndex(rowIndex); + } + + // row range + final Range rowRange; + if (rowIndex != -1) { + int rowTop = (int) Math.floor(body.getRowTop(rowIndex)); + int rowHeight = (int) Math.ceil(body.getDefaultRowHeight()); + rowRange = Range.withLength(rowTop, rowHeight); + } else { + rowRange = Range.withLength(0, 0); + } + + // get spacer + final SpacerContainer.SpacerImpl spacer = body.spacerContainer + .getSpacer(rowIndex); + + if (rowIndex == -1 && spacer == null) { + throw new IllegalArgumentException( + "Cannot scroll to row index " + + "-1, as there is no spacer open at that index."); + } + + // make into target range + final Range targetRange; + if (spacer != null) { + final int spacerTop = (int) Math.floor(spacer.getTop()); + final int spacerHeight = (int) Math.ceil(spacer.getHeight()); + Range spacerRange = Range.withLength(spacerTop, + spacerHeight); + + targetRange = rowRange.combineWith(spacerRange); + } else { + targetRange = rowRange; + } + + // get params + int targetStart = targetRange.getStart(); + int targetEnd = targetRange.getEnd(); + double viewportStart = getScrollTop(); + double viewportEnd = viewportStart + body.getHeightOfSection(); + + double scrollPos = getScrollPos(destination, targetStart, + targetEnd, viewportStart, viewportEnd, padding); + + setScrollTop(scrollPos); + } + }); + } + + private static void validateScrollDestination( + final ScrollDestination destination, final int padding) { + if (destination == null) { + throw new IllegalArgumentException("Destination cannot be null"); + } + + if (destination == ScrollDestination.MIDDLE && padding != 0) { + throw new IllegalArgumentException( + "You cannot have a padding with a MIDDLE destination"); + } + } + + /** + * Recalculates the dimensions for all elements that require manual + * calculations. Also updates the dimension caches. + *

+ * Note: This method has the side-effect + * automatically makes sure that an appropriate amount of escalator rows are + * present. So, if the body area grows, more escalator rows might be + * inserted. Conversely, if the body area shrinks, + * escalator rows might be removed. + */ + private void recalculateElementSizes() { + if (!isAttached()) { + return; + } + + Profiler.enter("Escalator.recalculateElementSizes"); + widthOfEscalator = Math.max(0, WidgetUtil + .getRequiredWidthBoundingClientRectDouble(getElement())); + heightOfEscalator = Math.max(0, WidgetUtil + .getRequiredHeightBoundingClientRectDouble(getElement())); + + header.recalculateSectionHeight(); + body.recalculateSectionHeight(); + footer.recalculateSectionHeight(); + + scroller.recalculateScrollbarsForVirtualViewport(); + body.verifyEscalatorCount(); + body.reapplySpacerWidths(); + Profiler.leave("Escalator.recalculateElementSizes"); + } + + /** + * Snap deltas of x and y to the major four axes (up, down, left, right) + * with a threshold of a number of degrees from those axes. + * + * @param deltaX + * the delta in the x axis + * @param deltaY + * the delta in the y axis + * @param thresholdRatio + * the threshold in ratio (0..1) between x and y for when to snap + * @return a two-element array: [snappedX, snappedY] + */ + private static double[] snapDeltas(final double deltaX, + final double deltaY, final double thresholdRatio) { + + final double[] array = new double[2]; + if (deltaX != 0 && deltaY != 0) { + final double aDeltaX = Math.abs(deltaX); + final double aDeltaY = Math.abs(deltaY); + final double yRatio = aDeltaY / aDeltaX; + final double xRatio = aDeltaX / aDeltaY; + + array[0] = (xRatio < thresholdRatio) ? 0 : deltaX; + array[1] = (yRatio < thresholdRatio) ? 0 : deltaY; + } else { + array[0] = deltaX; + array[1] = deltaY; + } + + return array; + } + + /** + * Adds an event handler that gets notified when the range of visible rows + * changes e.g. because of scrolling, row resizing or spacers + * appearing/disappearing. + * + * @param rowVisibilityChangeHandler + * the event handler + * @return a handler registration for the added handler + */ + public HandlerRegistration addRowVisibilityChangeHandler( + RowVisibilityChangeHandler rowVisibilityChangeHandler) { + return addHandler(rowVisibilityChangeHandler, + RowVisibilityChangeEvent.TYPE); + } + + private void fireRowVisibilityChangeEvent() { + if (!body.visualRowOrder.isEmpty()) { + int visibleRangeStart = body.getLogicalRowIndex(body.visualRowOrder + .getFirst()); + int visibleRangeEnd = body.getLogicalRowIndex(body.visualRowOrder + .getLast()) + 1; + + int visibleRowCount = visibleRangeEnd - visibleRangeStart; + fireEvent(new RowVisibilityChangeEvent(visibleRangeStart, + visibleRowCount)); + } else { + fireEvent(new RowVisibilityChangeEvent(0, 0)); + } + } + + /** + * Gets the logical index range of currently visible rows. + * + * @return logical index range of visible rows + */ + public Range getVisibleRowRange() { + if (!body.visualRowOrder.isEmpty()) { + return Range.withLength(body.getTopRowLogicalIndex(), + body.visualRowOrder.size()); + } else { + return Range.withLength(0, 0); + } + } + + /** + * Returns the widget from a cell node or null if there is no + * widget in the cell + * + * @param cellNode + * The cell node + */ + static Widget getWidgetFromCell(Node cellNode) { + Node possibleWidgetNode = cellNode.getFirstChild(); + if (possibleWidgetNode != null + && possibleWidgetNode.getNodeType() == Node.ELEMENT_NODE) { + @SuppressWarnings("deprecation") + com.google.gwt.user.client.Element castElement = (com.google.gwt.user.client.Element) possibleWidgetNode + .cast(); + Widget w = WidgetUtil.findWidget(castElement, null); + + // Ensure findWidget did not traverse past the cell element in the + // DOM hierarchy + if (cellNode.isOrHasChild(w.getElement())) { + return w; + } + } + return null; + } + + @Override + public void setStylePrimaryName(String style) { + super.setStylePrimaryName(style); + + verticalScrollbar.setStylePrimaryName(style); + horizontalScrollbar.setStylePrimaryName(style); + + UIObject.setStylePrimaryName(tableWrapper, style + "-tablewrapper"); + UIObject.setStylePrimaryName(headerDeco, style + "-header-deco"); + UIObject.setStylePrimaryName(footerDeco, style + "-footer-deco"); + UIObject.setStylePrimaryName(horizontalScrollbarDeco, style + + "-horizontal-scrollbar-deco"); + UIObject.setStylePrimaryName(spacerDecoContainer, style + + "-spacer-deco-container"); + + header.setStylePrimaryName(style); + body.setStylePrimaryName(style); + footer.setStylePrimaryName(style); + } + + /** + * Sets the number of rows that should be visible in Escalator's body, while + * {@link #getHeightMode()} is {@link HeightMode#ROW}. + *

+ * If Escalator is currently not in {@link HeightMode#ROW}, the given value + * is remembered, and applied once the mode is applied. + * + * @param rows + * the number of rows that should be visible in Escalator's body + * @throws IllegalArgumentException + * if {@code rows} is ≤ 0, + * {@link Double#isInifinite(double) infinite} or + * {@link Double#isNaN(double) NaN}. + * @see #setHeightMode(HeightMode) + */ + public void setHeightByRows(double rows) throws IllegalArgumentException { + if (rows <= 0) { + throw new IllegalArgumentException( + "The number of rows must be a positive number."); + } else if (Double.isInfinite(rows)) { + throw new IllegalArgumentException( + "The number of rows must be finite."); + } else if (Double.isNaN(rows)) { + throw new IllegalArgumentException("The number must not be NaN."); + } + + heightByRows = rows; + applyHeightByRows(); + } + + /** + * Gets the amount of rows in Escalator's body that are shown, while + * {@link #getHeightMode()} is {@link HeightMode#ROW}. + *

+ * By default, it is 10. + * + * @return the amount of rows that are being shown in Escalator's body + * @see #setHeightByRows(double) + */ + public double getHeightByRows() { + return heightByRows; + } + + /** + * Reapplies the row-based height of the Grid, if Grid currently should + * define its height that way. + */ + private void applyHeightByRows() { + if (heightMode != HeightMode.ROW) { + return; + } + + double headerHeight = header.getHeightOfSection(); + double footerHeight = footer.getHeightOfSection(); + double bodyHeight = body.getDefaultRowHeight() * heightByRows; + double scrollbar = horizontalScrollbar.showsScrollHandle() ? horizontalScrollbar + .getScrollbarThickness() : 0; + + double totalHeight = headerHeight + bodyHeight + scrollbar + + footerHeight; + setHeightInternal(totalHeight + "px"); + } + + /** + * Defines the mode in which the Escalator widget's height is calculated. + *

+ * If {@link HeightMode#CSS} is given, Escalator will respect the values + * given via {@link #setHeight(String)}, and behave as a traditional Widget. + *

+ * If {@link HeightMode#ROW} is given, Escalator will make sure that the + * {@link #getBody() body} will display as many rows as + * {@link #getHeightByRows()} defines. Note: If headers/footers are + * inserted or removed, the widget will resize itself to still display the + * required amount of rows in its body. It also takes the horizontal + * scrollbar into account. + * + * @param heightMode + * the mode in to which Escalator should be set + */ + public void setHeightMode(HeightMode heightMode) { + /* + * This method is a workaround for the fact that Vaadin re-applies + * widget dimensions (height/width) on each state change event. The + * original design was to have setHeight an setHeightByRow be equals, + * and whichever was called the latest was considered in effect. + * + * But, because of Vaadin always calling setHeight on the widget, this + * approach doesn't work. + */ + + if (heightMode != this.heightMode) { + this.heightMode = heightMode; + + switch (this.heightMode) { + case CSS: + setHeight(heightByCss); + break; + case ROW: + setHeightByRows(heightByRows); + break; + default: + throw new IllegalStateException("Unimplemented feature " + + "- unknown HeightMode: " + this.heightMode); + } + } + } + + /** + * Returns the current {@link HeightMode} the Escalator is in. + *

+ * Defaults to {@link HeightMode#CSS}. + * + * @return the current HeightMode + */ + public HeightMode getHeightMode() { + return heightMode; + } + + /** + * Returns the {@link RowContainer} which contains the element. + * + * @param element + * the element to check for + * @return the container the element is in or null if element + * is not present in any container. + */ + public RowContainer findRowContainer(Element element) { + if (getHeader().getElement() != element + && getHeader().getElement().isOrHasChild(element)) { + return getHeader(); + } else if (getBody().getElement() != element + && getBody().getElement().isOrHasChild(element)) { + return getBody(); + } else if (getFooter().getElement() != element + && getFooter().getElement().isOrHasChild(element)) { + return getFooter(); + } + return null; + } + + /** + * Sets whether a scroll direction is locked or not. + *

+ * If a direction is locked, the escalator will refuse to scroll in that + * direction. + * + * @param direction + * the orientation of the scroll to set the lock status + * @param locked + * true to lock, false to unlock + */ + public void setScrollLocked(ScrollbarBundle.Direction direction, + boolean locked) { + switch (direction) { + case HORIZONTAL: + horizontalScrollbar.setLocked(locked); + break; + case VERTICAL: + verticalScrollbar.setLocked(locked); + break; + default: + throw new UnsupportedOperationException("Unexpected value: " + + direction); + } + } + + /** + * Checks whether or not an direction is locked for scrolling. + * + * @param direction + * the direction of the scroll of which to check the lock status + * @return true iff the direction is locked + */ + public boolean isScrollLocked(ScrollbarBundle.Direction direction) { + switch (direction) { + case HORIZONTAL: + return horizontalScrollbar.isLocked(); + case VERTICAL: + return verticalScrollbar.isLocked(); + default: + throw new UnsupportedOperationException("Unexpected value: " + + direction); + } + } + + /** + * Adds a scroll handler to this escalator + * + * @param handler + * the scroll handler to add + * @return a handler registration for the registered scroll handler + */ + public HandlerRegistration addScrollHandler(ScrollHandler handler) { + return addHandler(handler, ScrollEvent.TYPE); + } + + @Override + public boolean isWorkPending() { + return body.domSorter.waiting || verticalScrollbar.isWorkPending() + || horizontalScrollbar.isWorkPending() || layoutIsScheduled; + } + + @Override + public void onResize() { + if (isAttached() && !layoutIsScheduled) { + layoutIsScheduled = true; + Scheduler.get().scheduleFinally(layoutCommand); + } + } + + /** + * Gets the maximum number of body rows that can be visible on the screen at + * once. + * + * @return the maximum capacity + */ + public int getMaxVisibleRowCount() { + return body.getMaxEscalatorRowCapacity(); + } + + /** + * Gets the escalator's inner width. This is the entire width in pixels, + * without the vertical scrollbar. + * + * @return escalator's inner width + */ + public double getInnerWidth() { + return WidgetUtil + .getRequiredWidthBoundingClientRectDouble(tableWrapper); + } + + /** + * Resets all cached pixel sizes and reads new values from the DOM. This + * methods should be used e.g. when styles affecting the dimensions of + * elements in this escalator have been changed. + */ + public void resetSizesFromDom() { + header.autodetectRowHeightNow(); + body.autodetectRowHeightNow(); + footer.autodetectRowHeightNow(); + + for (int i = 0; i < columnConfiguration.getColumnCount(); i++) { + columnConfiguration.setColumnWidth(i, + columnConfiguration.getColumnWidth(i)); + } + } + + private Range getViewportPixels() { + int from = (int) Math.floor(verticalScrollbar.getScrollPos()); + int to = (int) body.getHeightOfSection(); + return Range.withLength(from, to); + } + + @Override + @SuppressWarnings("deprecation") + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + SubPartArguments args = SubPartArguments.create(subPart); + + Element tableStructureElement = getSubPartElementTableStructure(args); + if (tableStructureElement != null) { + return DOM.asOld(tableStructureElement); + } + + Element spacerElement = getSubPartElementSpacer(args); + if (spacerElement != null) { + return DOM.asOld(spacerElement); + } + + return null; + } + + private Element getSubPartElementTableStructure(SubPartArguments args) { + + String type = args.getType(); + int[] indices = args.getIndices(); + + // Get correct RowContainer for type from Escalator + RowContainer container = null; + if (type.equalsIgnoreCase("header")) { + container = getHeader(); + } else if (type.equalsIgnoreCase("cell")) { + // If wanted row is not visible, we need to scroll there. + Range visibleRowRange = getVisibleRowRange(); + if (indices.length > 0 && !visibleRowRange.contains(indices[0])) { + try { + scrollToRow(indices[0], ScrollDestination.ANY, 0); + } catch (IllegalArgumentException e) { + getLogger().log(Level.SEVERE, e.getMessage()); + } + // Scrolling causes a lazy loading event. No element can + // currently be retrieved. + return null; + } + container = getBody(); + } else if (type.equalsIgnoreCase("footer")) { + container = getFooter(); + } + + if (null != container) { + if (indices.length == 0) { + // No indexing. Just return the wanted container element + return container.getElement(); + } else { + try { + return getSubPart(container, indices); + } catch (Exception e) { + getLogger().log(Level.SEVERE, e.getMessage()); + } + } + } + return null; + } + + private Element getSubPart(RowContainer container, int[] indices) { + Element targetElement = container.getRowElement(indices[0]); + + // Scroll wanted column to view if able + if (indices.length > 1 && targetElement != null) { + if (getColumnConfiguration().getFrozenColumnCount() <= indices[1]) { + scrollToColumn(indices[1], ScrollDestination.ANY, 0); + } + + targetElement = getCellFromRow(TableRowElement.as(targetElement), + indices[1]); + + for (int i = 2; i < indices.length && targetElement != null; ++i) { + targetElement = (Element) targetElement.getChild(indices[i]); + } + } + + return targetElement; + } + + private static Element getCellFromRow(TableRowElement rowElement, int index) { + int childCount = rowElement.getCells().getLength(); + if (index < 0 || index >= childCount) { + return null; + } + + TableCellElement currentCell = null; + boolean indexInColspan = false; + int i = 0; + + while (!indexInColspan) { + currentCell = rowElement.getCells().getItem(i); + + // Calculate if this is the cell we are looking for + int colSpan = currentCell.getColSpan(); + indexInColspan = index < colSpan + i; + + // Increment by colspan to skip over hidden cells + i += colSpan; + } + return currentCell; + } + + private Element getSubPartElementSpacer(SubPartArguments args) { + if ("spacer".equals(args.getType()) && args.getIndicesLength() == 1) { + return body.spacerContainer.getSubPartElement(args.getIndex(0)); + } else { + return null; + } + } + + @Override + @SuppressWarnings("deprecation") + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + + /* + * The spacer check needs to be before table structure check, because + * (for now) the table structure will take spacer elements into account + * as well, when it shouldn't. + */ + + String spacer = getSubPartNameSpacer(subElement); + if (spacer != null) { + return spacer; + } + + String tableStructure = getSubPartNameTableStructure(subElement); + if (tableStructure != null) { + return tableStructure; + } + + return null; + } + + private String getSubPartNameTableStructure(Element subElement) { + + List containers = Arrays.asList(getHeader(), getBody(), + getFooter()); + List containerType = Arrays.asList("header", "cell", "footer"); + + for (int i = 0; i < containers.size(); ++i) { + RowContainer container = containers.get(i); + boolean containerRow = (subElement.getTagName().equalsIgnoreCase( + "tr") && subElement.getParentElement() == container + .getElement()); + if (containerRow) { + /* + * Wanted SubPart is row that is a child of containers root to + * get indices, we use a cell that is a child of this row + */ + subElement = subElement.getFirstChildElement(); + } + + Cell cell = container.getCell(subElement); + if (cell != null) { + // Skip the column index if subElement was a child of root + return containerType.get(i) + "[" + cell.getRow() + + (containerRow ? "]" : "][" + cell.getColumn() + "]"); + } + } + return null; + } + + private String getSubPartNameSpacer(Element subElement) { + return body.spacerContainer.getSubPartName(subElement); + } + + private void logWarning(String message) { + getLogger().warning(message); + } + + /** + * This is an internal method for calculating minimum width for Column + * resize. + * + * @return minimum width for column + */ + double getMinCellWidth(int colIndex) { + return columnConfiguration.getMinCellWidth(colIndex); + } +} diff --git a/client/src/main/java/com/vaadin/client/widgets/Grid.java b/client/src/main/java/com/vaadin/client/widgets/Grid.java new file mode 100644 index 0000000000..c4e3491992 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widgets/Grid.java @@ -0,0 +1,8877 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widgets; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.core.shared.GWT; +import com.google.gwt.dom.client.BrowserEvents; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.EventTarget; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.TableCellElement; +import com.google.gwt.dom.client.TableRowElement; +import com.google.gwt.dom.client.TableSectionElement; +import com.google.gwt.dom.client.Touch; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyEvent; +import com.google.gwt.event.dom.client.MouseEvent; +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.touch.client.Point; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Event.NativePreviewHandler; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HasEnabled; +import com.google.gwt.user.client.ui.HasWidgets; +import com.google.gwt.user.client.ui.MenuBar; +import com.google.gwt.user.client.ui.MenuItem; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.ResizeComposite; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.DeferredWorker; +import com.vaadin.client.Focusable; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.data.DataChangeHandler; +import com.vaadin.client.data.DataSource; +import com.vaadin.client.data.DataSource.RowHandle; +import com.vaadin.client.renderers.ComplexRenderer; +import com.vaadin.client.renderers.Renderer; +import com.vaadin.client.renderers.WidgetRenderer; +import com.vaadin.client.ui.FocusUtil; +import com.vaadin.client.ui.SubPartAware; +import com.vaadin.client.ui.dd.DragAndDropHandler; +import com.vaadin.client.ui.dd.DragAndDropHandler.DragAndDropCallback; +import com.vaadin.client.ui.dd.DragHandle; +import com.vaadin.client.ui.dd.DragHandle.DragHandleCallback; +import com.vaadin.client.widget.escalator.Cell; +import com.vaadin.client.widget.escalator.ColumnConfiguration; +import com.vaadin.client.widget.escalator.EscalatorUpdater; +import com.vaadin.client.widget.escalator.FlyweightCell; +import com.vaadin.client.widget.escalator.Row; +import com.vaadin.client.widget.escalator.RowContainer; +import com.vaadin.client.widget.escalator.RowVisibilityChangeEvent; +import com.vaadin.client.widget.escalator.RowVisibilityChangeHandler; +import com.vaadin.client.widget.escalator.ScrollbarBundle.Direction; +import com.vaadin.client.widget.escalator.Spacer; +import com.vaadin.client.widget.escalator.SpacerUpdater; +import com.vaadin.client.widget.grid.AutoScroller; +import com.vaadin.client.widget.grid.AutoScroller.AutoScrollerCallback; +import com.vaadin.client.widget.grid.AutoScroller.ScrollAxis; +import com.vaadin.client.widget.grid.CellReference; +import com.vaadin.client.widget.grid.CellStyleGenerator; +import com.vaadin.client.widget.grid.DataAvailableEvent; +import com.vaadin.client.widget.grid.DataAvailableHandler; +import com.vaadin.client.widget.grid.DefaultEditorEventHandler; +import com.vaadin.client.widget.grid.DetailsGenerator; +import com.vaadin.client.widget.grid.EditorHandler; +import com.vaadin.client.widget.grid.EditorHandler.EditorRequest; +import com.vaadin.client.widget.grid.EventCellReference; +import com.vaadin.client.widget.grid.HeightAwareDetailsGenerator; +import com.vaadin.client.widget.grid.RendererCellReference; +import com.vaadin.client.widget.grid.RowReference; +import com.vaadin.client.widget.grid.RowStyleGenerator; +import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler; +import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler; +import com.vaadin.client.widget.grid.events.BodyClickHandler; +import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler; +import com.vaadin.client.widget.grid.events.BodyKeyDownHandler; +import com.vaadin.client.widget.grid.events.BodyKeyPressHandler; +import com.vaadin.client.widget.grid.events.BodyKeyUpHandler; +import com.vaadin.client.widget.grid.events.ColumnReorderEvent; +import com.vaadin.client.widget.grid.events.ColumnReorderHandler; +import com.vaadin.client.widget.grid.events.ColumnResizeEvent; +import com.vaadin.client.widget.grid.events.ColumnResizeHandler; +import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent; +import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler; +import com.vaadin.client.widget.grid.events.FooterClickHandler; +import com.vaadin.client.widget.grid.events.FooterDoubleClickHandler; +import com.vaadin.client.widget.grid.events.FooterKeyDownHandler; +import com.vaadin.client.widget.grid.events.FooterKeyPressHandler; +import com.vaadin.client.widget.grid.events.FooterKeyUpHandler; +import com.vaadin.client.widget.grid.events.GridClickEvent; +import com.vaadin.client.widget.grid.events.GridDoubleClickEvent; +import com.vaadin.client.widget.grid.events.GridKeyDownEvent; +import com.vaadin.client.widget.grid.events.GridKeyPressEvent; +import com.vaadin.client.widget.grid.events.GridKeyUpEvent; +import com.vaadin.client.widget.grid.events.HeaderClickHandler; +import com.vaadin.client.widget.grid.events.HeaderDoubleClickHandler; +import com.vaadin.client.widget.grid.events.HeaderKeyDownHandler; +import com.vaadin.client.widget.grid.events.HeaderKeyPressHandler; +import com.vaadin.client.widget.grid.events.HeaderKeyUpHandler; +import com.vaadin.client.widget.grid.events.ScrollEvent; +import com.vaadin.client.widget.grid.events.ScrollHandler; +import com.vaadin.client.widget.grid.events.SelectAllEvent; +import com.vaadin.client.widget.grid.events.SelectAllHandler; +import com.vaadin.client.widget.grid.selection.HasSelectionHandlers; +import com.vaadin.client.widget.grid.selection.MultiSelectionRenderer; +import com.vaadin.client.widget.grid.selection.SelectionEvent; +import com.vaadin.client.widget.grid.selection.SelectionHandler; +import com.vaadin.client.widget.grid.selection.SelectionModel; +import com.vaadin.client.widget.grid.selection.SelectionModel.Multi; +import com.vaadin.client.widget.grid.selection.SelectionModel.Single; +import com.vaadin.client.widget.grid.selection.SelectionModelMulti; +import com.vaadin.client.widget.grid.selection.SelectionModelNone; +import com.vaadin.client.widget.grid.selection.SelectionModelSingle; +import com.vaadin.client.widget.grid.sort.Sort; +import com.vaadin.client.widget.grid.sort.SortEvent; +import com.vaadin.client.widget.grid.sort.SortHandler; +import com.vaadin.client.widget.grid.sort.SortOrder; +import com.vaadin.client.widgets.Escalator.AbstractRowContainer; +import com.vaadin.client.widgets.Escalator.SubPartArguments; +import com.vaadin.client.widgets.Grid.Editor.State; +import com.vaadin.client.widgets.Grid.StaticSection.StaticCell; +import com.vaadin.client.widgets.Grid.StaticSection.StaticRow; +import com.vaadin.shared.data.sort.SortDirection; +import com.vaadin.shared.ui.grid.GridConstants; +import com.vaadin.shared.ui.grid.GridConstants.Section; +import com.vaadin.shared.ui.grid.GridStaticCellType; +import com.vaadin.shared.ui.grid.HeightMode; +import com.vaadin.shared.ui.grid.Range; +import com.vaadin.shared.ui.grid.ScrollDestination; +import com.vaadin.shared.util.SharedUtil; + +/** + * A data grid view that supports columns and lazy loading of data rows from a + * data source. + * + *

Columns

+ *

+ * Each column in Grid is represented by a {@link Column}. Each + * {@code GridColumn} has a custom implementation for + * {@link Column#getValue(Object)} that gets the row object as an argument, and + * returns the value for that particular column, extracted from the row object. + *

+ * Each column also has a Renderer. Its function is to take the value that is + * given by the {@code GridColumn} and display it to the user. A simple column + * might have a {@link com.vaadin.client.renderers.TextRenderer TextRenderer} + * that simply takes in a {@code String} and displays it as the cell's content. + * A more complex renderer might be + * {@link com.vaadin.client.renderers.ProgressBarRenderer ProgressBarRenderer} + * that takes in a floating point number, and displays a progress bar instead, + * based on the given number. + *

+ * See: {@link #addColumn(Column)}, {@link #addColumn(Column, int)} and + * {@link #addColumns(Column...)}. Also + * {@link Column#setRenderer(Renderer)}. + * + *

Data Sources

+ *

+ * Grid gets its data from a {@link DataSource}, providing row objects to Grid + * from a user-defined endpoint. It can be either a local in-memory data source + * (e.g. {@link com.vaadin.client.widget.grid.datasources.ListDataSource + * ListDataSource}) or even a remote one, retrieving data from e.g. a REST API + * (see {@link com.vaadin.client.data.AbstractRemoteDataSource + * AbstractRemoteDataSource}). + * + * + * @param + * The row type of the grid. The row type is the POJO type from where + * the data is retrieved into the column cells. + * @since 7.4 + * @author Vaadin Ltd + */ +public class Grid extends ResizeComposite implements + HasSelectionHandlers, SubPartAware, DeferredWorker, Focusable, + com.google.gwt.user.client.ui.Focusable, HasWidgets, HasEnabled { + + private static final String STYLE_NAME = "v-grid"; + + private static final String SELECT_ALL_CHECKBOX_CLASSNAME = "-select-all-checkbox"; + + /** + * Abstract base class for Grid header and footer sections. + * + * @since 7.5.0 + * + * @param + * the type of the rows in the section + */ + public abstract static class StaticSection> { + + /** + * A header or footer cell. Has a simple textual caption. + * + */ + public static class StaticCell { + + private Object content = null; + + private int colspan = 1; + + private StaticSection section; + + private GridStaticCellType type = GridStaticCellType.TEXT; + + private String styleName = null; + + /** + * Sets the text displayed in this cell. + * + * @param text + * a plain text caption + */ + public void setText(String text) { + this.content = text; + this.type = GridStaticCellType.TEXT; + section.requestSectionRefresh(); + } + + /** + * Returns the text displayed in this cell. + * + * @return the plain text caption + */ + public String getText() { + if (type != GridStaticCellType.TEXT) { + throw new IllegalStateException( + "Cannot fetch Text from a cell with type " + type); + } + return (String) content; + } + + protected StaticSection getSection() { + assert section != null; + return section; + } + + protected void setSection(StaticSection section) { + this.section = section; + } + + /** + * Returns the amount of columns the cell spans. By default is 1. + * + * @return The amount of columns the cell spans. + */ + public int getColspan() { + return colspan; + } + + /** + * Sets the amount of columns the cell spans. Must be more or equal + * to 1. By default is 1. + * + * @param colspan + * the colspan to set + */ + public void setColspan(int colspan) { + if (colspan < 1) { + throw new IllegalArgumentException( + "Colspan cannot be less than 1"); + } + + this.colspan = colspan; + section.requestSectionRefresh(); + } + + /** + * Returns the html inside the cell. + * + * @throws IllegalStateException + * if trying to retrive HTML from a cell with a type + * other than {@link GridStaticCellType#HTML}. + * @return the html content of the cell. + */ + public String getHtml() { + if (type != GridStaticCellType.HTML) { + throw new IllegalStateException( + "Cannot fetch HTML from a cell with type " + type); + } + return (String) content; + } + + /** + * Sets the content of the cell to the provided html. All previous + * content is discarded and the cell type is set to + * {@link GridStaticCellType#HTML}. + * + * @param html + * The html content of the cell + */ + public void setHtml(String html) { + this.content = html; + this.type = GridStaticCellType.HTML; + section.requestSectionRefresh(); + } + + /** + * Returns the widget in the cell. + * + * @throws IllegalStateException + * if the cell is not {@link GridStaticCellType#WIDGET} + * + * @return the widget in the cell + */ + public Widget getWidget() { + if (type != GridStaticCellType.WIDGET) { + throw new IllegalStateException( + "Cannot fetch Widget from a cell with type " + type); + } + return (Widget) content; + } + + /** + * Set widget as the content of the cell. The type of the cell + * becomes {@link GridStaticCellType#WIDGET}. All previous content + * is discarded. + * + * @param widget + * The widget to add to the cell. Should not be + * previously attached anywhere (widget.getParent == + * null). + */ + public void setWidget(Widget widget) { + if (this.content == widget) { + return; + } + + if (this.content instanceof Widget) { + // Old widget in the cell, detach it first + section.getGrid().detachWidget((Widget) this.content); + } + this.content = widget; + this.type = GridStaticCellType.WIDGET; + section.requestSectionRefresh(); + } + + /** + * Returns the type of the cell. + * + * @return the type of content the cell contains. + */ + public GridStaticCellType getType() { + return type; + } + + /** + * Returns the custom style name for this cell. + * + * @return the style name or null if no style name has been set + */ + public String getStyleName() { + return styleName; + } + + /** + * Sets a custom style name for this cell. + * + * @param styleName + * the style name to set or null to not use any style + * name + */ + public void setStyleName(String styleName) { + this.styleName = styleName; + section.requestSectionRefresh(); + + } + + /** + * Called when the cell is detached from the row + * + * @since 7.6.3 + */ + void detach() { + if (this.content instanceof Widget) { + // Widget in the cell, detach it + section.getGrid().detachWidget((Widget) this.content); + } + } + } + + /** + * Abstract base class for Grid header and footer rows. + * + * @param + * the type of the cells in the row + */ + public abstract static class StaticRow { + + private Map, CELLTYPE> cells = new HashMap, CELLTYPE>(); + + private StaticSection section; + + /** + * Map from set of spanned columns to cell meta data. + */ + private Map>, CELLTYPE> cellGroups = new HashMap>, CELLTYPE>(); + + /** + * A custom style name for the row or null if none is set. + */ + private String styleName = null; + + /** + * Returns the cell on given GridColumn. If the column is merged + * returned cell is the cell for the whole group. + * + * @param column + * the column in grid + * @return the cell on given column, merged cell for merged columns, + * null if not found + */ + public CELLTYPE getCell(Column column) { + Set> cellGroup = getCellGroupForColumn(column); + if (cellGroup != null) { + return cellGroups.get(cellGroup); + } + return cells.get(column); + } + + /** + * Returns true if this row contains spanned cells. + * + * @since 7.5.0 + * @return does this row contain spanned cells + */ + public boolean hasSpannedCells() { + return !cellGroups.isEmpty(); + } + + /** + * Merges columns cells in a row + * + * @param columns + * the columns which header should be merged + * @return the remaining visible cell after the merge, or the cell + * on first column if all are hidden + */ + public CELLTYPE join(Column... columns) { + if (columns.length <= 1) { + throw new IllegalArgumentException( + "You can't merge less than 2 columns together."); + } + + HashSet> columnGroup = new HashSet>(); + // NOTE: this doesn't care about hidden columns, those are + // filtered in calculateColspans() + for (Column column : columns) { + if (!cells.containsKey(column)) { + throw new IllegalArgumentException( + "Given column does not exists on row " + column); + } else if (getCellGroupForColumn(column) != null) { + throw new IllegalStateException( + "Column is already in a group."); + } + columnGroup.add(column); + } + + CELLTYPE joinedCell = createCell(); + cellGroups.put(columnGroup, joinedCell); + joinedCell.setSection(getSection()); + + calculateColspans(); + + return joinedCell; + } + + /** + * Merges columns cells in a row + * + * @param cells + * The cells to merge. Must be from the same row. + * @return The remaining visible cell after the merge, or the first + * cell if all columns are hidden + */ + public CELLTYPE join(CELLTYPE... cells) { + if (cells.length <= 1) { + throw new IllegalArgumentException( + "You can't merge less than 2 cells together."); + } + + Column[] columns = new Column[cells.length]; + + int j = 0; + for (Column column : this.cells.keySet()) { + CELLTYPE cell = this.cells.get(column); + if (!this.cells.containsValue(cells[j])) { + throw new IllegalArgumentException( + "Given cell does not exists on row"); + } else if (cell.equals(cells[j])) { + columns[j++] = column; + if (j == cells.length) { + break; + } + } + } + + return join(columns); + } + + private Set> getCellGroupForColumn(Column column) { + for (Set> group : cellGroups.keySet()) { + if (group.contains(column)) { + return group; + } + } + return null; + } + + void calculateColspans() { + // Reset all cells + for (CELLTYPE cell : this.cells.values()) { + cell.setColspan(1); + } + // Set colspan for grouped cells + for (Set> group : cellGroups.keySet()) { + if (!checkMergedCellIsContinuous(group)) { + // on error simply break the merged cell + cellGroups.get(group).setColspan(1); + } else { + int colSpan = 0; + for (Column column : group) { + if (!column.isHidden()) { + colSpan++; + } + } + // colspan can't be 0 + cellGroups.get(group).setColspan(Math.max(1, colSpan)); + } + } + + } + + private boolean checkMergedCellIsContinuous( + Set> mergedCell) { + // no matter if hidden or not, just check for continuous order + final List> columnOrder = new ArrayList>( + section.grid.getColumns()); + + if (!columnOrder.containsAll(mergedCell)) { + return false; + } + + for (int i = 0; i < columnOrder.size(); ++i) { + if (!mergedCell.contains(columnOrder.get(i))) { + continue; + } + + for (int j = 1; j < mergedCell.size(); ++j) { + if (!mergedCell.contains(columnOrder.get(i + j))) { + return false; + } + } + return true; + } + return false; + } + + protected void addCell(Column column) { + CELLTYPE cell = createCell(); + cell.setSection(getSection()); + cells.put(column, cell); + } + + protected void removeCell(Column column) { + cells.remove(column); + } + + protected abstract CELLTYPE createCell(); + + protected StaticSection getSection() { + return section; + } + + protected void setSection(StaticSection section) { + this.section = section; + } + + /** + * Returns the custom style name for this row. + * + * @return the style name or null if no style name has been set + */ + public String getStyleName() { + return styleName; + } + + /** + * Sets a custom style name for this row. + * + * @param styleName + * the style name to set or null to not use any style + * name + */ + public void setStyleName(String styleName) { + this.styleName = styleName; + section.requestSectionRefresh(); + } + + /** + * Called when the row is detached from the grid + * + * @since 7.6.3 + */ + void detach() { + // Avoid calling detach twice for a merged cell + HashSet cells = new HashSet(); + for (Column column : getSection().grid.getColumns()) { + cells.add(getCell(column)); + } + for (CELLTYPE cell : cells) { + cell.detach(); + } + } + } + + private Grid grid; + + private List rows = new ArrayList(); + + private boolean visible = true; + + /** + * Creates and returns a new instance of the row type. + * + * @return the created row + */ + protected abstract ROWTYPE createRow(); + + /** + * Informs the grid that this section should be re-rendered. + *

+ * Note that re-render means calling update() on each cell, + * preAttach()/postAttach()/preDetach()/postDetach() is not called as + * the cells are not removed from the DOM. + */ + protected abstract void requestSectionRefresh(); + + /** + * Sets the visibility of the whole section. + * + * @param visible + * true to show this section, false to hide + */ + public void setVisible(boolean visible) { + this.visible = visible; + requestSectionRefresh(); + } + + /** + * Returns the visibility of this section. + * + * @return true if visible, false otherwise. + */ + public boolean isVisible() { + return visible; + } + + /** + * Inserts a new row at the given position. Shifts the row currently at + * that position and any subsequent rows down (adds one to their + * indices). + * + * @param index + * the position at which to insert the row + * @return the new row + * + * @throws IndexOutOfBoundsException + * if the index is out of bounds + * @see #appendRow() + * @see #prependRow() + * @see #removeRow(int) + * @see #removeRow(StaticRow) + */ + public ROWTYPE addRowAt(int index) { + ROWTYPE row = createRow(); + row.setSection(this); + for (int i = 0; i < getGrid().getColumnCount(); ++i) { + row.addCell(grid.getColumn(i)); + } + rows.add(index, row); + + requestSectionRefresh(); + return row; + } + + /** + * Adds a new row at the top of this section. + * + * @return the new row + * @see #appendRow() + * @see #addRowAt(int) + * @see #removeRow(int) + * @see #removeRow(StaticRow) + */ + public ROWTYPE prependRow() { + return addRowAt(0); + } + + /** + * Adds a new row at the bottom of this section. + * + * @return the new row + * @see #prependRow() + * @see #addRowAt(int) + * @see #removeRow(int) + * @see #removeRow(StaticRow) + */ + public ROWTYPE appendRow() { + return addRowAt(rows.size()); + } + + /** + * Removes the row at the given position. + * + * @param index + * the position of the row + * + * @throws IndexOutOfBoundsException + * if the index is out of bounds + * @see #addRowAt(int) + * @see #appendRow() + * @see #prependRow() + * @see #removeRow(StaticRow) + */ + public void removeRow(int index) { + ROWTYPE row = rows.remove(index); + row.detach(); + requestSectionRefresh(); + } + + /** + * Removes the given row from the section. + * + * @param row + * the row to be removed + * + * @throws IllegalArgumentException + * if the row does not exist in this section + * @see #addRowAt(int) + * @see #appendRow() + * @see #prependRow() + * @see #removeRow(int) + */ + public void removeRow(ROWTYPE row) { + try { + removeRow(rows.indexOf(row)); + } catch (IndexOutOfBoundsException e) { + throw new IllegalArgumentException( + "Section does not contain the given row"); + } + } + + /** + * Returns the row at the given position. + * + * @param index + * the position of the row + * @return the row with the given index + * + * @throws IndexOutOfBoundsException + * if the index is out of bounds + */ + public ROWTYPE getRow(int index) { + try { + return rows.get(index); + } catch (IndexOutOfBoundsException e) { + throw new IllegalArgumentException("Row with index " + index + + " does not exist"); + } + } + + /** + * Returns the number of rows in this section. + * + * @return the number of rows + */ + public int getRowCount() { + return rows.size(); + } + + protected List getRows() { + return rows; + } + + protected int getVisibleRowCount() { + return isVisible() ? getRowCount() : 0; + } + + protected void addColumn(Column column) { + for (ROWTYPE row : rows) { + row.addCell(column); + } + } + + protected void removeColumn(Column column) { + for (ROWTYPE row : rows) { + row.removeCell(column); + } + } + + protected void setGrid(Grid grid) { + this.grid = grid; + } + + protected Grid getGrid() { + assert grid != null; + return grid; + } + + protected void updateColSpans() { + for (ROWTYPE row : rows) { + if (row.hasSpannedCells()) { + row.calculateColspans(); + } + } + } + } + + /** + * Represents the header section of a Grid. A header consists of a single + * header row containing a header cell for each column. Each cell has a + * simple textual caption. + */ + protected static class Header extends StaticSection { + private HeaderRow defaultRow; + + private boolean markAsDirty = false; + + @Override + public void removeRow(int index) { + HeaderRow removedRow = getRow(index); + super.removeRow(index); + if (removedRow == defaultRow) { + setDefaultRow(null); + } + } + + /** + * Sets the default row of this header. The default row is a special + * header row providing a user interface for sorting columns. + * + * @param row + * the new default row, or null for no default row + * + * @throws IllegalArgumentException + * this header does not contain the row + */ + public void setDefaultRow(HeaderRow row) { + if (row == defaultRow) { + return; + } + if (row != null && !getRows().contains(row)) { + throw new IllegalArgumentException( + "Cannot set a default row that does not exist in the container"); + } + if (defaultRow != null) { + defaultRow.setDefault(false); + } + if (row != null) { + row.setDefault(true); + } + + defaultRow = row; + requestSectionRefresh(); + } + + /** + * Returns the current default row of this header. The default row is a + * special header row providing a user interface for sorting columns. + * + * @return the default row or null if no default row set + */ + public HeaderRow getDefaultRow() { + return defaultRow; + } + + @Override + protected HeaderRow createRow() { + return new HeaderRow(); + } + + @Override + protected void requestSectionRefresh() { + markAsDirty = true; + + /* + * Defer the refresh so if we multiple times call refreshSection() + * (for example when updating cell values) we only get one actual + * refresh in the end. + */ + Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() { + + @Override + public void execute() { + if (markAsDirty) { + markAsDirty = false; + getGrid().refreshHeader(); + } + } + }); + } + + /** + * Returns the events consumed by the header + * + * @return a collection of BrowserEvents + */ + public Collection getConsumedEvents() { + return Arrays.asList(BrowserEvents.TOUCHSTART, + BrowserEvents.TOUCHMOVE, BrowserEvents.TOUCHEND, + BrowserEvents.TOUCHCANCEL, BrowserEvents.CLICK); + } + + @Override + protected void addColumn(Column column) { + super.addColumn(column); + + // Add default content for new columns. + if (defaultRow != null) { + column.setDefaultHeaderContent(defaultRow.getCell(column)); + } + } + } + + /** + * A single row in a grid header section. + * + */ + public static class HeaderRow extends StaticSection.StaticRow { + + private boolean isDefault = false; + + protected void setDefault(boolean isDefault) { + this.isDefault = isDefault; + if (isDefault) { + for (Column column : getSection().grid.getColumns()) { + column.setDefaultHeaderContent(getCell(column)); + } + } + } + + public boolean isDefault() { + return isDefault; + } + + @Override + protected HeaderCell createCell() { + return new HeaderCell(); + } + } + + /** + * A single cell in a grid header row. Has a caption and, if it's in a + * default row, a drag handle. + */ + public static class HeaderCell extends StaticSection.StaticCell { + } + + /** + * Represents the footer section of a Grid. The footer is always empty. + */ + protected static class Footer extends StaticSection { + private boolean markAsDirty = false; + + @Override + protected FooterRow createRow() { + return new FooterRow(); + } + + @Override + protected void requestSectionRefresh() { + markAsDirty = true; + + /* + * Defer the refresh so if we multiple times call refreshSection() + * (for example when updating cell values) we only get one actual + * refresh in the end. + */ + Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() { + + @Override + public void execute() { + if (markAsDirty) { + markAsDirty = false; + getGrid().refreshFooter(); + } + } + }); + } + } + + /** + * A single cell in a grid Footer row. Has a textual caption. + * + */ + public static class FooterCell extends StaticSection.StaticCell { + } + + /** + * A single row in a grid Footer section. + * + */ + public static class FooterRow extends StaticSection.StaticRow { + + @Override + protected FooterCell createCell() { + return new FooterCell(); + } + } + + private static class EditorRequestImpl implements EditorRequest { + + /** + * A callback interface used to notify the invoker of the editor handler + * of completed editor requests. + * + * @param + * the row data type + */ + public static interface RequestCallback { + /** + * The method that must be called when the request has been + * processed correctly. + * + * @param request + * the original request object + */ + public void onSuccess(EditorRequest request); + + /** + * The method that must be called when processing the request has + * produced an aborting error. + * + * @param request + * the original request object + */ + public void onError(EditorRequest request); + } + + private Grid grid; + private final int rowIndex; + private final int columnIndex; + private RequestCallback callback; + private boolean completed = false; + + public EditorRequestImpl(Grid grid, int rowIndex, int columnIndex, + RequestCallback callback) { + this.grid = grid; + this.rowIndex = rowIndex; + this.columnIndex = columnIndex; + this.callback = callback; + } + + @Override + public int getRowIndex() { + return rowIndex; + } + + @Override + public int getColumnIndex() { + return columnIndex; + } + + @Override + public T getRow() { + return grid.getDataSource().getRow(rowIndex); + } + + @Override + public Grid getGrid() { + return grid; + } + + @Override + public Widget getWidget(Grid.Column column) { + Widget w = grid.getEditorWidget(column); + assert w != null; + return w; + } + + private void complete(String errorMessage, + Collection> errorColumns) { + if (completed) { + throw new IllegalStateException( + "An EditorRequest must be completed exactly once"); + } + completed = true; + + if (errorColumns == null) { + errorColumns = Collections.emptySet(); + } + grid.getEditor().setEditorError(errorMessage, errorColumns); + } + + @Override + public void success() { + complete(null, null); + if (callback != null) { + callback.onSuccess(this); + } + } + + @Override + public void failure(String errorMessage, + Collection> errorColumns) { + complete(errorMessage, errorColumns); + if (callback != null) { + callback.onError(this); + } + } + + @Override + public boolean isCompleted() { + return completed; + } + } + + /** + * A wrapper for native DOM events originating from Grid. In addition to the + * native event, contains a {@link CellReference} instance specifying which + * cell the event originated from. + * + * @since 7.6 + * @param + * The row type of the grid + */ + public static class GridEvent { + private Event event; + private EventCellReference cell; + + protected GridEvent(Event event, EventCellReference cell) { + this.event = event; + this.cell = cell; + } + + /** + * Returns the wrapped DOM event. + * + * @return the DOM event + */ + public Event getDomEvent() { + return event; + } + + /** + * Returns the Grid cell this event originated from. + * + * @return the event cell + */ + public EventCellReference getCell() { + return cell; + } + + /** + * Returns the Grid instance this event originated from. + * + * @return the grid + */ + public Grid getGrid() { + return cell.getGrid(); + } + } + + /** + * A wrapper for native DOM events related to the {@link Editor Grid editor} + * . + * + * @since 7.6 + * @param + * the row type of the grid + */ + public static class EditorDomEvent extends GridEvent { + + private final Widget editorWidget; + + protected EditorDomEvent(Event event, EventCellReference cell, + Widget editorWidget) { + super(event, cell); + this.editorWidget = editorWidget; + } + + /** + * Returns the editor of the Grid this event originated from. + * + * @return the related editor instance + */ + public Editor getEditor() { + return getGrid().getEditor(); + } + + /** + * Returns the currently focused editor widget. + * + * @return the focused editor widget or {@code null} if not editable + */ + public Widget getEditorWidget() { + return editorWidget; + } + + /** + * Returns the row index the editor is open at. If the editor is not + * open, returns -1. + * + * @return the index of the edited row or -1 if editor is not open + */ + public int getRowIndex() { + return getEditor().rowIndex; + } + + /** + * Returns the column index the editor was opened at. If the editor is + * not open, returns -1. + * + * @return the column index or -1 if editor is not open + */ + public int getFocusedColumnIndex() { + return getEditor().focusedColumnIndex; + } + } + + /** + * An editor UI for Grid rows. A single Grid row at a time can be opened for + * editing. + * + * @since 7.6 + * @param + * the row type of the grid + */ + public static class Editor implements DeferredWorker { + + public static final int KEYCODE_SHOW = KeyCodes.KEY_ENTER; + public static final int KEYCODE_HIDE = KeyCodes.KEY_ESCAPE; + + private static final String ERROR_CLASS_NAME = "error"; + private static final String NOT_EDITABLE_CLASS_NAME = "not-editable"; + + ScheduledCommand fieldFocusCommand = new ScheduledCommand() { + private int count = 0; + + @Override + public void execute() { + Element focusedElement = WidgetUtil.getFocusedElement(); + if (focusedElement == grid.getElement() + || focusedElement == Document.get().getBody() + || count > 2) { + focusColumn(focusedColumnIndex); + } else { + ++count; + Scheduler.get().scheduleDeferred(this); + } + } + }; + + /** + * A handler for events related to the Grid editor. Responsible for + * opening, moving or closing the editor based on the received event. + * + * @since 7.6 + * @author Vaadin Ltd + * @param + * the row type of the grid + */ + public interface EventHandler { + /** + * Handles editor-related events in an appropriate way. Opens, + * moves, or closes the editor based on the given event. + * + * @param event + * the received event + * @return true if the event was handled and nothing else should be + * done, false otherwise + */ + boolean handleEvent(EditorDomEvent event); + } + + protected enum State { + INACTIVE, ACTIVATING, BINDING, ACTIVE, SAVING + } + + private Grid grid; + private EditorHandler handler; + private EventHandler eventHandler = GWT + .create(DefaultEditorEventHandler.class); + + private DivElement editorOverlay = DivElement.as(DOM.createDiv()); + private DivElement cellWrapper = DivElement.as(DOM.createDiv()); + private DivElement frozenCellWrapper = DivElement.as(DOM.createDiv()); + + private DivElement messageAndButtonsWrapper = DivElement.as(DOM + .createDiv()); + + private DivElement messageWrapper = DivElement.as(DOM.createDiv()); + private DivElement buttonsWrapper = DivElement.as(DOM.createDiv()); + + // Element which contains the error message for the editor + // Should only be added to the DOM when there's a message to show + private DivElement message = DivElement.as(DOM.createDiv()); + + private Map, Widget> columnToWidget = new HashMap, Widget>(); + private List focusHandlers = new ArrayList(); + + private boolean enabled = false; + private State state = State.INACTIVE; + private int rowIndex = -1; + private int focusedColumnIndex = -1; + private String styleName = null; + + private HandlerRegistration hScrollHandler; + private HandlerRegistration vScrollHandler; + + private final Button saveButton; + private final Button cancelButton; + + private static final int SAVE_TIMEOUT_MS = 5000; + private final Timer saveTimeout = new Timer() { + @Override + public void run() { + getLogger().warning( + "Editor save action is taking longer than expected (" + + SAVE_TIMEOUT_MS + "ms). Does your " + + EditorHandler.class.getSimpleName() + + " remember to call success() or fail()?"); + } + }; + + private final EditorRequestImpl.RequestCallback saveRequestCallback = new EditorRequestImpl.RequestCallback() { + @Override + public void onSuccess(EditorRequest request) { + if (state == State.SAVING) { + cleanup(); + cancel(); + grid.clearSortOrder(); + } + } + + @Override + public void onError(EditorRequest request) { + if (state == State.SAVING) { + cleanup(); + } + } + + private void cleanup() { + state = State.ACTIVE; + setButtonsEnabled(true); + saveTimeout.cancel(); + } + }; + + private static final int BIND_TIMEOUT_MS = 5000; + private final Timer bindTimeout = new Timer() { + @Override + public void run() { + getLogger().warning( + "Editor bind action is taking longer than expected (" + + BIND_TIMEOUT_MS + "ms). Does your " + + EditorHandler.class.getSimpleName() + + " remember to call success() or fail()?"); + } + }; + + private final EditorRequestImpl.RequestCallback bindRequestCallback = new EditorRequestImpl.RequestCallback() { + @Override + public void onSuccess(EditorRequest request) { + if (state == State.BINDING) { + state = State.ACTIVE; + bindTimeout.cancel(); + + rowIndex = request.getRowIndex(); + focusedColumnIndex = request.getColumnIndex(); + if (focusedColumnIndex >= 0) { + // Update internal focus of Grid + grid.focusCell(rowIndex, focusedColumnIndex); + } + + showOverlay(); + } + } + + @Override + public void onError(EditorRequest request) { + if (state == State.BINDING) { + if (rowIndex == -1) { + doCancel(); + } else { + state = State.ACTIVE; + // TODO: Maybe restore focus? + } + bindTimeout.cancel(); + } + } + }; + + /** A set of all the columns that display an error flag. */ + private final Set> columnErrors = new HashSet>(); + private boolean buffered = true; + + /** Original position of editor */ + private double originalTop; + /** Original scroll position of grid when editor was opened */ + private double originalScrollTop; + private RowHandle pinnedRowHandle; + + public Editor() { + saveButton = new Button(); + saveButton.setText(GridConstants.DEFAULT_SAVE_CAPTION); + saveButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + save(); + } + }); + + cancelButton = new Button(); + cancelButton.setText(GridConstants.DEFAULT_CANCEL_CAPTION); + cancelButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + cancel(); + } + }); + } + + public void setEditorError(String errorMessage, + Collection> errorColumns) { + + if (errorMessage == null) { + message.removeFromParent(); + } else { + message.setInnerText(errorMessage); + if (message.getParentElement() == null) { + messageWrapper.appendChild(message); + } + } + // In unbuffered mode only show message wrapper if there is an error + if (!isBuffered()) { + setMessageAndButtonsWrapperVisible(errorMessage != null); + } + + if (state == State.ACTIVE || state == State.SAVING) { + for (Column c : grid.getColumns()) { + grid.getEditor().setEditorColumnError(c, + errorColumns.contains(c)); + } + } + } + + public int getRow() { + return rowIndex; + } + + /** + * If a cell of this Grid had focus once this editRow call was + * triggered, the editor component at the previously focused column + * index will be focused. + * + * If a Grid cell was not focused prior to calling this method, it will + * be equivalent to {@code editRow(rowIndex, -1)}. + * + * @see #editRow(int, int) + */ + public void editRow(int rowIndex) { + // Focus the last focused column in the editor iff grid or its child + // was focused before the edit request + Cell focusedCell = grid.cellFocusHandler.getFocusedCell(); + Element focusedElement = WidgetUtil.getFocusedElement(); + if (focusedCell != null && focusedElement != null + && grid.getElement().isOrHasChild(focusedElement)) { + editRow(rowIndex, focusedCell.getColumn()); + } else { + editRow(rowIndex, -1); + } + } + + /** + * Opens the editor over the row with the given index and attempts to + * focus the editor widget in the given column index. Does not move + * focus if the widget is not focusable or if the column index is -1. + * + * @param rowIndex + * the index of the row to be edited + * @param columnIndex + * the column index of the editor widget that should be + * initially focused or -1 to not set focus + * + * @throws IllegalStateException + * if this editor is not enabled + * @throws IllegalStateException + * if this editor is already in edit mode and in buffered + * mode + * + * @since 7.5 + */ + public void editRow(final int rowIndex, final int columnIndex) { + if (!enabled) { + throw new IllegalStateException( + "Cannot edit row: editor is not enabled"); + } + + if (isWorkPending()) { + // Request pending a response, don't move try to start another + // request. + return; + } + + if (state != State.INACTIVE && this.rowIndex != rowIndex) { + if (isBuffered()) { + throw new IllegalStateException( + "Cannot edit row: editor already in edit mode"); + } else if (!columnErrors.isEmpty()) { + // Don't move row if errors are present + + // FIXME: Should attempt bind if error field values have + // changed. + + return; + } + } + if (columnIndex >= grid.getVisibleColumns().size()) { + throw new IllegalArgumentException("Edited column index " + + columnIndex + + " was bigger than visible column count."); + } + + if (this.rowIndex == rowIndex && focusedColumnIndex == columnIndex) { + // NO-OP + return; + } + + if (this.rowIndex == rowIndex) { + if (focusedColumnIndex != columnIndex) { + if (columnIndex >= grid.getFrozenColumnCount()) { + // Scroll to new focused column. + grid.getEscalator().scrollToColumn(columnIndex, + ScrollDestination.ANY, 0); + } + + focusedColumnIndex = columnIndex; + } + + updateHorizontalScrollPosition(); + + // Update Grid internal focus and focus widget if possible + if (focusedColumnIndex >= 0) { + grid.focusCell(rowIndex, focusedColumnIndex); + focusColumn(focusedColumnIndex); + } + + // No need to request anything from the editor handler. + return; + } + state = State.ACTIVATING; + + final Escalator escalator = grid.getEscalator(); + if (escalator.getVisibleRowRange().contains(rowIndex)) { + show(rowIndex, columnIndex); + } else { + vScrollHandler = grid.addScrollHandler(new ScrollHandler() { + @Override + public void onScroll(ScrollEvent event) { + if (escalator.getVisibleRowRange().contains(rowIndex)) { + show(rowIndex, columnIndex); + vScrollHandler.removeHandler(); + } + } + }); + grid.scrollToRow(rowIndex, + isBuffered() ? ScrollDestination.MIDDLE + : ScrollDestination.ANY); + } + } + + /** + * Cancels the currently active edit and hides the editor. Any changes + * that are not {@link #save() saved} are lost. + * + * @throws IllegalStateException + * if this editor is not enabled + * @throws IllegalStateException + * if this editor is not in edit mode + */ + public void cancel() { + if (!enabled) { + throw new IllegalStateException( + "Cannot cancel edit: editor is not enabled"); + } + if (state == State.INACTIVE) { + throw new IllegalStateException( + "Cannot cancel edit: editor is not in edit mode"); + } + handler.cancel(new EditorRequestImpl(grid, rowIndex, + focusedColumnIndex, null)); + doCancel(); + } + + private void doCancel() { + hideOverlay(); + state = State.INACTIVE; + rowIndex = -1; + focusedColumnIndex = -1; + grid.getEscalator().setScrollLocked(Direction.VERTICAL, false); + updateSelectionCheckboxesAsNeeded(true); + } + + private void updateSelectionCheckboxesAsNeeded(boolean isEnabled) { + // FIXME: This is too much guessing. Define a better way to do this. + if (grid.selectionColumn != null + && grid.selectionColumn.getRenderer() instanceof MultiSelectionRenderer) { + grid.refreshBody(); + CheckBox checkBox = (CheckBox) grid.getDefaultHeaderRow() + .getCell(grid.selectionColumn).getWidget(); + checkBox.setEnabled(isEnabled); + } + } + + /** + * Saves any unsaved changes to the data source and hides the editor. + * + * @throws IllegalStateException + * if this editor is not enabled + * @throws IllegalStateException + * if this editor is not in edit mode + */ + public void save() { + if (!enabled) { + throw new IllegalStateException( + "Cannot save: editor is not enabled"); + } + if (state != State.ACTIVE) { + throw new IllegalStateException( + "Cannot save: editor is not in edit mode"); + } + + state = State.SAVING; + setButtonsEnabled(false); + saveTimeout.schedule(SAVE_TIMEOUT_MS); + EditorRequest request = new EditorRequestImpl(grid, rowIndex, + focusedColumnIndex, saveRequestCallback); + handler.save(request); + updateSelectionCheckboxesAsNeeded(true); + } + + /** + * Returns the handler responsible for binding data and editor widgets + * to this editor. + * + * @return the editor handler or null if not set + */ + public EditorHandler getHandler() { + return handler; + } + + /** + * Sets the handler responsible for binding data and editor widgets to + * this editor. + * + * @param rowHandler + * the new editor handler + * + * @throws IllegalStateException + * if this editor is currently in edit mode + */ + public void setHandler(EditorHandler rowHandler) { + if (state != State.INACTIVE) { + throw new IllegalStateException( + "Cannot set EditorHandler: editor is currently in edit mode"); + } + handler = rowHandler; + } + + public boolean isEnabled() { + return enabled; + } + + /** + * Sets the enabled state of this editor. + * + * @param enabled + * true if enabled, false otherwise + * + * @throws IllegalStateException + * if in edit mode and trying to disable + * @throws IllegalStateException + * if the editor handler is not set + */ + public void setEnabled(boolean enabled) { + if (enabled == false && state != State.INACTIVE) { + throw new IllegalStateException( + "Cannot disable: editor is in edit mode"); + } else if (enabled == true && getHandler() == null) { + throw new IllegalStateException( + "Cannot enable: EditorHandler not set"); + } + this.enabled = enabled; + } + + protected void show(int rowIndex, int columnIndex) { + if (state == State.ACTIVATING) { + state = State.BINDING; + bindTimeout.schedule(BIND_TIMEOUT_MS); + EditorRequest request = new EditorRequestImpl(grid, + rowIndex, columnIndex, bindRequestCallback); + handler.bind(request); + grid.getEscalator().setScrollLocked(Direction.VERTICAL, + isBuffered()); + updateSelectionCheckboxesAsNeeded(false); + } + } + + protected void setGrid(final Grid grid) { + assert grid != null : "Grid cannot be null"; + assert this.grid == null : "Can only attach editor to Grid once"; + + this.grid = grid; + } + + protected State getState() { + return state; + } + + protected void setState(State state) { + this.state = state; + } + + /** + * Returns the editor widget associated with the given column. If the + * editor is not active or the column is not + * {@link Grid.Column#isEditable() editable}, returns null. + * + * @param column + * the column + * @return the widget if the editor is open and the column is editable, + * null otherwise + */ + protected Widget getWidget(Column column) { + return columnToWidget.get(column); + } + + /** + * Equivalent to {@code showOverlay()}. The argument is ignored. + * + * @param unused + * ignored argument + * + * @deprecated As of 7.5, use {@link #showOverlay()} instead. + */ + @Deprecated + protected void showOverlay(TableRowElement unused) { + showOverlay(); + } + + /** + * Opens the editor overlay over the table row indicated by + * {@link #getRow()}. + * + * @since 7.5 + */ + protected void showOverlay() { + // Ensure overlay is hidden initially + hideOverlay(); + DivElement gridElement = DivElement.as(grid.getElement()); + + TableRowElement tr = grid.getEscalator().getBody() + .getRowElement(rowIndex); + + hScrollHandler = grid.addScrollHandler(new ScrollHandler() { + @Override + public void onScroll(ScrollEvent event) { + updateHorizontalScrollPosition(); + updateVerticalScrollPosition(); + } + }); + + gridElement.appendChild(editorOverlay); + editorOverlay.appendChild(frozenCellWrapper); + editorOverlay.appendChild(cellWrapper); + editorOverlay.appendChild(messageAndButtonsWrapper); + + updateBufferedStyleName(); + + int frozenColumns = grid.getVisibleFrozenColumnCount(); + double frozenColumnsWidth = 0; + double cellHeight = 0; + + for (int i = 0; i < tr.getCells().getLength(); i++) { + Element cell = createCell(tr.getCells().getItem(i)); + cellHeight = Math.max(cellHeight, WidgetUtil + .getRequiredHeightBoundingClientRectDouble(tr + .getCells().getItem(i))); + + Column column = grid.getVisibleColumn(i); + + if (i < frozenColumns) { + frozenCellWrapper.appendChild(cell); + frozenColumnsWidth += WidgetUtil + .getRequiredWidthBoundingClientRectDouble(tr + .getCells().getItem(i)); + } else { + cellWrapper.appendChild(cell); + } + + if (column.isEditable()) { + Widget editor = getHandler().getWidget(column); + + if (editor != null) { + columnToWidget.put(column, editor); + grid.attachWidget(editor, cell); + } + + if (i == focusedColumnIndex) { + if (BrowserInfo.get().isIE8()) { + Scheduler.get().scheduleDeferred(fieldFocusCommand); + } else { + focusColumn(focusedColumnIndex); + } + } + } else { + cell.addClassName(NOT_EDITABLE_CLASS_NAME); + cell.addClassName(tr.getCells().getItem(i).getClassName()); + // If the focused or frozen stylename is present it should + // not be inherited by the editor cell as it is not useful + // in the editor and would look broken without additional + // style rules. This is a bit of a hack. + cell.removeClassName(grid.cellFocusStyleName); + cell.removeClassName("frozen"); + + if (column == grid.selectionColumn) { + // Duplicate selection column CheckBox + + pinnedRowHandle = grid.getDataSource().getHandle( + grid.getDataSource().getRow(rowIndex)); + pinnedRowHandle.pin(); + + // We need to duplicate the selection CheckBox for the + // editor overlay since the original one is hidden by + // the overlay + final CheckBox checkBox = GWT.create(CheckBox.class); + checkBox.setValue(grid.isSelected(pinnedRowHandle + .getRow())); + checkBox.sinkEvents(Event.ONCLICK); + + checkBox.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + T row = pinnedRowHandle.getRow(); + if (grid.isSelected(row)) { + grid.deselect(row); + } else { + grid.select(row); + } + } + }); + grid.attachWidget(checkBox, cell); + columnToWidget.put(column, checkBox); + + // Only enable CheckBox in non-buffered mode + checkBox.setEnabled(!isBuffered()); + + } else if (!(column.getRenderer() instanceof WidgetRenderer)) { + // Copy non-widget content directly + cell.setInnerHTML(tr.getCells().getItem(i) + .getInnerHTML()); + } + } + } + + setBounds(frozenCellWrapper, 0, 0, frozenColumnsWidth, 0); + setBounds(cellWrapper, frozenColumnsWidth, 0, tr.getOffsetWidth() + - frozenColumnsWidth, cellHeight); + + // Only add these elements once + if (!messageAndButtonsWrapper.isOrHasChild(messageWrapper)) { + messageAndButtonsWrapper.appendChild(messageWrapper); + messageAndButtonsWrapper.appendChild(buttonsWrapper); + } + + if (isBuffered()) { + grid.attachWidget(saveButton, buttonsWrapper); + grid.attachWidget(cancelButton, buttonsWrapper); + } + + setMessageAndButtonsWrapperVisible(isBuffered()); + + updateHorizontalScrollPosition(); + + AbstractRowContainer body = (AbstractRowContainer) grid + .getEscalator().getBody(); + double rowTop = body.getRowTop(tr); + + int bodyTop = body.getElement().getAbsoluteTop(); + int gridTop = gridElement.getAbsoluteTop(); + double overlayTop = rowTop + bodyTop - gridTop; + + originalScrollTop = grid.getScrollTop(); + if (!isBuffered() || buttonsShouldBeRenderedBelow(tr)) { + // Default case, editor buttons are below the edited row + editorOverlay.getStyle().setTop(overlayTop, Unit.PX); + originalTop = overlayTop; + editorOverlay.getStyle().clearBottom(); + } else { + // Move message and buttons wrapper on top of cell wrapper if + // there is not enough space visible space under and fix the + // overlay from the bottom + editorOverlay.insertFirst(messageAndButtonsWrapper); + int gridHeight = grid.getElement().getOffsetHeight(); + editorOverlay.getStyle() + .setBottom( + gridHeight - overlayTop - tr.getOffsetHeight(), + Unit.PX); + editorOverlay.getStyle().clearTop(); + } + + // Do not render over the vertical scrollbar + editorOverlay.getStyle().setWidth(grid.escalator.getInnerWidth(), + Unit.PX); + } + + private void focusColumn(int colIndex) { + if (colIndex < 0 || colIndex >= grid.getVisibleColumns().size()) { + // NO-OP + return; + } + + Widget editor = getWidget(grid.getVisibleColumn(colIndex)); + if (editor instanceof Focusable) { + ((Focusable) editor).focus(); + } else if (editor instanceof com.google.gwt.user.client.ui.Focusable) { + ((com.google.gwt.user.client.ui.Focusable) editor) + .setFocus(true); + } else { + grid.focus(); + } + } + + private boolean buttonsShouldBeRenderedBelow(TableRowElement tr) { + TableSectionElement tfoot = grid.escalator.getFooter().getElement(); + double tfootPageTop = WidgetUtil.getBoundingClientRect(tfoot) + .getTop(); + double trPageBottom = WidgetUtil.getBoundingClientRect(tr) + .getBottom(); + int messageAndButtonsHeight = messageAndButtonsWrapper + .getOffsetHeight(); + double bottomOfButtons = trPageBottom + messageAndButtonsHeight; + + return bottomOfButtons < tfootPageTop; + } + + protected void hideOverlay() { + if (editorOverlay.getParentElement() == null) { + return; + } + + if (pinnedRowHandle != null) { + pinnedRowHandle.unpin(); + pinnedRowHandle = null; + } + + for (HandlerRegistration r : focusHandlers) { + r.removeHandler(); + } + focusHandlers.clear(); + + for (Widget w : columnToWidget.values()) { + setParent(w, null); + } + columnToWidget.clear(); + + if (isBuffered()) { + grid.detachWidget(saveButton); + grid.detachWidget(cancelButton); + } + + editorOverlay.removeAllChildren(); + cellWrapper.removeAllChildren(); + frozenCellWrapper.removeAllChildren(); + editorOverlay.removeFromParent(); + + hScrollHandler.removeHandler(); + + clearEditorColumnErrors(); + } + + private void updateBufferedStyleName() { + if (isBuffered()) { + editorOverlay.removeClassName("unbuffered"); + editorOverlay.addClassName("buffered"); + } else { + editorOverlay.removeClassName("buffered"); + editorOverlay.addClassName("unbuffered"); + } + } + + protected void setStylePrimaryName(String primaryName) { + if (styleName != null) { + editorOverlay.removeClassName(styleName); + + cellWrapper.removeClassName(styleName + "-cells"); + frozenCellWrapper.removeClassName(styleName + "-cells"); + messageAndButtonsWrapper.removeClassName(styleName + "-footer"); + + messageWrapper.removeClassName(styleName + "-message"); + buttonsWrapper.removeClassName(styleName + "-buttons"); + + saveButton.removeStyleName(styleName + "-save"); + cancelButton.removeStyleName(styleName + "-cancel"); + } + styleName = primaryName + "-editor"; + editorOverlay.setClassName(styleName); + + cellWrapper.setClassName(styleName + "-cells"); + frozenCellWrapper.setClassName(styleName + "-cells frozen"); + messageAndButtonsWrapper.setClassName(styleName + "-footer"); + + messageWrapper.setClassName(styleName + "-message"); + buttonsWrapper.setClassName(styleName + "-buttons"); + + saveButton.setStyleName(styleName + "-save"); + cancelButton.setStyleName(styleName + "-cancel"); + } + + /** + * Creates an editor cell corresponding to the given table cell. The + * returned element is empty and has the same dimensions and position as + * the table cell. + * + * @param td + * the table cell used as a reference + * @return an editor cell corresponding to the given cell + */ + protected Element createCell(TableCellElement td) { + DivElement cell = DivElement.as(DOM.createDiv()); + double width = WidgetUtil + .getRequiredWidthBoundingClientRectDouble(td); + double height = WidgetUtil + .getRequiredHeightBoundingClientRectDouble(td); + setBounds(cell, td.getOffsetLeft(), td.getOffsetTop(), width, + height); + return cell; + } + + private static void setBounds(Element e, double left, double top, + double width, double height) { + Style style = e.getStyle(); + style.setLeft(left, Unit.PX); + style.setTop(top, Unit.PX); + style.setWidth(width, Unit.PX); + style.setHeight(height, Unit.PX); + } + + private void updateHorizontalScrollPosition() { + double scrollLeft = grid.getScrollLeft(); + cellWrapper.getStyle().setLeft( + frozenCellWrapper.getOffsetWidth() - scrollLeft, Unit.PX); + } + + /** + * Moves the editor overlay on scroll so that it stays on top of the + * edited row. This will also snap the editor to top or bottom of the + * row container if the edited row is scrolled out of the visible area. + */ + private void updateVerticalScrollPosition() { + if (isBuffered()) { + return; + } + + double newScrollTop = grid.getScrollTop(); + + int gridTop = grid.getElement().getAbsoluteTop(); + int editorHeight = editorOverlay.getOffsetHeight(); + + Escalator escalator = grid.getEscalator(); + TableSectionElement header = escalator.getHeader().getElement(); + int footerTop = escalator.getFooter().getElement().getAbsoluteTop(); + int headerBottom = header.getAbsoluteBottom(); + + double newTop = originalTop - (newScrollTop - originalScrollTop); + + if (newTop + gridTop < headerBottom) { + // Snap editor to top of the row container + newTop = header.getOffsetHeight(); + } else if (newTop + gridTop > footerTop - editorHeight) { + // Snap editor to the bottom of the row container + newTop = footerTop - editorHeight - gridTop; + } + + editorOverlay.getStyle().setTop(newTop, Unit.PX); + } + + protected void setGridEnabled(boolean enabled) { + // TODO: This should be informed to handler as well so possible + // fields can be disabled. + setButtonsEnabled(enabled); + } + + private void setButtonsEnabled(boolean enabled) { + saveButton.setEnabled(enabled); + cancelButton.setEnabled(enabled); + } + + public void setSaveCaption(String saveCaption) + throws IllegalArgumentException { + if (saveCaption == null) { + throw new IllegalArgumentException( + "Save caption cannot be null"); + } + saveButton.setText(saveCaption); + } + + public String getSaveCaption() { + return saveButton.getText(); + } + + public void setCancelCaption(String cancelCaption) + throws IllegalArgumentException { + if (cancelCaption == null) { + throw new IllegalArgumentException( + "Cancel caption cannot be null"); + } + cancelButton.setText(cancelCaption); + } + + public String getCancelCaption() { + return cancelButton.getText(); + } + + public void setEditorColumnError(Column column, boolean hasError) { + if (state != State.ACTIVE && state != State.SAVING) { + throw new IllegalStateException("Cannot set cell error " + + "status: editor is neither active nor saving."); + } + + if (isEditorColumnError(column) == hasError) { + return; + } + + Element editorCell = getWidget(column).getElement() + .getParentElement(); + if (hasError) { + editorCell.addClassName(ERROR_CLASS_NAME); + columnErrors.add(column); + } else { + editorCell.removeClassName(ERROR_CLASS_NAME); + columnErrors.remove(column); + } + } + + public void clearEditorColumnErrors() { + + /* + * editorOverlay has no children if it's not active, effectively + * making this loop a NOOP. + */ + Element e = editorOverlay.getFirstChildElement(); + while (e != null) { + e.removeClassName(ERROR_CLASS_NAME); + e = e.getNextSiblingElement(); + } + + columnErrors.clear(); + } + + public boolean isEditorColumnError(Column column) { + return columnErrors.contains(column); + } + + public void setBuffered(boolean buffered) { + this.buffered = buffered; + setMessageAndButtonsWrapperVisible(buffered); + } + + public boolean isBuffered() { + return buffered; + } + + private void setMessageAndButtonsWrapperVisible(boolean visible) { + if (visible) { + messageAndButtonsWrapper.getStyle().clearDisplay(); + } else { + messageAndButtonsWrapper.getStyle().setDisplay(Display.NONE); + } + } + + /** + * Sets the event handler for this Editor. + * + * @since 7.6 + * @param handler + * the new event handler + */ + public void setEventHandler(EventHandler handler) { + eventHandler = handler; + } + + /** + * Returns the event handler of this Editor. + * + * @since 7.6 + * @return the current event handler + */ + public EventHandler getEventHandler() { + return eventHandler; + } + + @Override + public boolean isWorkPending() { + return saveTimeout.isRunning() || bindTimeout.isRunning(); + } + + protected int getElementColumn(Element e) { + int frozenCells = frozenCellWrapper.getChildCount(); + if (frozenCellWrapper.isOrHasChild(e)) { + for (int i = 0; i < frozenCells; ++i) { + if (frozenCellWrapper.getChild(i).isOrHasChild(e)) { + return i; + } + } + } + + if (cellWrapper.isOrHasChild(e)) { + for (int i = 0; i < cellWrapper.getChildCount(); ++i) { + if (cellWrapper.getChild(i).isOrHasChild(e)) { + return i + frozenCells; + } + } + } + + return -1; + } + } + + public static abstract class AbstractGridKeyEvent + extends KeyEvent { + + private Grid grid; + private final Type associatedType = new Type( + getBrowserEventType(), this); + private final CellReference targetCell; + + public AbstractGridKeyEvent(Grid grid, CellReference targetCell) { + this.grid = grid; + this.targetCell = targetCell; + } + + protected abstract String getBrowserEventType(); + + /** + * Gets the Grid instance for this event. + * + * @return grid + */ + public Grid getGrid() { + return grid; + } + + /** + * Gets the focused cell for this event. + * + * @return focused cell + */ + public CellReference getFocusedCell() { + return targetCell; + } + + @Override + protected void dispatch(HANDLER handler) { + EventTarget target = getNativeEvent().getEventTarget(); + if (Element.is(target) + && !grid.isElementInChildWidget(Element.as(target))) { + + Section section = Section.FOOTER; + final RowContainer container = grid.cellFocusHandler.containerWithFocus; + if (container == grid.escalator.getHeader()) { + section = Section.HEADER; + } else if (container == grid.escalator.getBody()) { + section = Section.BODY; + } + + doDispatch(handler, section); + } + } + + protected abstract void doDispatch(HANDLER handler, Section section); + + @Override + public Type getAssociatedType() { + return associatedType; + } + } + + public static abstract class AbstractGridMouseEvent + extends MouseEvent { + + private Grid grid; + private final CellReference targetCell; + private final Type associatedType = new Type( + getBrowserEventType(), this); + + public AbstractGridMouseEvent(Grid grid, CellReference targetCell) { + this.grid = grid; + this.targetCell = targetCell; + } + + protected abstract String getBrowserEventType(); + + /** + * Gets the Grid instance for this event. + * + * @return grid + */ + public Grid getGrid() { + return grid; + } + + /** + * Gets the reference of target cell for this event. + * + * @return target cell + */ + public CellReference getTargetCell() { + return targetCell; + } + + @Override + protected void dispatch(HANDLER handler) { + EventTarget target = getNativeEvent().getEventTarget(); + if (!Element.is(target)) { + // Target is not an element + return; + } + + Element targetElement = Element.as(target); + if (grid.isElementInChildWidget(targetElement)) { + // Target is some widget inside of Grid + return; + } + + final RowContainer container = grid.escalator + .findRowContainer(targetElement); + if (container == null) { + // No container for given element + return; + } + + Section section = Section.FOOTER; + if (container == grid.escalator.getHeader()) { + section = Section.HEADER; + } else if (container == grid.escalator.getBody()) { + section = Section.BODY; + } + + doDispatch(handler, section); + } + + protected abstract void doDispatch(HANDLER handler, Section section); + + @Override + public Type getAssociatedType() { + return associatedType; + } + } + + private static final String CUSTOM_STYLE_PROPERTY_NAME = "customStyle"; + + /** + * An initial height that is given to new details rows before rendering the + * appropriate widget that we then can be measure + * + * @see GridSpacerUpdater + */ + private static final double DETAILS_ROW_INITIAL_HEIGHT = 50; + + private EventCellReference eventCell = new EventCellReference(this); + private GridKeyDownEvent keyDown = new GridKeyDownEvent(this, eventCell); + private GridKeyUpEvent keyUp = new GridKeyUpEvent(this, eventCell); + private GridKeyPressEvent keyPress = new GridKeyPressEvent(this, eventCell); + private GridClickEvent clickEvent = new GridClickEvent(this, eventCell); + private GridDoubleClickEvent doubleClickEvent = new GridDoubleClickEvent( + this, eventCell); + + private class CellFocusHandler { + + private RowContainer containerWithFocus = escalator.getBody(); + private int rowWithFocus = 0; + private Range cellFocusRange = Range.withLength(0, 1); + private int lastFocusedBodyRow = 0; + private int lastFocusedHeaderRow = 0; + private int lastFocusedFooterRow = 0; + private TableCellElement cellWithFocusStyle = null; + private TableRowElement rowWithFocusStyle = null; + + public CellFocusHandler() { + sinkEvents(getNavigationEvents()); + } + + private Cell getFocusedCell() { + return new Cell(rowWithFocus, cellFocusRange.getStart(), + cellWithFocusStyle); + } + + /** + * Sets style names for given cell when needed. + */ + public void updateFocusedCellStyle(FlyweightCell cell, + RowContainer cellContainer) { + int cellRow = cell.getRow(); + int cellColumn = cell.getColumn(); + int colSpan = cell.getColSpan(); + boolean columnHasFocus = Range.withLength(cellColumn, colSpan) + .intersects(cellFocusRange); + + if (cellContainer == containerWithFocus) { + // Cell is in the current container + if (cellRow == rowWithFocus && columnHasFocus) { + if (cellWithFocusStyle != cell.getElement()) { + // Cell is correct but it does not have focused style + if (cellWithFocusStyle != null) { + // Remove old focus style + setStyleName(cellWithFocusStyle, + cellFocusStyleName, false); + } + cellWithFocusStyle = cell.getElement(); + + // Add focus style to correct cell. + setStyleName(cellWithFocusStyle, cellFocusStyleName, + true); + } + } else if (cellWithFocusStyle == cell.getElement()) { + // Due to escalator reusing cells, a new cell has the same + // element but is not the focused cell. + setStyleName(cellWithFocusStyle, cellFocusStyleName, false); + cellWithFocusStyle = null; + } + } + } + + /** + * Sets focus style for the given row if needed. + * + * @param row + * a row object + */ + public void updateFocusedRowStyle(Row row) { + if (rowWithFocus == row.getRow() + && containerWithFocus == escalator.getBody()) { + if (row.getElement() != rowWithFocusStyle) { + // Row should have focus style but does not have it. + if (rowWithFocusStyle != null) { + setStyleName(rowWithFocusStyle, rowFocusStyleName, + false); + } + rowWithFocusStyle = row.getElement(); + setStyleName(rowWithFocusStyle, rowFocusStyleName, true); + } + } else if (rowWithFocusStyle == row.getElement() + || (containerWithFocus != escalator.getBody() && rowWithFocusStyle != null)) { + // Remove focus style. + setStyleName(rowWithFocusStyle, rowFocusStyleName, false); + rowWithFocusStyle = null; + } + } + + /** + * Sets the currently focused. + *

+ * NOTE: the column index is the index in DOM, not the logical + * column index which includes hidden columns. + * + * @param rowIndex + * the index of the row having focus + * @param columnIndexDOM + * the index of the cell having focus + * @param container + * the row container having focus + */ + private void setCellFocus(int rowIndex, int columnIndexDOM, + RowContainer container) { + if (rowIndex == rowWithFocus + && cellFocusRange.contains(columnIndexDOM) + && container == this.containerWithFocus) { + refreshRow(rowWithFocus); + return; + } + + int oldRow = rowWithFocus; + rowWithFocus = rowIndex; + Range oldRange = cellFocusRange; + + if (container == escalator.getBody()) { + scrollToRow(rowWithFocus); + cellFocusRange = Range.withLength(columnIndexDOM, 1); + } else { + int i = 0; + Element cell = container.getRowElement(rowWithFocus) + .getFirstChildElement(); + do { + int colSpan = cell + .getPropertyInt(FlyweightCell.COLSPAN_ATTR); + Range cellRange = Range.withLength(i, colSpan); + if (cellRange.contains(columnIndexDOM)) { + cellFocusRange = cellRange; + break; + } + cell = cell.getNextSiblingElement(); + ++i; + } while (cell != null); + } + int columnIndex = getColumns().indexOf( + getVisibleColumn(columnIndexDOM)); + if (columnIndex >= escalator.getColumnConfiguration() + .getFrozenColumnCount()) { + escalator.scrollToColumn(columnIndexDOM, ScrollDestination.ANY, + 10); + } + + if (this.containerWithFocus == container) { + if (oldRange.equals(cellFocusRange) && oldRow != rowWithFocus) { + refreshRow(oldRow); + } else { + refreshHeader(); + refreshFooter(); + } + } else { + RowContainer oldContainer = this.containerWithFocus; + this.containerWithFocus = container; + + if (oldContainer == escalator.getBody()) { + lastFocusedBodyRow = oldRow; + } else if (oldContainer == escalator.getHeader()) { + lastFocusedHeaderRow = oldRow; + } else { + lastFocusedFooterRow = oldRow; + } + + if (!oldRange.equals(cellFocusRange)) { + refreshHeader(); + refreshFooter(); + if (oldContainer == escalator.getBody()) { + oldContainer.refreshRows(oldRow, 1); + } + } else { + oldContainer.refreshRows(oldRow, 1); + } + } + refreshRow(rowWithFocus); + } + + /** + * Sets focus on a cell. + * + *

+ * Note: cell focus is not the same as JavaScript's + * {@code document.activeElement}. + * + * @param cell + * a cell object + */ + public void setCellFocus(CellReference cell) { + setCellFocus(cell.getRowIndex(), cell.getColumnIndexDOM(), + escalator.findRowContainer(cell.getElement())); + } + + /** + * Gets list of events that can be used for cell focusing. + * + * @return list of navigation related event types + */ + public Collection getNavigationEvents() { + return Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.CLICK); + } + + /** + * Handle events that can move the cell focus. + */ + public void handleNavigationEvent(Event event, CellReference cell) { + if (event.getType().equals(BrowserEvents.CLICK)) { + setCellFocus(cell); + // Grid should have focus when clicked. + getElement().focus(); + } else if (event.getType().equals(BrowserEvents.KEYDOWN)) { + int newRow = rowWithFocus; + RowContainer newContainer = containerWithFocus; + int newColumn = cellFocusRange.getStart(); + + switch (event.getKeyCode()) { + case KeyCodes.KEY_DOWN: + ++newRow; + break; + case KeyCodes.KEY_UP: + --newRow; + break; + case KeyCodes.KEY_RIGHT: + if (cellFocusRange.getEnd() >= getVisibleColumns().size()) { + return; + } + newColumn = cellFocusRange.getEnd(); + break; + case KeyCodes.KEY_LEFT: + if (newColumn == 0) { + return; + } + --newColumn; + break; + case KeyCodes.KEY_TAB: + if (event.getShiftKey()) { + newContainer = getPreviousContainer(containerWithFocus); + } else { + newContainer = getNextContainer(containerWithFocus); + } + + if (newContainer == containerWithFocus) { + return; + } + break; + case KeyCodes.KEY_HOME: + if (newContainer.getRowCount() > 0) { + newRow = 0; + } + break; + case KeyCodes.KEY_END: + if (newContainer.getRowCount() > 0) { + newRow = newContainer.getRowCount() - 1; + } + break; + case KeyCodes.KEY_PAGEDOWN: + case KeyCodes.KEY_PAGEUP: + if (newContainer.getRowCount() > 0) { + boolean down = event.getKeyCode() == KeyCodes.KEY_PAGEDOWN; + // If there is a visible focused cell, scroll by one + // page from its position. Otherwise, use the first or + // the last visible row as the scroll start position. + // This avoids jumping when using both keyboard and the + // scroll bar for scrolling. + int firstVisible = getFirstVisibleRowIndex(); + int lastVisible = getLastVisibleRowIndex(); + if (newRow < firstVisible || newRow > lastVisible) { + newRow = down ? lastVisible : firstVisible; + } + // Scroll by a little less than the visible area to + // account for the possibility that the top and the + // bottom row are only partially visible. + int moveFocusBy = Math.max(1, lastVisible + - firstVisible - 1); + moveFocusBy *= down ? 1 : -1; + newRow += moveFocusBy; + newRow = Math.max(0, Math.min( + newContainer.getRowCount() - 1, newRow)); + } + break; + default: + return; + } + + if (newContainer != containerWithFocus) { + if (newContainer == escalator.getBody()) { + newRow = lastFocusedBodyRow; + } else if (newContainer == escalator.getHeader()) { + newRow = lastFocusedHeaderRow; + } else { + newRow = lastFocusedFooterRow; + } + } else if (newRow < 0) { + newContainer = getPreviousContainer(newContainer); + + if (newContainer == containerWithFocus) { + newRow = 0; + } else if (newContainer == escalator.getBody()) { + newRow = getLastVisibleRowIndex(); + } else { + newRow = newContainer.getRowCount() - 1; + } + } else if (newRow >= containerWithFocus.getRowCount()) { + newContainer = getNextContainer(newContainer); + + if (newContainer == containerWithFocus) { + newRow = containerWithFocus.getRowCount() - 1; + } else if (newContainer == escalator.getBody()) { + newRow = getFirstVisibleRowIndex(); + } else { + newRow = 0; + } + } + + if (newContainer.getRowCount() == 0) { + /* + * There are no rows in the container. Can't change the + * focused cell. + */ + return; + } + + event.preventDefault(); + event.stopPropagation(); + + setCellFocus(newRow, newColumn, newContainer); + } + + } + + private RowContainer getPreviousContainer(RowContainer current) { + if (current == escalator.getFooter()) { + current = escalator.getBody(); + } else if (current == escalator.getBody()) { + current = escalator.getHeader(); + } else { + return current; + } + + if (current.getRowCount() == 0) { + return getPreviousContainer(current); + } + return current; + } + + private RowContainer getNextContainer(RowContainer current) { + if (current == escalator.getHeader()) { + current = escalator.getBody(); + } else if (current == escalator.getBody()) { + current = escalator.getFooter(); + } else { + return current; + } + + if (current.getRowCount() == 0) { + return getNextContainer(current); + } + return current; + } + + private void refreshRow(int row) { + containerWithFocus.refreshRows(row, 1); + } + + /** + * Offsets the focused cell's range. + * + * @param offset + * offset for fixing focused cell's range + */ + public void offsetRangeBy(int offset) { + cellFocusRange = cellFocusRange.offsetBy(offset); + } + + /** + * Informs {@link CellFocusHandler} that certain range of rows has been + * added to the Grid body. {@link CellFocusHandler} will fix indices + * accordingly. + * + * @param added + * a range of added rows + */ + public void rowsAddedToBody(Range added) { + boolean bodyHasFocus = (containerWithFocus == escalator.getBody()); + boolean insertionIsAboveFocusedCell = (added.getStart() <= rowWithFocus); + if (bodyHasFocus && insertionIsAboveFocusedCell) { + rowWithFocus += added.length(); + rowWithFocus = Math.min(rowWithFocus, escalator.getBody() + .getRowCount() - 1); + refreshRow(rowWithFocus); + } + } + + /** + * Informs {@link CellFocusHandler} that certain range of rows has been + * removed from the Grid body. {@link CellFocusHandler} will fix indices + * accordingly. + * + * @param removed + * a range of removed rows + */ + public void rowsRemovedFromBody(Range removed) { + if (containerWithFocus != escalator.getBody()) { + return; + } else if (!removed.contains(rowWithFocus)) { + if (removed.getStart() > rowWithFocus) { + return; + } + rowWithFocus = rowWithFocus - removed.length(); + } else { + if (containerWithFocus.getRowCount() > removed.getEnd()) { + rowWithFocus = removed.getStart(); + } else if (removed.getStart() > 0) { + rowWithFocus = removed.getStart() - 1; + } else { + if (escalator.getHeader().getRowCount() > 0) { + rowWithFocus = Math.min(lastFocusedHeaderRow, escalator + .getHeader().getRowCount() - 1); + containerWithFocus = escalator.getHeader(); + } else if (escalator.getFooter().getRowCount() > 0) { + rowWithFocus = Math.min(lastFocusedFooterRow, escalator + .getFooter().getRowCount() - 1); + containerWithFocus = escalator.getFooter(); + } + } + } + refreshRow(rowWithFocus); + } + } + + public final class SelectionColumn extends Column { + + private boolean initDone = false; + private boolean selected = false; + private CheckBox selectAllCheckBox; + + SelectionColumn(final Renderer selectColumnRenderer) { + super(selectColumnRenderer); + } + + void initDone() { + setWidth(-1); + + setEditable(false); + setResizable(false); + + initDone = true; + } + + @Override + protected void setDefaultHeaderContent(HeaderCell selectionCell) { + /* + * TODO: Currently the select all check box is shown when multi + * selection is in use. This might result in malfunctions if no + * SelectAllHandlers are present. + * + * Later on this could be fixed so that it check such handlers + * exist. + */ + final SelectionModel.Multi model = (Multi) getSelectionModel(); + + if (selectAllCheckBox == null) { + selectAllCheckBox = GWT.create(CheckBox.class); + selectAllCheckBox.setStylePrimaryName(getStylePrimaryName() + + SELECT_ALL_CHECKBOX_CLASSNAME); + selectAllCheckBox + .addValueChangeHandler(new ValueChangeHandler() { + + @Override + public void onValueChange( + ValueChangeEvent event) { + if (event.getValue()) { + fireEvent(new SelectAllEvent(model)); + selected = true; + } else { + model.deselectAll(); + selected = false; + } + } + }); + selectAllCheckBox.setValue(selected); + + addHeaderClickHandler(new HeaderClickHandler() { + @Override + public void onClick(GridClickEvent event) { + CellReference targetCell = event.getTargetCell(); + int defaultRowIndex = getHeader().getRows().indexOf( + getDefaultHeaderRow()); + + if (targetCell.getColumnIndex() == 0 + && targetCell.getRowIndex() == defaultRowIndex) { + selectAllCheckBox.setValue( + !selectAllCheckBox.getValue(), true); + } + } + }); + + // Select all with space when "select all" cell is active + addHeaderKeyUpHandler(new HeaderKeyUpHandler() { + @Override + public void onKeyUp(GridKeyUpEvent event) { + if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE) { + return; + } + HeaderRow targetHeaderRow = getHeader().getRow( + event.getFocusedCell().getRowIndex()); + if (!targetHeaderRow.isDefault()) { + return; + } + if (event.getFocusedCell().getColumn() == SelectionColumn.this) { + // Send events to ensure state is updated + selectAllCheckBox.setValue( + !selectAllCheckBox.getValue(), true); + } + } + }); + } else { + for (HeaderRow row : header.getRows()) { + if (row.getCell(this).getType() == GridStaticCellType.WIDGET) { + // Detach from old header. + row.getCell(this).setText(""); + } + } + } + + selectionCell.setWidget(selectAllCheckBox); + } + + @Override + public Column setWidth(double pixels) { + if (pixels != getWidth() && initDone) { + throw new UnsupportedOperationException("The selection " + + "column cannot be modified after init"); + } else { + super.setWidth(pixels); + } + + return this; + } + + @Override + public Boolean getValue(T row) { + return Boolean.valueOf(isSelected(row)); + } + + @Override + public Column setExpandRatio(int ratio) { + throw new UnsupportedOperationException( + "can't change the expand ratio of the selection column"); + } + + @Override + public int getExpandRatio() { + return 0; + } + + @Override + public Column setMaximumWidth(double pixels) { + throw new UnsupportedOperationException( + "can't change the maximum width of the selection column"); + } + + @Override + public double getMaximumWidth() { + return -1; + } + + @Override + public Column setMinimumWidth(double pixels) { + throw new UnsupportedOperationException( + "can't change the minimum width of the selection column"); + } + + @Override + public double getMinimumWidth() { + return -1; + } + + @Override + public Column setEditable(boolean editable) { + if (initDone) { + throw new UnsupportedOperationException( + "can't set the selection column editable"); + } + super.setEditable(editable); + return this; + } + } + + /** + * Helper class for performing sorting through the user interface. Controls + * the sort() method, reporting USER as the event originator. This is a + * completely internal class, and is, as such, safe to re-name should a more + * descriptive name come to mind. + */ + private final class UserSorter { + + private final Timer timer; + private boolean scheduledMultisort; + private Column column; + + private UserSorter() { + timer = new Timer() { + + @Override + public void run() { + UserSorter.this.sort(column, scheduledMultisort); + } + }; + } + + /** + * Toggle sorting for a cell. If the multisort parameter is set to true, + * the cell's sort order is modified as a natural part of a multi-sort + * chain. If false, the sorting order is set to ASCENDING for that + * cell's column. If that column was already the only sorted column in + * the Grid, the sort direction is flipped. + * + * @param cell + * a valid cell reference + * @param multisort + * whether the sort command should act as a multi-sort stack + * or not + */ + public void sort(Column column, boolean multisort) { + + if (!columns.contains(column)) { + throw new IllegalArgumentException( + "Given column is not a column in this grid. " + + column.toString()); + } + + if (!column.isSortable()) { + return; + } + + final SortOrder so = getSortOrder(column); + + if (multisort) { + + // If the sort order exists, replace existing value with its + // opposite + if (so != null) { + final int idx = sortOrder.indexOf(so); + sortOrder.set(idx, so.getOpposite()); + } else { + // If it doesn't, just add a new sort order to the end of + // the list + sortOrder.add(new SortOrder(column)); + } + + } else { + + // Since we're doing single column sorting, first clear the + // list. Then, if the sort order existed, add its opposite, + // otherwise just add a new sort value + + int items = sortOrder.size(); + sortOrder.clear(); + if (so != null && items == 1) { + sortOrder.add(so.getOpposite()); + } else { + sortOrder.add(new SortOrder(column)); + } + } + + // sortOrder has been changed; tell the Grid to re-sort itself by + // user request. + Grid.this.sort(true); + } + + /** + * Perform a sort after a delay. + * + * @param delay + * delay, in milliseconds + */ + public void sortAfterDelay(int delay, boolean multisort) { + column = eventCell.getColumn(); + scheduledMultisort = multisort; + timer.schedule(delay); + } + + /** + * Check if a delayed sort command has been issued but not yet carried + * out. + * + * @return a boolean value + */ + public boolean isDelayedSortScheduled() { + return timer.isRunning(); + } + + /** + * Cancel a scheduled sort. + */ + public void cancelDelayedSort() { + timer.cancel(); + } + + } + + /** + * @see Grid#autoColumnWidthsRecalculator + */ + private class AutoColumnWidthsRecalculator { + private double lastCalculatedInnerWidth = -1; + + private final ScheduledCommand calculateCommand = new ScheduledCommand() { + + @Override + public void execute() { + if (!isScheduled) { + // something cancelled running this. + return; + } + + if (header.markAsDirty || footer.markAsDirty) { + if (rescheduleCount < 10) { + /* + * Headers and footers are rendered as finally, this way + * we re-schedule this loop as finally, at the end of + * the queue, so that the headers have a chance to + * render themselves. + */ + Scheduler.get().scheduleFinally(this); + rescheduleCount++; + } else { + /* + * We've tried too many times reschedule finally. Seems + * like something is being deferred. Let the queue + * execute and retry again. + */ + rescheduleCount = 0; + Scheduler.get().scheduleDeferred(this); + } + } else if (dataIsBeingFetched) { + Scheduler.get().scheduleDeferred(this); + } else { + calculate(); + } + } + }; + + private int rescheduleCount = 0; + private boolean isScheduled; + + /** + * Calculates and applies column widths, taking into account fixed + * widths and column expand rules + * + * @param immediately + * true if the widths should be executed + * immediately (ignoring lazy loading completely), or + * false if the command should be run after a + * while (duplicate non-immediately invocations are ignored). + * @see Column#setWidth(double) + * @see Column#setExpandRatio(int) + * @see Column#setMinimumWidth(double) + * @see Column#setMaximumWidth(double) + */ + public void schedule() { + if (!isScheduled && isAttached()) { + isScheduled = true; + Scheduler.get().scheduleFinally(calculateCommand); + } + } + + private void calculate() { + isScheduled = false; + rescheduleCount = 0; + + assert !dataIsBeingFetched : "Trying to calculate column widths even though data is still being fetched."; + + if (columnsAreGuaranteedToBeWiderThanGrid()) { + applyColumnWidths(); + } else { + applyColumnWidthsWithExpansion(); + } + + // Update latest width to prevent recalculate on height change. + lastCalculatedInnerWidth = escalator.getInnerWidth(); + } + + private boolean columnsAreGuaranteedToBeWiderThanGrid() { + double freeSpace = escalator.getInnerWidth(); + for (Column column : getVisibleColumns()) { + if (column.getWidth() >= 0) { + freeSpace -= column.getWidth(); + } else if (column.getMinimumWidth() >= 0) { + freeSpace -= column.getMinimumWidth(); + } + } + return freeSpace < 0; + } + + @SuppressWarnings("boxing") + private void applyColumnWidths() { + + /* Step 1: Apply all column widths as they are. */ + + Map selfWidths = new LinkedHashMap(); + List> columns = getVisibleColumns(); + for (int index = 0; index < columns.size(); index++) { + selfWidths.put(index, columns.get(index).getWidth()); + } + Grid.this.escalator.getColumnConfiguration().setColumnWidths( + selfWidths); + + /* + * Step 2: Make sure that each column ends up obeying their min/max + * width constraints if defined as autowidth. If constraints are + * violated, fix it. + */ + + Map constrainedWidths = new LinkedHashMap(); + for (int index = 0; index < columns.size(); index++) { + Column column = columns.get(index); + + boolean hasAutoWidth = column.getWidth() < 0; + if (!hasAutoWidth) { + continue; + } + + // TODO: bug: these don't honor the CSS max/min. :( + double actualWidth = column.getWidthActual(); + if (actualWidth < getMinWidth(column)) { + constrainedWidths.put(index, column.getMinimumWidth()); + } else if (actualWidth > getMaxWidth(column)) { + constrainedWidths.put(index, column.getMaximumWidth()); + } + } + Grid.this.escalator.getColumnConfiguration().setColumnWidths( + constrainedWidths); + } + + private void applyColumnWidthsWithExpansion() { + boolean defaultExpandRatios = true; + int totalRatios = 0; + double reservedPixels = 0; + final Set> columnsToExpand = new HashSet>(); + List> nonFixedColumns = new ArrayList>(); + Map columnSizes = new HashMap(); + final List> visibleColumns = getVisibleColumns(); + + /* + * Set all fixed widths and also calculate the size-to-fit widths + * for the autocalculated columns. + * + * This way we know with how many pixels we have left to expand the + * rest. + */ + for (Column column : visibleColumns) { + final double widthAsIs = column.getWidth(); + final boolean isFixedWidth = widthAsIs >= 0; + // Check for max width just to be sure we don't break the limits + final double widthFixed = Math.max( + Math.min(getMaxWidth(column), widthAsIs), + column.getMinimumWidth()); + defaultExpandRatios = defaultExpandRatios + && (column.getExpandRatio() == -1 || column == selectionColumn); + + if (isFixedWidth) { + columnSizes.put(visibleColumns.indexOf(column), widthFixed); + reservedPixels += widthFixed; + } else { + nonFixedColumns.add(column); + columnSizes.put(visibleColumns.indexOf(column), -1.0d); + } + } + + setColumnSizes(columnSizes); + + for (Column column : nonFixedColumns) { + final int expandRatio = (defaultExpandRatios ? 1 : column + .getExpandRatio()); + final double maxWidth = getMaxWidth(column); + final double newWidth = Math.min(maxWidth, + column.getWidthActual()); + boolean shouldExpand = newWidth < maxWidth && expandRatio > 0 + && column != selectionColumn; + if (shouldExpand) { + totalRatios += expandRatio; + columnsToExpand.add(column); + } + reservedPixels += newWidth; + columnSizes.put(visibleColumns.indexOf(column), newWidth); + } + + /* + * Now that we know how many pixels we need at the very least, we + * can distribute the remaining pixels to all columns according to + * their expand ratios. + */ + double pixelsToDistribute = escalator.getInnerWidth() + - reservedPixels; + if (pixelsToDistribute <= 0 || totalRatios <= 0) { + if (pixelsToDistribute <= 0) { + // Set column sizes for expanding columns + setColumnSizes(columnSizes); + } + + return; + } + + /* + * Check for columns that hit their max width. Adjust + * pixelsToDistribute and totalRatios accordingly. Recheck. Stop + * when no new columns hit their max width + */ + boolean aColumnHasMaxedOut; + do { + aColumnHasMaxedOut = false; + final double widthPerRatio = pixelsToDistribute / totalRatios; + final Iterator> i = columnsToExpand.iterator(); + while (i.hasNext()) { + final Column column = i.next(); + final int expandRatio = getExpandRatio(column, + defaultExpandRatios); + final int columnIndex = visibleColumns.indexOf(column); + final double autoWidth = columnSizes.get(columnIndex); + final double maxWidth = getMaxWidth(column); + double expandedWidth = autoWidth + widthPerRatio + * expandRatio; + + if (maxWidth <= expandedWidth) { + i.remove(); + totalRatios -= expandRatio; + aColumnHasMaxedOut = true; + pixelsToDistribute -= maxWidth - autoWidth; + columnSizes.put(columnIndex, maxWidth); + } + } + } while (aColumnHasMaxedOut); + + if (totalRatios <= 0 && columnsToExpand.isEmpty()) { + setColumnSizes(columnSizes); + return; + } + assert pixelsToDistribute > 0 : "We've run out of pixels to distribute (" + + pixelsToDistribute + + "px to " + + totalRatios + + " ratios between " + columnsToExpand.size() + " columns)"; + assert totalRatios > 0 && !columnsToExpand.isEmpty() : "Bookkeeping out of sync. Ratios: " + + totalRatios + " Columns: " + columnsToExpand.size(); + + /* + * If we still have anything left, distribute the remaining pixels + * to the remaining columns. + */ + final double widthPerRatio; + int leftOver = 0; + if (BrowserInfo.get().isIE8() || BrowserInfo.get().isIE9() + || BrowserInfo.getBrowserString().contains("PhantomJS")) { + // These browsers report subpixels as integers. this usually + // results into issues.. + widthPerRatio = (int) (pixelsToDistribute / totalRatios); + leftOver = (int) (pixelsToDistribute - widthPerRatio + * totalRatios); + } else { + widthPerRatio = pixelsToDistribute / totalRatios; + } + for (Column column : columnsToExpand) { + final int expandRatio = getExpandRatio(column, + defaultExpandRatios); + final int columnIndex = visibleColumns.indexOf(column); + final double autoWidth = columnSizes.get(columnIndex); + double totalWidth = autoWidth + widthPerRatio * expandRatio; + if (leftOver > 0) { + totalWidth += 1; + leftOver--; + } + columnSizes.put(columnIndex, totalWidth); + + totalRatios -= expandRatio; + } + assert totalRatios == 0 : "Bookkeeping error: there were still some ratios left undistributed: " + + totalRatios; + + /* + * Check the guarantees for minimum width and scoot back the columns + * that don't care. + */ + boolean minWidthsCausedReflows; + do { + minWidthsCausedReflows = false; + + /* + * First, let's check which columns were too cramped, and expand + * them. Also keep track on how many pixels we grew - we need to + * remove those pixels from other columns + */ + double pixelsToRemoveFromOtherColumns = 0; + for (Column column : visibleColumns) { + /* + * We can't iterate over columnsToExpand, even though that + * would be convenient. This is because some column without + * an expand ratio might still have a min width - those + * wouldn't show up in that set. + */ + + double minWidth = getMinWidth(column); + final int columnIndex = visibleColumns.indexOf(column); + double currentWidth = columnSizes.get(columnIndex); + boolean hasAutoWidth = column.getWidth() < 0; + if (hasAutoWidth && currentWidth < minWidth) { + columnSizes.put(columnIndex, minWidth); + pixelsToRemoveFromOtherColumns += (minWidth - currentWidth); + minWidthsCausedReflows = true; + + /* + * Remove this column form the set if it exists. This + * way we make sure that it doesn't get shrunk in the + * next step. + */ + columnsToExpand.remove(column); + } + } + + /* + * Now we need to shrink the remaining columns according to + * their ratios. Recalculate the sum of remaining ratios. + */ + totalRatios = 0; + for (Column column : columnsToExpand) { + totalRatios += getExpandRatio(column, defaultExpandRatios); + } + final double pixelsToRemovePerRatio = pixelsToRemoveFromOtherColumns + / totalRatios; + for (Column column : columnsToExpand) { + final double pixelsToRemove = pixelsToRemovePerRatio + * getExpandRatio(column, defaultExpandRatios); + int colIndex = visibleColumns.indexOf(column); + columnSizes.put(colIndex, columnSizes.get(colIndex) + - pixelsToRemove); + } + + } while (minWidthsCausedReflows); + + // Finally set all the column sizes. + setColumnSizes(columnSizes); + } + + private void setColumnSizes(Map columnSizes) { + // Set all widths at once + escalator.getColumnConfiguration().setColumnWidths(columnSizes); + } + + private int getExpandRatio(Column column, + boolean defaultExpandRatios) { + int expandRatio = column.getExpandRatio(); + if (expandRatio > 0) { + return expandRatio; + } else if (expandRatio < 0) { + assert defaultExpandRatios : "No columns should've expanded"; + return 1; + } else { + assert false : "this method should've not been called at all if expandRatio is 0"; + return 0; + } + } + + /** + * Returns the maximum width of the column, or {@link Double#MAX_VALUE} + * if defined as negative. + */ + private double getMaxWidth(Column column) { + double maxWidth = column.getMaximumWidth(); + if (maxWidth >= 0) { + return maxWidth; + } else { + return Double.MAX_VALUE; + } + } + + /** + * Returns the minimum width of the column, or {@link Double#MIN_VALUE} + * if defined as negative. + */ + private double getMinWidth(Column column) { + double minWidth = column.getMinimumWidth(); + if (minWidth >= 0) { + return minWidth; + } else { + return Double.MIN_VALUE; + } + } + + /** + * Check whether the auto width calculation is currently scheduled. + * + * @return true if auto width calculation is currently + * scheduled + */ + public boolean isScheduled() { + return isScheduled; + } + } + + private class GridSpacerUpdater implements SpacerUpdater { + + private static final String STRIPE_CLASSNAME = "stripe"; + + private final Map elementToWidgetMap = new HashMap(); + + @Override + public void init(Spacer spacer) { + initTheming(spacer); + + int rowIndex = spacer.getRow(); + + Widget detailsWidget = null; + try { + detailsWidget = detailsGenerator.getDetails(rowIndex); + } catch (Throwable e) { + getLogger().log( + Level.SEVERE, + "Exception while generating details for row " + + rowIndex, e); + } + + final double spacerHeight; + Element spacerElement = spacer.getElement(); + if (detailsWidget == null) { + spacerElement.removeAllChildren(); + spacerHeight = DETAILS_ROW_INITIAL_HEIGHT; + } else { + Element element = detailsWidget.getElement(); + spacerElement.appendChild(element); + setParent(detailsWidget, Grid.this); + Widget previousWidget = elementToWidgetMap.put(element, + detailsWidget); + + assert previousWidget == null : "Overwrote a pre-existing widget on row " + + rowIndex + " without proper removal first."; + + /* + * Once we have the content properly inside the DOM, we should + * re-measure it to make sure that it's the correct height. + * + * This is rather tricky, since the row (tr) will get the + * height, but the spacer cell (td) has the borders, which + * should go on top of the previous row and next row. + */ + double contentHeight; + if (detailsGenerator instanceof HeightAwareDetailsGenerator) { + HeightAwareDetailsGenerator sadg = (HeightAwareDetailsGenerator) detailsGenerator; + contentHeight = sadg.getDetailsHeight(rowIndex); + } else { + contentHeight = WidgetUtil + .getRequiredHeightBoundingClientRectDouble(element); + } + double borderTopAndBottomHeight = WidgetUtil + .getBorderTopAndBottomThickness(spacerElement); + double measuredHeight = contentHeight + + borderTopAndBottomHeight; + assert getElement().isOrHasChild(spacerElement) : "The spacer element wasn't in the DOM during measurement, but was assumed to be."; + spacerHeight = measuredHeight; + } + + escalator.getBody().setSpacer(rowIndex, spacerHeight); + } + + @Override + public void destroy(Spacer spacer) { + Element spacerElement = spacer.getElement(); + + assert getElement().isOrHasChild(spacerElement) : "Trying " + + "to destroy a spacer that is not connected to this " + + "Grid's DOM. (row: " + spacer.getRow() + ", element: " + + spacerElement + ")"; + + Widget detailsWidget = elementToWidgetMap.remove(spacerElement + .getFirstChildElement()); + + if (detailsWidget != null) { + /* + * The widget may be null here if the previous generator + * returned a null widget. + */ + + assert spacerElement.getFirstChild() != null : "The " + + "details row to destroy did not contain a widget - " + + "probably removed by something else without " + + "permission? (row: " + spacer.getRow() + + ", element: " + spacerElement + ")"; + + setParent(detailsWidget, null); + spacerElement.removeAllChildren(); + } + } + + private void initTheming(Spacer spacer) { + Element spacerRoot = spacer.getElement(); + + if (spacer.getRow() % 2 == 1) { + spacerRoot.getParentElement().addClassName(STRIPE_CLASSNAME); + } else { + spacerRoot.getParentElement().removeClassName(STRIPE_CLASSNAME); + } + } + + } + + /** + * Sidebar displaying toggles for hidable columns and custom widgets + * provided by the application. + *

+ * The button for opening the sidebar is automatically visible inside the + * grid, if it contains any column hiding options or custom widgets. The + * column hiding toggles and custom widgets become visible once the sidebar + * has been opened. + * + * @since 7.5.0 + */ + private static class Sidebar extends Composite implements HasEnabled { + + private final ClickHandler openCloseButtonHandler = new ClickHandler() { + + @Override + public void onClick(ClickEvent event) { + if (!isOpen()) { + open(); + } else { + close(); + } + } + }; + + private final FlowPanel rootContainer; + + private final FlowPanel content; + + private final MenuBar menuBar; + + private final Button openCloseButton; + + private final Grid grid; + + private Overlay overlay; + + private Sidebar(Grid grid) { + this.grid = grid; + + rootContainer = new FlowPanel(); + initWidget(rootContainer); + + openCloseButton = new Button(); + + openCloseButton.addClickHandler(openCloseButtonHandler); + + rootContainer.add(openCloseButton); + + content = new FlowPanel() { + @Override + public boolean remove(Widget w) { + // Check here to catch child.removeFromParent() calls + boolean removed = super.remove(w); + if (removed) { + updateVisibility(); + } + + return removed; + } + }; + + createOverlay(); + + menuBar = new MenuBar(true) { + + @Override + public MenuItem insertItem(MenuItem item, int beforeIndex) + throws IndexOutOfBoundsException { + if (getParent() == null) { + content.insert(this, 0); + updateVisibility(); + } + return super.insertItem(item, beforeIndex); + } + + @Override + public void removeItem(MenuItem item) { + super.removeItem(item); + if (getItems().isEmpty()) { + menuBar.removeFromParent(); + } + } + + @Override + public void onBrowserEvent(Event event) { + // selecting a item with enter will lose the focus and + // selected item, which means that further keyboard + // selection won't work unless we do this: + if (event.getTypeInt() == Event.ONKEYDOWN + && event.getKeyCode() == KeyCodes.KEY_ENTER) { + final MenuItem item = getSelectedItem(); + super.onBrowserEvent(event); + Scheduler.get().scheduleDeferred( + new ScheduledCommand() { + + @Override + public void execute() { + selectItem(item); + focus(); + } + }); + + } else { + super.onBrowserEvent(event); + } + } + + }; + KeyDownHandler keyDownHandler = new KeyDownHandler() { + + @Override + public void onKeyDown(KeyDownEvent event) { + if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) { + close(); + } + } + }; + openCloseButton.addDomHandler(keyDownHandler, + KeyDownEvent.getType()); + menuBar.addDomHandler(keyDownHandler, KeyDownEvent.getType()); + } + + /** + * Creates and initializes the overlay. + */ + private void createOverlay() { + overlay = GWT.create(Overlay.class); + overlay.setOwner(grid); + overlay.setAutoHideEnabled(true); + overlay.addStyleDependentName("popup"); + overlay.add(content); + overlay.addAutoHidePartner(rootContainer.getElement()); + overlay.addCloseHandler(new CloseHandler() { + @Override + public void onClose(CloseEvent event) { + removeStyleName("open"); + addStyleName("closed"); + } + }); + } + + /** + * Opens the sidebar if not yet opened. Opening the sidebar has no + * effect if it is empty. + */ + public void open() { + if (!isOpen() && isInDOM()) { + addStyleName("open"); + removeStyleName("closed"); + overlay.showRelativeTo(rootContainer); + } + } + + /** + * Closes the sidebar if not yet closed. + */ + public void close() { + overlay.hide(); + } + + /** + * Returns whether the sidebar is open or not. + * + * @return true if open, false if not + */ + public boolean isOpen() { + return overlay != null && overlay.isShowing(); + } + + @Override + public void setStylePrimaryName(String styleName) { + super.setStylePrimaryName(styleName); + overlay.setStylePrimaryName(styleName); + content.setStylePrimaryName(styleName + "-content"); + openCloseButton.setStylePrimaryName(styleName + "-button"); + if (isOpen()) { + addStyleName("open"); + removeStyleName("closed"); + } else { + removeStyleName("open"); + addStyleName("closed"); + } + } + + @Override + public void addStyleName(String style) { + super.addStyleName(style); + overlay.addStyleName(style); + } + + @Override + public void removeStyleName(String style) { + super.removeStyleName(style); + overlay.removeStyleName(style); + } + + private void setHeightToHeaderCellHeight() { + RowContainer header = grid.escalator.getHeader(); + if (header.getRowCount() == 0 + || !header.getRowElement(0).hasChildNodes()) { + getLogger() + .info("No header cell available when calculating sidebar button height"); + openCloseButton.setHeight(header.getDefaultRowHeight() + "px"); + + return; + } + + Element firstHeaderCell = header.getRowElement(0) + .getFirstChildElement(); + double height = WidgetUtil + .getRequiredHeightBoundingClientRectDouble(firstHeaderCell) + - (WidgetUtil.measureVerticalBorder(getElement()) / 2); + openCloseButton.setHeight(height + "px"); + } + + private void updateVisibility() { + final boolean hasWidgets = content.getWidgetCount() > 0; + final boolean isVisible = isInDOM(); + if (isVisible && !hasWidgets) { + Grid.setParent(this, null); + getElement().removeFromParent(); + } else if (!isVisible && hasWidgets) { + close(); + grid.getElement().appendChild(getElement()); + Grid.setParent(this, grid); + // border calculation won't work until attached + setHeightToHeaderCellHeight(); + } + } + + private boolean isInDOM() { + return getParent() != null; + } + + @Override + protected void onAttach() { + super.onAttach(); + // make sure the button will get correct height if the button should + // be visible when the grid is rendered the first time. + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + + @Override + public void execute() { + setHeightToHeaderCellHeight(); + } + }); + } + + @Override + public boolean isEnabled() { + return openCloseButton.isEnabled(); + } + + @Override + public void setEnabled(boolean enabled) { + if (!enabled && isOpen()) { + close(); + } + + openCloseButton.setEnabled(enabled); + } + } + + /** + * UI and functionality related to hiding columns with toggles in the + * sidebar. + */ + private final class ColumnHider { + + /** Map from columns to their hiding toggles, component might change */ + private HashMap, MenuItem> columnToHidingToggleMap = new HashMap, MenuItem>(); + + /** + * When column is being hidden with a toggle, do not refresh toggles for + * no reason. Also helps for keeping the keyboard navigation working. + */ + private boolean hidingColumn; + + private void updateColumnHidable(final Column column) { + if (column.isHidable()) { + MenuItem toggle = columnToHidingToggleMap.get(column); + if (toggle == null) { + toggle = createToggle(column); + } + toggle.setStyleName("hidden", column.isHidden()); + } else if (columnToHidingToggleMap.containsKey(column)) { + sidebar.menuBar.removeItem((columnToHidingToggleMap + .remove(column))); + } + updateTogglesOrder(); + } + + private MenuItem createToggle(final Column column) { + MenuItem toggle = new MenuItem(createHTML(column), true, + new ScheduledCommand() { + + @Override + public void execute() { + hidingColumn = true; + column.setHidden(!column.isHidden(), true); + hidingColumn = false; + } + }); + toggle.addStyleName("column-hiding-toggle"); + columnToHidingToggleMap.put(column, toggle); + return toggle; + } + + private String createHTML(Column column) { + final StringBuffer buf = new StringBuffer(); + buf.append("

"); + String caption = column.getHidingToggleCaption(); + if (caption == null) { + caption = column.headerCaption; + } + buf.append(caption); + buf.append("
"); + + return buf.toString(); + } + + private void updateTogglesOrder() { + if (!hidingColumn) { + int lastIndex = 0; + for (Column column : getColumns()) { + if (column.isHidable()) { + final MenuItem menuItem = columnToHidingToggleMap + .get(column); + sidebar.menuBar.removeItem(menuItem); + sidebar.menuBar.insertItem(menuItem, lastIndex++); + } + } + } + } + + private void updateHidingToggle(Column column) { + if (column.isHidable()) { + MenuItem toggle = columnToHidingToggleMap.get(column); + toggle.setHTML(createHTML(column)); + toggle.setStyleName("hidden", column.isHidden()); + } // else we can just ignore + } + + private void removeColumnHidingToggle(Column column) { + sidebar.menuBar.removeItem(columnToHidingToggleMap.get(column)); + } + + } + + /** + * Escalator used internally by grid to render the rows + */ + private Escalator escalator = GWT.create(Escalator.class); + + private final Header header = GWT.create(Header.class); + + private final Footer footer = GWT.create(Footer.class); + + private final Sidebar sidebar = new Sidebar(this); + + /** + * List of columns in the grid. Order defines the visible order. + */ + private List> columns = new ArrayList>(); + + /** + * The datasource currently in use. Note: it is null + * on initialization, but not after that. + */ + private DataSource dataSource; + + /** + * Currently available row range in DataSource. + */ + private Range currentDataAvailable = Range.withLength(0, 0); + + /** + * The number of frozen columns, 0 freezes the selection column if + * displayed, -1 also prevents selection col from freezing. + */ + private int frozenColumnCount = 0; + + /** + * Current sort order. The (private) sort() method reads this list to + * determine the order in which to present rows. + */ + private List sortOrder = new ArrayList(); + + private Renderer selectColumnRenderer = null; + + private SelectionColumn selectionColumn; + + private String rowStripeStyleName; + private String rowHasDataStyleName; + private String rowSelectedStyleName; + private String cellFocusStyleName; + private String rowFocusStyleName; + + /** + * Current selection model. + */ + private SelectionModel selectionModel; + + protected final CellFocusHandler cellFocusHandler; + + private final UserSorter sorter = new UserSorter(); + + private final Editor editor = GWT.create(Editor.class); + + private boolean dataIsBeingFetched = false; + + /** + * The cell a click event originated from + *

+ * This is a workaround to make Chrome work like Firefox. In Chrome, + * normally if you start a drag on one cell and release on: + *

    + *
  • that same cell, the click event is that {@code }. + *
  • a cell on that same row, the click event is the parent {@code }. + *
  • a cell on another row, the click event is the table section ancestor + * ({@code }, {@code } or {@code }). + *
+ * + * @see #onBrowserEvent(Event) + */ + private Cell cellOnPrevMouseDown; + + /** + * A scheduled command to re-evaluate the widths of all columns + * that have calculated widths. Most probably called because + * minwidth/maxwidth/expandratio has changed. + */ + private final AutoColumnWidthsRecalculator autoColumnWidthsRecalculator = new AutoColumnWidthsRecalculator(); + + private boolean enabled = true; + + private DetailsGenerator detailsGenerator = DetailsGenerator.NULL; + private GridSpacerUpdater gridSpacerUpdater = new GridSpacerUpdater(); + /** A set keeping track of the indices of all currently open details */ + private Set visibleDetails = new HashSet(); + + private boolean columnReorderingAllowed; + + private ColumnHider columnHider = new ColumnHider(); + + private DragAndDropHandler dndHandler = new DragAndDropHandler(); + + private AutoScroller autoScroller = new AutoScroller(this); + + private DragAndDropHandler.DragAndDropCallback headerCellDndCallback = new DragAndDropCallback() { + + private final AutoScrollerCallback autoScrollerCallback = new AutoScrollerCallback() { + + @Override + public void onAutoScroll(int scrollDiff) { + autoScrollX = scrollDiff; + onDragUpdate(null); + } + + @Override + public void onAutoScrollReachedMin() { + // make sure the drop marker is visible on the left + autoScrollX = 0; + updateDragDropMarker(clientX); + } + + @Override + public void onAutoScrollReachedMax() { + // make sure the drop marker is visible on the right + autoScrollX = 0; + updateDragDropMarker(clientX); + } + }; + /** + * Elements for displaying the dragged column(s) and drop marker + * properly + */ + private Element table; + private Element tableHeader; + /** Marks the column drop location */ + private Element dropMarker; + /** A copy of the dragged column(s), moves with cursor. */ + private Element dragElement; + /** Tracks index of the column whose left side the drop would occur */ + private int latestColumnDropIndex; + /** + * Map of possible drop positions for the column and the corresponding + * column index. + */ + private final TreeMap possibleDropPositions = new TreeMap(); + /** + * Makes sure that drag cancel doesn't cause anything unwanted like sort + */ + private HandlerRegistration columnSortPreventRegistration; + + private int clientX; + + /** How much the grid is being auto scrolled while dragging. */ + private int autoScrollX; + + /** Captures the value of the focused column before reordering */ + private int focusedColumnIndex; + + /** Offset caused by the drag and drop marker width */ + private double dropMarkerWidthOffset; + + private void initHeaderDragElementDOM() { + if (table == null) { + tableHeader = DOM.createTHead(); + dropMarker = DOM.createDiv(); + tableHeader.appendChild(dropMarker); + table = DOM.createTable(); + table.appendChild(tableHeader); + table.setClassName("header-drag-table"); + } + // update the style names on each run in case primary name has been + // modified + tableHeader.setClassName(escalator.getHeader().getElement() + .getClassName()); + dropMarker.setClassName(getStylePrimaryName() + "-drop-marker"); + int topOffset = 0; + for (int i = 0; i < eventCell.getRowIndex(); i++) { + topOffset += escalator.getHeader().getRowElement(i) + .getFirstChildElement().getOffsetHeight(); + } + tableHeader.getStyle().setTop(topOffset, Unit.PX); + + getElement().appendChild(table); + + dropMarkerWidthOffset = WidgetUtil + .getRequiredWidthBoundingClientRectDouble(dropMarker) / 2; + } + + @Override + public void onDragUpdate(Event e) { + if (e != null) { + clientX = WidgetUtil.getTouchOrMouseClientX(e); + autoScrollX = 0; + } + resolveDragElementHorizontalPosition(clientX); + updateDragDropMarker(clientX); + } + + private void updateDragDropMarker(final int clientX) { + final double scrollLeft = getScrollLeft(); + final double cursorXCoordinate = clientX + - escalator.getHeader().getElement().getAbsoluteLeft(); + final Entry cellEdgeOnRight = possibleDropPositions + .ceilingEntry(cursorXCoordinate); + final Entry cellEdgeOnLeft = possibleDropPositions + .floorEntry(cursorXCoordinate); + final double diffToRightEdge = cellEdgeOnRight == null ? Double.MAX_VALUE + : cellEdgeOnRight.getKey() - cursorXCoordinate; + final double diffToLeftEdge = cellEdgeOnLeft == null ? Double.MAX_VALUE + : cursorXCoordinate - cellEdgeOnLeft.getKey(); + + double dropMarkerLeft = 0 - scrollLeft; + if (diffToRightEdge > diffToLeftEdge) { + latestColumnDropIndex = cellEdgeOnLeft.getValue(); + dropMarkerLeft += cellEdgeOnLeft.getKey(); + } else { + latestColumnDropIndex = cellEdgeOnRight.getValue(); + dropMarkerLeft += cellEdgeOnRight.getKey(); + } + + dropMarkerLeft += autoScrollX; + + final double frozenColumnsWidth = autoScroller + .getFrozenColumnsWidth(); + final double rightBoundaryForDrag = getSidebarBoundaryComparedTo(dropMarkerLeft); + final int visibleColumns = getVisibleColumns().size(); + + // First check if the drop marker should move left because of the + // sidebar opening button. this only the case if the grid is + // scrolled to the right + if (latestColumnDropIndex == visibleColumns + && rightBoundaryForDrag < dropMarkerLeft + && dropMarkerLeft <= escalator.getInnerWidth()) { + dropMarkerLeft = rightBoundaryForDrag - dropMarkerWidthOffset; + } + + // Check if the drop marker shouldn't be shown at all + else if (dropMarkerLeft < frozenColumnsWidth + || dropMarkerLeft > Math.min(rightBoundaryForDrag, + escalator.getInnerWidth()) || dropMarkerLeft < 0) { + dropMarkerLeft = -10000000; + } + dropMarker.getStyle().setLeft(dropMarkerLeft, Unit.PX); + } + + private void resolveDragElementHorizontalPosition(final int clientX) { + double left = clientX - table.getAbsoluteLeft(); + + // Do not show the drag element beyond a spanned header cell + // limitation + final Double leftBound = possibleDropPositions.firstKey(); + final Double rightBound = possibleDropPositions.lastKey(); + final double scrollLeft = getScrollLeft(); + if (left + scrollLeft < leftBound) { + left = leftBound - scrollLeft + autoScrollX; + } else if (left + scrollLeft > rightBound) { + left = rightBound - scrollLeft + autoScrollX; + } + + // Do not show the drag element beyond the grid + final double sidebarBoundary = getSidebarBoundaryComparedTo(left); + final double gridBoundary = escalator.getInnerWidth(); + final double rightBoundary = Math + .min(sidebarBoundary, gridBoundary); + + // Do not show on left of the frozen columns (even if scrolled) + final int frozenColumnsWidth = (int) autoScroller + .getFrozenColumnsWidth(); + + left = Math.max(frozenColumnsWidth, Math.min(left, rightBoundary)); + + left -= dragElement.getClientWidth() / 2; + dragElement.getStyle().setLeft(left, Unit.PX); + } + + private boolean isSidebarOnDraggedRow() { + return eventCell.getRowIndex() == 0 && sidebar.isInDOM() + && !sidebar.isOpen(); + } + + /** + * Returns the sidebar left coordinate, in relation to the grid. Or + * Double.MAX_VALUE if it doesn't cause a boundary. + */ + private double getSidebarBoundaryComparedTo(double left) { + if (isSidebarOnDraggedRow()) { + double absoluteLeft = left + getElement().getAbsoluteLeft(); + double sidebarLeft = sidebar.getElement().getAbsoluteLeft(); + double diff = absoluteLeft - sidebarLeft; + + if (diff > 0) { + return left - diff; + } + } + return Double.MAX_VALUE; + } + + @Override + public boolean onDragStart(Event e) { + calculatePossibleDropPositions(); + + if (possibleDropPositions.isEmpty()) { + return false; + } + + initHeaderDragElementDOM(); + // needs to clone focus and sorting indicators too (UX) + dragElement = DOM.clone(eventCell.getElement(), true); + dragElement.getStyle().clearWidth(); + dropMarker.getStyle().setProperty("height", + dragElement.getStyle().getHeight()); + tableHeader.appendChild(dragElement); + // mark the column being dragged for styling + eventCell.getElement().addClassName("dragged"); + // mark the floating cell, for styling & testing + dragElement.addClassName("dragged-column-header"); + + // start the auto scroll handler + autoScroller.setScrollArea(60); + autoScroller.start(e, ScrollAxis.HORIZONTAL, autoScrollerCallback); + return true; + } + + @Override + public void onDragEnd() { + table.removeFromParent(); + dragElement.removeFromParent(); + eventCell.getElement().removeClassName("dragged"); + } + + @Override + public void onDrop() { + final int draggedColumnIndex = eventCell.getColumnIndex(); + final int colspan = header.getRow(eventCell.getRowIndex()) + .getCell(eventCell.getColumn()).getColspan(); + if (latestColumnDropIndex != draggedColumnIndex + && latestColumnDropIndex != (draggedColumnIndex + colspan)) { + List> columns = getColumns(); + List> reordered = new ArrayList>(); + if (draggedColumnIndex < latestColumnDropIndex) { + reordered.addAll(columns.subList(0, draggedColumnIndex)); + reordered.addAll(columns.subList(draggedColumnIndex + + colspan, latestColumnDropIndex)); + reordered.addAll(columns.subList(draggedColumnIndex, + draggedColumnIndex + colspan)); + reordered.addAll(columns.subList(latestColumnDropIndex, + columns.size())); + } else { + reordered.addAll(columns.subList(0, latestColumnDropIndex)); + reordered.addAll(columns.subList(draggedColumnIndex, + draggedColumnIndex + colspan)); + reordered.addAll(columns.subList(latestColumnDropIndex, + draggedColumnIndex)); + reordered.addAll(columns.subList(draggedColumnIndex + + colspan, columns.size())); + } + reordered.remove(selectionColumn); // since setColumnOrder will + // add it anyway! + + // capture focused cell column before reorder + Cell focusedCell = cellFocusHandler.getFocusedCell(); + if (focusedCell != null) { + // take hidden columns into account + focusedColumnIndex = getColumns().indexOf( + getVisibleColumn(focusedCell.getColumn())); + } + + Column[] array = reordered.toArray(new Column[reordered + .size()]); + setColumnOrder(array); + transferCellFocusOnDrop(); + } // else no reordering + } + + private void transferCellFocusOnDrop() { + final Cell focusedCell = cellFocusHandler.getFocusedCell(); + if (focusedCell != null) { + final int focusedColumnIndexDOM = focusedCell.getColumn(); + final int focusedRowIndex = focusedCell.getRow(); + final int draggedColumnIndex = eventCell.getColumnIndex(); + // transfer focus if it was effected by the new column order + final RowContainer rowContainer = escalator + .findRowContainer(focusedCell.getElement()); + if (focusedColumnIndex == draggedColumnIndex) { + // move with the dragged column + int adjustedDropIndex = latestColumnDropIndex > draggedColumnIndex ? latestColumnDropIndex - 1 + : latestColumnDropIndex; + // remove hidden columns from indexing + adjustedDropIndex = getVisibleColumns().indexOf( + getColumn(adjustedDropIndex)); + cellFocusHandler.setCellFocus(focusedRowIndex, + adjustedDropIndex, rowContainer); + } else if (latestColumnDropIndex <= focusedColumnIndex + && draggedColumnIndex > focusedColumnIndex) { + cellFocusHandler.setCellFocus(focusedRowIndex, + focusedColumnIndexDOM + 1, rowContainer); + } else if (latestColumnDropIndex > focusedColumnIndex + && draggedColumnIndex < focusedColumnIndex) { + cellFocusHandler.setCellFocus(focusedRowIndex, + focusedColumnIndexDOM - 1, rowContainer); + } + } + } + + @Override + public void onDragCancel() { + // cancel next click so that we may prevent column sorting if + // mouse was released on top of the dragged cell + if (columnSortPreventRegistration == null) { + columnSortPreventRegistration = Event + .addNativePreviewHandler(new NativePreviewHandler() { + + @Override + public void onPreviewNativeEvent( + NativePreviewEvent event) { + if (event.getTypeInt() == Event.ONCLICK) { + event.cancel(); + event.getNativeEvent().preventDefault(); + columnSortPreventRegistration + .removeHandler(); + columnSortPreventRegistration = null; + } + } + }); + } + autoScroller.stop(); + } + + /** + * Returns the amount of frozen columns. The selection column is always + * considered frozen, since it can't be moved. + */ + private int getSelectionAndFrozenColumnCount() { + // no matter if selection column is frozen or not, it is considered + // frozen for column dnd reorder + if (getSelectionModel().getSelectionColumnRenderer() != null) { + return Math.max(0, getFrozenColumnCount()) + 1; + } else { + return Math.max(0, getFrozenColumnCount()); + } + } + + @SuppressWarnings("boxing") + private void calculatePossibleDropPositions() { + possibleDropPositions.clear(); + + final int draggedColumnIndex = eventCell.getColumnIndex(); + final StaticRow draggedCellRow = header.getRow(eventCell + .getRowIndex()); + final int draggedColumnRightIndex = draggedColumnIndex + + draggedCellRow.getCell(eventCell.getColumn()) + .getColspan(); + final int frozenColumns = getSelectionAndFrozenColumnCount(); + final Range draggedCellRange = Range.between(draggedColumnIndex, + draggedColumnRightIndex); + /* + * If the dragged cell intersects with a spanned cell in any other + * header or footer row, then the drag is limited inside that + * spanned cell. The same rules apply: the cell can't be dropped + * inside another spanned cell. The left and right bounds keep track + * of the edges of the most limiting spanned cell. + */ + int leftBound = -1; + int rightBound = getColumnCount() + 1; + + final HashSet unavailableColumnDropIndices = new HashSet(); + final List> rows = new ArrayList>(); + rows.addAll(header.getRows()); + rows.addAll(footer.getRows()); + for (StaticRow row : rows) { + if (!row.hasSpannedCells()) { + continue; + } + final boolean isDraggedCellRow = row.equals(draggedCellRow); + for (int cellColumnIndex = frozenColumns; cellColumnIndex < getColumnCount(); cellColumnIndex++) { + StaticCell cell = row.getCell(getColumn(cellColumnIndex)); + int colspan = cell.getColspan(); + if (colspan <= 1) { + continue; + } + final int cellColumnRightIndex = cellColumnIndex + colspan; + final Range cellRange = Range.between(cellColumnIndex, + cellColumnRightIndex); + final boolean intersects = draggedCellRange + .intersects(cellRange); + if (intersects && !isDraggedCellRow) { + // if the currently iterated cell is inside or same as + // the dragged cell, then it doesn't restrict the drag + if (cellRange.isSubsetOf(draggedCellRange)) { + cellColumnIndex = cellColumnRightIndex - 1; + continue; + } + /* + * if the dragged cell is a spanned cell and it crosses + * with the currently iterated cell without sharing + * either start or end then not possible to drag the + * cell. + */ + if (!draggedCellRange.isSubsetOf(cellRange)) { + return; + } + // the spanned cell overlaps the dragged cell (but is + // not the dragged cell) + if (cellColumnIndex <= draggedColumnIndex + && cellColumnIndex > leftBound) { + leftBound = cellColumnIndex; + } + if (cellColumnRightIndex < rightBound) { + rightBound = cellColumnRightIndex; + } + cellColumnIndex = cellColumnRightIndex - 1; + } + + else { // can't drop inside a spanned cell, or this is the + // dragged cell + while (colspan > 1) { + cellColumnIndex++; + colspan--; + unavailableColumnDropIndices.add(cellColumnIndex); + } + } + } + } + + if (leftBound == (rightBound - 1)) { + return; + } + + double position = autoScroller.getFrozenColumnsWidth(); + // iterate column indices and add possible drop positions + for (int i = frozenColumns; i < getColumnCount(); i++) { + Column column = getColumn(i); + if (!unavailableColumnDropIndices.contains(i) + && !column.isHidden()) { + if (leftBound != -1) { + if (i >= leftBound && i <= rightBound) { + possibleDropPositions.put(position, i); + } + } else { + possibleDropPositions.put(position, i); + } + } + position += column.getWidthActual(); + } + + if (leftBound == -1) { + // add the right side of the last column as columns.size() + possibleDropPositions.put(position, getColumnCount()); + } + } + + }; + + /** + * Enumeration for easy setting of selection mode. + */ + public enum SelectionMode { + + /** + * Shortcut for {@link SelectionModelSingle}. + */ + SINGLE { + + @Override + protected SelectionModel createModel() { + return GWT.create(SelectionModelSingle.class); + } + }, + + /** + * Shortcut for {@link SelectionModelMulti}. + */ + MULTI { + + @Override + protected SelectionModel createModel() { + return GWT.create(SelectionModelMulti.class); + } + }, + + /** + * Shortcut for {@link SelectionModelNone}. + */ + NONE { + + @Override + protected SelectionModel createModel() { + return GWT.create(SelectionModelNone.class); + } + }; + + protected abstract SelectionModel createModel(); + } + + /** + * Base class for grid columns internally used by the Grid. The user should + * use {@link Column} when creating new columns. + * + * @param + * the column type + * + * @param + * the row type + */ + public static abstract class Column { + + /** + * Default renderer for GridColumns. Renders everything into text + * through {@link Object#toString()}. + */ + private final class DefaultTextRenderer implements Renderer { + boolean warned = false; + private final String DEFAULT_RENDERER_WARNING = "This column uses a dummy default TextRenderer. " + + "A more suitable renderer should be set using the setRenderer() method."; + + @Override + public void render(RendererCellReference cell, Object data) { + if (!warned && !(data instanceof String)) { + getLogger().warning( + Column.this.toString() + ": " + + DEFAULT_RENDERER_WARNING); + warned = true; + } + + final String text; + if (data == null) { + text = ""; + } else { + text = data.toString(); + } + + cell.getElement().setInnerText(text); + } + } + + /** + * the column is associated with + */ + private Grid grid; + + /** + * Width of column in pixels as {@link #setWidth(double)} has been + * called + */ + private double widthUser = GridConstants.DEFAULT_COLUMN_WIDTH_PX; + + /** + * Renderer for rendering a value into the cell + */ + private Renderer bodyRenderer; + + private boolean sortable = false; + + private boolean editable = true; + + private boolean resizable = true; + + private boolean hidden = false; + + private boolean hidable = false; + + private String headerCaption = ""; + + private String hidingToggleCaption = null; + + private double minimumWidthPx = GridConstants.DEFAULT_MIN_WIDTH; + private double maximumWidthPx = GridConstants.DEFAULT_MAX_WIDTH; + private int expandRatio = GridConstants.DEFAULT_EXPAND_RATIO; + + /** + * Constructs a new column with a simple TextRenderer. + */ + public Column() { + setRenderer(new DefaultTextRenderer()); + } + + /** + * Constructs a new column with a simple TextRenderer. + * + * @param caption + * The header caption for this column + * + * @throws IllegalArgumentException + * if given header caption is null + */ + public Column(String caption) throws IllegalArgumentException { + this(); + setHeaderCaption(caption); + } + + /** + * Constructs a new column with a custom renderer. + * + * @param renderer + * The renderer to use for rendering the cells + * + * @throws IllegalArgumentException + * if given Renderer is null + */ + public Column(Renderer renderer) + throws IllegalArgumentException { + setRenderer(renderer); + } + + /** + * Constructs a new column with a custom renderer. + * + * @param renderer + * The renderer to use for rendering the cells + * @param caption + * The header caption for this column + * + * @throws IllegalArgumentException + * if given Renderer or header caption is null + */ + public Column(String caption, Renderer renderer) + throws IllegalArgumentException { + this(renderer); + setHeaderCaption(caption); + } + + /** + * Internally used by the grid to set itself + * + * @param grid + */ + private void setGrid(Grid grid) { + if (this.grid != null && grid != null) { + // Trying to replace grid + throw new IllegalStateException("Column already is attached " + + "to a grid. Remove the column first from the grid " + + "and then add it. (in: " + toString() + ")"); + } + + if (this.grid != null) { + this.grid.recalculateColumnWidths(); + } + this.grid = grid; + if (this.grid != null) { + this.grid.recalculateColumnWidths(); + } + } + + /** + * Sets a header caption for this column. + * + * @param caption + * The header caption for this column + * @return the column itself + * + */ + public Column setHeaderCaption(String caption) { + if (caption == null) { + caption = ""; + } + + if (!this.headerCaption.equals(caption)) { + this.headerCaption = caption; + if (grid != null) { + updateHeader(); + } + } + + return this; + } + + /** + * Returns the current header caption for this column + * + * @since 7.6 + * @return the header caption string + */ + public String getHeaderCaption() { + return headerCaption; + } + + private void updateHeader() { + HeaderRow row = grid.getHeader().getDefaultRow(); + if (row != null) { + row.getCell(this).setText(headerCaption); + if (isHidable()) { + grid.columnHider.updateHidingToggle(this); + } + } + } + + /** + * Returns the data that should be rendered into the cell. By default + * returning Strings and Widgets are supported. If the return type is a + * String then it will be treated as preformatted text. + *

+ * To support other types you will need to pass a custom renderer to the + * column via the column constructor. + * + * @param row + * The row object that provides the cell content. + * + * @return The cell content + */ + public abstract C getValue(T row); + + /** + * The renderer to render the cell with. By default renders the data as + * a String or adds the widget into the cell if the column type is of + * widget type. + * + * @return The renderer to render the cell content with + */ + public Renderer getRenderer() { + return bodyRenderer; + } + + /** + * Sets a custom {@link Renderer} for this column. + * + * @param renderer + * The renderer to use for rendering the cells + * @return the column itself + * + * @throws IllegalArgumentException + * if given Renderer is null + */ + public Column setRenderer(Renderer renderer) + throws IllegalArgumentException { + if (renderer == null) { + throw new IllegalArgumentException("Renderer cannot be null."); + } + + if (renderer != bodyRenderer) { + // Variables used to restore removed column. + boolean columnRemoved = false; + double widthInConfiguration = 0.0d; + ColumnConfiguration conf = null; + int index = 0; + + if (grid != null + && (bodyRenderer instanceof WidgetRenderer || renderer instanceof WidgetRenderer)) { + // Column needs to be recreated. + index = grid.getColumns().indexOf(this); + conf = grid.escalator.getColumnConfiguration(); + widthInConfiguration = conf.getColumnWidth(index); + + conf.removeColumns(index, 1); + columnRemoved = true; + } + + // Complex renderers need to be destroyed. + if (bodyRenderer instanceof ComplexRenderer) { + ((ComplexRenderer) bodyRenderer).destroy(); + } + + bodyRenderer = renderer; + + if (columnRemoved) { + // Restore the column. + conf.insertColumns(index, 1); + conf.setColumnWidth(index, widthInConfiguration); + } + + if (grid != null) { + grid.refreshBody(); + } + } + return this; + } + + /** + * Sets the pixel width of the column. Use a negative value for the grid + * to autosize column based on content and available space. + *

+ * This action is done "finally", once the current execution loop + * returns. This is done to reduce overhead of unintentionally always + * recalculate all columns, when modifying several columns at once. + *

+ * If the column is currently {@link #isHidden() hidden}, then this set + * width has effect only once the column has been made visible again. + * + * @param pixels + * the width in pixels or negative for auto sizing + */ + public Column setWidth(double pixels) { + if (!WidgetUtil.pixelValuesEqual(widthUser, pixels)) { + widthUser = pixels; + if (!isHidden()) { + scheduleColumnWidthRecalculator(); + } + } + return this; + } + + void doSetWidth(double pixels) { + assert !isHidden() : "applying width for a hidden column"; + if (grid != null) { + int index = grid.getVisibleColumns().indexOf(this); + ColumnConfiguration conf = grid.escalator + .getColumnConfiguration(); + conf.setColumnWidth(index, pixels); + } + } + + /** + * Returns the pixel width of the column as given by the user. + *

+ * Note: If a negative value was given to + * {@link #setWidth(double)}, that same negative value is returned here. + *

+ * Note: Returns the value, even if the column is currently + * {@link #isHidden() hidden}. + * + * @return pixel width of the column, or a negative number if the column + * width has been automatically calculated. + * @see #setWidth(double) + * @see #getWidthActual() + */ + public double getWidth() { + return widthUser; + } + + /** + * Returns the effective pixel width of the column. + *

+ * This differs from {@link #getWidth()} only when the column has been + * automatically resized, or when the column is currently + * {@link #isHidden() hidden}, when the value is 0. + * + * @return pixel width of the column. + */ + public double getWidthActual() { + if (isHidden()) { + return 0; + } + return grid.escalator.getColumnConfiguration() + .getColumnWidthActual( + grid.getVisibleColumns().indexOf(this)); + } + + void reapplyWidth() { + scheduleColumnWidthRecalculator(); + } + + /** + * Sets whether the column should be sortable by the user. The grid can + * be sorted by a sortable column by clicking or tapping the column's + * default header. Programmatic sorting using the Grid#sort methods is + * not affected by this setting. + * + * @param sortable + * {@code true} if the user should be able to sort the + * column, {@code false} otherwise + * @return the column itself + */ + public Column setSortable(boolean sortable) { + if (this.sortable != sortable) { + this.sortable = sortable; + if (grid != null) { + grid.refreshHeader(); + } + } + return this; + } + + /** + * Returns whether the user can sort the grid by this column. + *

+ * Note: it is possible to sort by this column programmatically + * using the Grid#sort methods regardless of the returned value. + * + * @return {@code true} if the column is sortable by the user, + * {@code false} otherwise + */ + public boolean isSortable() { + return sortable; + } + + /** + * Sets whether this column can be resized by the user. + * + * @since 7.6 + * + * @param resizable + * {@code true} if this column should be resizable, + * {@code false} otherwise + */ + public Column setResizable(boolean resizable) { + if (this.resizable != resizable) { + this.resizable = resizable; + if (grid != null) { + grid.refreshHeader(); + } + } + return this; + } + + /** + * Returns whether this column can be resized by the user. Default is + * {@code true}. + *

+ * Note: the column can be programmatically resized using + * {@link #setWidth(double)} and {@link #setWidthUndefined()} regardless + * of the returned value. + * + * @since 7.6 + * + * @return {@code true} if this column is resizable, {@code false} + * otherwise + */ + public boolean isResizable() { + return resizable; + } + + /** + * Hides or shows the column. By default columns are visible before + * explicitly hiding them. + * + * @since 7.5.0 + * @param hidden + * true to hide the column, false + * to show + */ + public Column setHidden(boolean hidden) { + setHidden(hidden, false); + return this; + } + + private void setHidden(boolean hidden, boolean userOriginated) { + if (this.hidden != hidden) { + if (hidden) { + grid.escalator.getColumnConfiguration().removeColumns( + grid.getVisibleColumns().indexOf(this), 1); + this.hidden = hidden; + } else { + this.hidden = hidden; + + final int columnIndex = grid.getVisibleColumns().indexOf( + this); + grid.escalator.getColumnConfiguration().insertColumns( + columnIndex, 1); + + // make sure column is set to frozen if it needs to be, + // escalator doesn't handle situation where the added column + // would be the last frozen column + int gridFrozenColumns = grid.getFrozenColumnCount(); + int escalatorFrozenColumns = grid.escalator + .getColumnConfiguration().getFrozenColumnCount(); + if (gridFrozenColumns > escalatorFrozenColumns + && escalatorFrozenColumns == columnIndex) { + grid.escalator.getColumnConfiguration() + .setFrozenColumnCount(++escalatorFrozenColumns); + } + } + grid.columnHider.updateHidingToggle(this); + grid.header.updateColSpans(); + grid.footer.updateColSpans(); + scheduleColumnWidthRecalculator(); + this.grid.fireEvent(new ColumnVisibilityChangeEvent(this, + hidden, userOriginated)); + } + } + + /** + * Returns whether this column is hidden. Default is {@code false}. + * + * @since 7.5.0 + * @return {@code true} if the column is currently hidden, {@code false} + * otherwise + */ + public boolean isHidden() { + return hidden; + } + + /** + * Set whether it is possible for the user to hide this column or not. + * Default is {@code false}. + *

+ * Note: it is still possible to hide the column + * programmatically using {@link #setHidden(boolean)}. + * + * @since 7.5.0 + * @param hidable + * {@code true} the user can hide this column, {@code false} + * otherwise + */ + public Column setHidable(boolean hidable) { + if (this.hidable != hidable) { + this.hidable = hidable; + grid.columnHider.updateColumnHidable(this); + } + return this; + } + + /** + * Is it possible for the the user to hide this column. Default is + * {@code false}. + *

+ * Note: the column can be programmatically hidden using + * {@link #setHidden(boolean)} regardless of the returned value. + * + * @since 7.5.0 + * @return true if the user can hide the column, + * false if not + */ + public boolean isHidable() { + return hidable; + } + + /** + * Sets the hiding toggle's caption for this column. Shown in the toggle + * for this column in the grid's sidebar when the column is + * {@link #isHidable() hidable}. + *

+ * The default value is null. In this case the header + * caption is used, see {@link #setHeaderCaption(String)}. + * + * @since 7.5.0 + * @param hidingToggleCaption + * the caption for the hiding toggle for this column + */ + public Column setHidingToggleCaption(String hidingToggleCaption) { + this.hidingToggleCaption = hidingToggleCaption; + if (isHidable()) { + grid.columnHider.updateHidingToggle(this); + } + return this; + } + + /** + * Gets the hiding toggle caption for this column. + * + * @since 7.5.0 + * @see #setHidingToggleCaption(String) + * @return the hiding toggle's caption for this column + */ + public String getHidingToggleCaption() { + return hidingToggleCaption; + } + + @Override + public String toString() { + String details = ""; + + if (headerCaption != null && !headerCaption.isEmpty()) { + details += "header:\"" + headerCaption + "\" "; + } else { + details += "header:empty "; + } + + if (grid != null) { + int index = grid.getColumns().indexOf(this); + if (index != -1) { + details += "attached:#" + index + " "; + } else { + details += "attached:unindexed "; + } + } else { + details += "detached "; + } + + details += "sortable:" + sortable + " "; + + return getClass().getSimpleName() + "[" + details.trim() + "]"; + } + + /** + * Sets the minimum width for this column. + *

+ * This defines the minimum guaranteed pixel width of the column + * when it is set to expand. + *

+ * This action is done "finally", once the current execution loop + * returns. This is done to reduce overhead of unintentionally always + * recalculate all columns, when modifying several columns at once. + * + * @param pixels + * the minimum width + * @return this column + */ + public Column setMinimumWidth(double pixels) { + final double maxwidth = getMaximumWidth(); + if (pixels >= 0 && pixels > maxwidth && maxwidth >= 0) { + throw new IllegalArgumentException("New minimum width (" + + pixels + ") was greater than maximum width (" + + maxwidth + ")"); + } + + if (minimumWidthPx != pixels) { + minimumWidthPx = pixels; + scheduleColumnWidthRecalculator(); + } + return this; + } + + /** + * Sets the maximum width for this column. + *

+ * This defines the maximum allowed pixel width of the column + * when it is set to expand. + *

+ * This action is done "finally", once the current execution loop + * returns. This is done to reduce overhead of unintentionally always + * recalculate all columns, when modifying several columns at once. + * + * @param pixels + * the maximum width + * @param immediately + * true if the widths should be executed + * immediately (ignoring lazy loading completely), or + * false if the command should be run after a + * while (duplicate non-immediately invocations are ignored). + * @return this column + */ + public Column setMaximumWidth(double pixels) { + final double minwidth = getMinimumWidth(); + if (pixels >= 0 && pixels < minwidth && minwidth >= 0) { + throw new IllegalArgumentException("New maximum width (" + + pixels + ") was less than minimum width (" + minwidth + + ")"); + } + + if (maximumWidthPx != pixels) { + maximumWidthPx = pixels; + scheduleColumnWidthRecalculator(); + } + return this; + } + + /** + * Sets the ratio with which the column expands. + *

+ * By default, all columns expand equally (treated as if all of them had + * an expand ratio of 1). Once at least one column gets a defined expand + * ratio, the implicit expand ratio is removed, and only the defined + * expand ratios are taken into account. + *

+ * If a column has a defined width ({@link #setWidth(double)}), it + * overrides this method's effects. + *

+ * Example: A grid with three columns, with expand ratios 0, 1 + * and 2, respectively. The column with a ratio of 0 is exactly + * as wide as its contents requires. The column with a ratio of + * 1 is as wide as it needs, plus a third of any excess + * space, bceause we have 3 parts total, and this column + * reservs only one of those. The column with a ratio of 2, is as wide + * as it needs to be, plus two thirds of the excess + * width. + *

+ * This action is done "finally", once the current execution loop + * returns. This is done to reduce overhead of unintentionally always + * recalculate all columns, when modifying several columns at once. + * + * @param expandRatio + * the expand ratio of this column. {@code 0} to not have it + * expand at all. A negative number to clear the expand + * value. + * @return this column + */ + public Column setExpandRatio(int ratio) { + if (expandRatio != ratio) { + expandRatio = ratio; + scheduleColumnWidthRecalculator(); + } + return this; + } + + /** + * Clears the column's expand ratio. + *

+ * Same as calling {@link #setExpandRatio(int) setExpandRatio(-1)} + * + * @return this column + */ + public Column clearExpandRatio() { + return setExpandRatio(-1); + } + + /** + * Gets the minimum width for this column. + * + * @return the minimum width for this column + * @see #setMinimumWidth(double) + */ + public double getMinimumWidth() { + return minimumWidthPx; + } + + /** + * Gets the maximum width for this column. + * + * @return the maximum width for this column + * @see #setMaximumWidth(double) + */ + public double getMaximumWidth() { + return maximumWidthPx; + } + + /** + * Gets the expand ratio for this column. + * + * @return the expand ratio for this column + * @see #setExpandRatio(int) + */ + public int getExpandRatio() { + return expandRatio; + } + + /** + * Sets whether the values in this column should be editable by the user + * when the row editor is active. By default columns are editable. + * + * @param editable + * {@code true} to set this column editable, {@code false} + * otherwise + * @return this column + * + * @throws IllegalStateException + * if the editor is currently active + * + * @see Grid#editRow(int) + * @see Grid#isEditorActive() + */ + public Column setEditable(boolean editable) { + if (editable != this.editable && grid.isEditorActive()) { + throw new IllegalStateException( + "Cannot change column editable status while the editor is active"); + } + this.editable = editable; + return this; + } + + /** + * Returns whether the values in this column are editable by the user + * when the row editor is active. + * + * @return {@code true} if this column is editable, {@code false} + * otherwise + * + * @see #setEditable(boolean) + */ + public boolean isEditable() { + return editable; + } + + private void scheduleColumnWidthRecalculator() { + if (grid != null) { + grid.recalculateColumnWidths(); + } else { + /* + * NOOP + * + * Since setGrid() will call reapplyWidths as the colum is + * attached to a grid, it will call setWidth, which, in turn, + * will call this method again. Therefore, it's guaranteed that + * the recalculation is scheduled eventually, once the column is + * attached to a grid. + */ + } + } + + /** + * Resets the default header cell contents to column header captions. + * + * @since 7.5.1 + * @param cell + * default header cell for this column + */ + protected void setDefaultHeaderContent(HeaderCell cell) { + cell.setText(headerCaption); + } + } + + protected class BodyUpdater implements EscalatorUpdater { + + @Override + public void preAttach(Row row, Iterable cellsToAttach) { + int rowIndex = row.getRow(); + rowReference.set(rowIndex, getDataSource().getRow(rowIndex), + row.getElement()); + for (FlyweightCell cell : cellsToAttach) { + Renderer renderer = findRenderer(cell); + if (renderer instanceof ComplexRenderer) { + try { + Column column = getVisibleColumn(cell.getColumn()); + rendererCellReference.set(cell, + getColumns().indexOf(column), column); + ((ComplexRenderer) renderer) + .init(rendererCellReference); + } catch (RuntimeException e) { + getLogger().log( + Level.SEVERE, + "Error initing cell in column " + + cell.getColumn(), e); + } + } + } + } + + @Override + public void postAttach(Row row, Iterable attachedCells) { + for (FlyweightCell cell : attachedCells) { + Renderer renderer = findRenderer(cell); + if (renderer instanceof WidgetRenderer) { + try { + WidgetRenderer widgetRenderer = (WidgetRenderer) renderer; + + Widget widget = widgetRenderer.createWidget(); + assert widget != null : "WidgetRenderer.createWidget() returned null. It should return a widget."; + assert widget.getParent() == null : "WidgetRenderer.createWidget() returned a widget which already is attached."; + assert cell.getElement().getChildCount() == 0 : "Cell content should be empty when adding Widget"; + + // Physical attach + cell.getElement().appendChild(widget.getElement()); + + // Logical attach + setParent(widget, Grid.this); + } catch (RuntimeException e) { + getLogger().log( + Level.SEVERE, + "Error attaching child widget in column " + + cell.getColumn(), e); + } + } + } + } + + @Override + public void update(Row row, Iterable cellsToUpdate) { + int rowIndex = row.getRow(); + TableRowElement rowElement = row.getElement(); + T rowData = dataSource.getRow(rowIndex); + + boolean hasData = rowData != null; + + /* + * TODO could be more efficient to build a list of all styles that + * should be used and update the element only once instead of + * attempting to update only the ones that have changed. + */ + + // Assign stylename for rows with data + boolean usedToHaveData = rowElement + .hasClassName(rowHasDataStyleName); + + if (usedToHaveData != hasData) { + setStyleName(rowElement, rowHasDataStyleName, hasData); + } + + boolean isEvenIndex = (row.getRow() % 2 == 0); + setStyleName(rowElement, rowStripeStyleName, !isEvenIndex); + + rowReference.set(rowIndex, rowData, rowElement); + + if (hasData) { + setStyleName(rowElement, rowSelectedStyleName, + isSelected(rowData)); + + if (rowStyleGenerator != null) { + try { + String rowStylename = rowStyleGenerator + .getStyle(rowReference); + setCustomStyleName(rowElement, rowStylename); + } catch (RuntimeException e) { + getLogger().log( + Level.SEVERE, + "Error generating styles for row " + + row.getRow(), e); + } + } else { + // Remove in case there was a generator previously + setCustomStyleName(rowElement, null); + } + } else if (usedToHaveData) { + setStyleName(rowElement, rowSelectedStyleName, false); + + setCustomStyleName(rowElement, null); + } + + cellFocusHandler.updateFocusedRowStyle(row); + + for (FlyweightCell cell : cellsToUpdate) { + Column column = getVisibleColumn(cell.getColumn()); + final int columnIndex = getColumns().indexOf(column); + + assert column != null : "Column was not found from cell (" + + cell.getColumn() + "," + cell.getRow() + ")"; + + cellFocusHandler.updateFocusedCellStyle(cell, + escalator.getBody()); + + if (hasData && cellStyleGenerator != null) { + try { + cellReference + .set(cell.getColumn(), columnIndex, column); + String generatedStyle = cellStyleGenerator + .getStyle(cellReference); + setCustomStyleName(cell.getElement(), generatedStyle); + } catch (RuntimeException e) { + getLogger().log( + Level.SEVERE, + "Error generating style for cell in column " + + cell.getColumn(), e); + } + } else if (hasData || usedToHaveData) { + setCustomStyleName(cell.getElement(), null); + } + + Renderer renderer = column.getRenderer(); + + try { + rendererCellReference.set(cell, columnIndex, column); + if (renderer instanceof ComplexRenderer) { + // Hide cell content if needed + ComplexRenderer clxRenderer = (ComplexRenderer) renderer; + if (hasData) { + if (!usedToHaveData) { + // Prepare cell for rendering + clxRenderer.setContentVisible( + rendererCellReference, true); + } + + Object value = column.getValue(rowData); + clxRenderer.render(rendererCellReference, value); + + } else { + // Prepare cell for no data + clxRenderer.setContentVisible( + rendererCellReference, false); + } + + } else if (hasData) { + // Simple renderers just render + Object value = column.getValue(rowData); + renderer.render(rendererCellReference, value); + + } else { + // Clear cell if there is no data + cell.getElement().removeAllChildren(); + } + } catch (RuntimeException e) { + getLogger().log( + Level.SEVERE, + "Error rendering cell in column " + + cell.getColumn(), e); + } + } + } + + @Override + public void preDetach(Row row, Iterable cellsToDetach) { + for (FlyweightCell cell : cellsToDetach) { + Renderer renderer = findRenderer(cell); + if (renderer instanceof WidgetRenderer) { + try { + Widget w = WidgetUtil.findWidget(cell.getElement() + .getFirstChildElement(), null); + if (w != null) { + + // Logical detach + setParent(w, null); + + // Physical detach + cell.getElement().removeChild(w.getElement()); + } + } catch (RuntimeException e) { + getLogger().log( + Level.SEVERE, + "Error detaching widget in column " + + cell.getColumn(), e); + } + } + } + } + + @Override + public void postDetach(Row row, Iterable detachedCells) { + int rowIndex = row.getRow(); + // Passing null row data since it might not exist in the data source + // any more + rowReference.set(rowIndex, null, row.getElement()); + for (FlyweightCell cell : detachedCells) { + Renderer renderer = findRenderer(cell); + if (renderer instanceof ComplexRenderer) { + try { + Column column = getVisibleColumn(cell.getColumn()); + rendererCellReference.set(cell, + getColumns().indexOf(column), column); + ((ComplexRenderer) renderer) + .destroy(rendererCellReference); + } catch (RuntimeException e) { + getLogger().log( + Level.SEVERE, + "Error destroying cell in column " + + cell.getColumn(), e); + } + } + } + } + } + + protected class StaticSectionUpdater implements EscalatorUpdater { + + private StaticSection section; + private RowContainer container; + + public StaticSectionUpdater(StaticSection section, + RowContainer container) { + super(); + this.section = section; + this.container = container; + } + + @Override + public void update(Row row, Iterable cellsToUpdate) { + StaticSection.StaticRow staticRow = section.getRow(row.getRow()); + final List> columns = getVisibleColumns(); + + setCustomStyleName(row.getElement(), staticRow.getStyleName()); + + for (FlyweightCell cell : cellsToUpdate) { + final StaticSection.StaticCell metadata = staticRow + .getCell(columns.get(cell.getColumn())); + + // Decorate default row with sorting indicators + if (staticRow instanceof HeaderRow) { + addSortingIndicatorsToHeaderRow((HeaderRow) staticRow, cell); + } + + // Assign colspan to cell before rendering + cell.setColSpan(metadata.getColspan()); + + Element td = cell.getElement(); + td.removeAllChildren(); + setCustomStyleName(td, metadata.getStyleName()); + + Element content; + // Wrap text or html content in default header to isolate + // the content from the possible column resize drag handle + // next to it + if (metadata.getType() != GridStaticCellType.WIDGET) { + content = DOM.createDiv(); + + if (staticRow instanceof HeaderRow) { + content.setClassName(getStylePrimaryName() + + "-column-header-content"); + if (((HeaderRow) staticRow).isDefault()) { + content.setClassName(content.getClassName() + " " + + getStylePrimaryName() + + "-column-default-header-content"); + } + } else if (staticRow instanceof FooterRow) { + content.setClassName(getStylePrimaryName() + + "-column-footer-content"); + } else { + getLogger().severe( + "Unhandled static row type " + + staticRow.getClass() + .getCanonicalName()); + } + + td.appendChild(content); + } else { + content = td; + } + + switch (metadata.getType()) { + case TEXT: + content.setInnerText(metadata.getText()); + break; + case HTML: + content.setInnerHTML(metadata.getHtml()); + break; + case WIDGET: + preDetach(row, Arrays.asList(cell)); + content.setInnerHTML(""); + postAttach(row, Arrays.asList(cell)); + break; + } + + // XXX: Should add only once in preAttach/postAttach or when + // resizable status changes + // Only add resize handles to default header row for now + if (columns.get(cell.getColumn()).isResizable() + && staticRow instanceof HeaderRow + && ((HeaderRow) staticRow).isDefault()) { + + final int column = cell.getColumn(); + DragHandle dragger = new DragHandle(getStylePrimaryName() + + "-column-resize-handle", + new DragHandleCallback() { + + private Column col = getVisibleColumn(column); + private double initialWidth = 0; + private double minCellWidth; + + @Override + public void onUpdate(double deltaX, + double deltaY) { + col.setWidth(Math.max(minCellWidth, + initialWidth + deltaX)); + } + + @Override + public void onStart() { + initialWidth = col.getWidthActual(); + + minCellWidth = escalator + .getMinCellWidth(getColumns() + .indexOf(col)); + for (Column c : getColumns()) { + if (selectionColumn == c) { + // Don't modify selection column. + continue; + } + + if (c.getWidth() < 0) { + c.setWidth(c.getWidthActual()); + fireEvent(new ColumnResizeEvent( + c)); + } + } + + WidgetUtil.setTextSelectionEnabled( + getElement(), false); + } + + @Override + public void onComplete() { + fireEvent(new ColumnResizeEvent(col)); + + WidgetUtil.setTextSelectionEnabled( + getElement(), true); + } + + @Override + public void onCancel() { + col.setWidth(initialWidth); + + WidgetUtil.setTextSelectionEnabled( + getElement(), true); + } + }); + dragger.addTo(td); + } + + cellFocusHandler.updateFocusedCellStyle(cell, container); + } + } + + private void addSortingIndicatorsToHeaderRow(HeaderRow headerRow, + FlyweightCell cell) { + + Element cellElement = cell.getElement(); + + boolean sortedBefore = cellElement.hasClassName("sort-asc") + || cellElement.hasClassName("sort-desc"); + + cleanup(cell); + if (!headerRow.isDefault()) { + // Nothing more to do if not in the default row + return; + } + + final Column column = getVisibleColumn(cell.getColumn()); + SortOrder sortingOrder = getSortOrder(column); + boolean sortable = column.isSortable(); + + if (sortable) { + cellElement.addClassName("sortable"); + } + + if (!sortable || sortingOrder == null) { + // Only apply sorting indicators to sortable header columns + return; + } + + if (SortDirection.ASCENDING == sortingOrder.getDirection()) { + cellElement.addClassName("sort-asc"); + } else { + cellElement.addClassName("sort-desc"); + } + + int sortIndex = Grid.this.getSortOrder().indexOf(sortingOrder); + if (sortIndex > -1 && Grid.this.getSortOrder().size() > 1) { + // Show sort order indicator if column is + // sorted and other sorted columns also exists. + cellElement.setAttribute("sort-order", + String.valueOf(sortIndex + 1)); + } + + if (!sortedBefore) { + verifyColumnWidth(column); + } + } + + /** + * Sort indicator requires a bit more space from the cell than normally. + * This method check that the now sorted column has enough width. + * + * @param column + * sorted column + */ + private void verifyColumnWidth(Column column) { + int colIndex = getColumns().indexOf(column); + double minWidth = escalator.getMinCellWidth(colIndex); + if (column.getWidthActual() < minWidth) { + // Fix column size + escalator.getColumnConfiguration().setColumnWidth(colIndex, + minWidth); + + fireEvent(new ColumnResizeEvent(column)); + } + } + + /** + * Finds the sort order for this column + */ + private SortOrder getSortOrder(Column column) { + for (SortOrder order : Grid.this.getSortOrder()) { + if (order.getColumn() == column) { + return order; + } + } + return null; + } + + private void cleanup(FlyweightCell cell) { + Element cellElement = cell.getElement(); + cellElement.removeAttribute("sort-order"); + cellElement.removeClassName("sort-desc"); + cellElement.removeClassName("sort-asc"); + cellElement.removeClassName("sortable"); + } + + @Override + public void preAttach(Row row, Iterable cellsToAttach) { + } + + @Override + public void postAttach(Row row, Iterable attachedCells) { + StaticSection.StaticRow gridRow = section.getRow(row.getRow()); + List> columns = getVisibleColumns(); + + for (FlyweightCell cell : attachedCells) { + StaticSection.StaticCell metadata = gridRow.getCell(columns + .get(cell.getColumn())); + /* + * If the cell contains widgets that are not currently attached + * then attach them now. + */ + if (GridStaticCellType.WIDGET.equals(metadata.getType())) { + final Widget widget = metadata.getWidget(); + if (widget != null && !widget.isAttached()) { + getGrid().attachWidget(metadata.getWidget(), + cell.getElement()); + } + } + } + } + + @Override + public void preDetach(Row row, Iterable cellsToDetach) { + if (section.getRowCount() > row.getRow()) { + StaticSection.StaticRow gridRow = section.getRow(row + .getRow()); + List> columns = getVisibleColumns(); + for (FlyweightCell cell : cellsToDetach) { + StaticSection.StaticCell metadata = gridRow.getCell(columns + .get(cell.getColumn())); + + if (GridStaticCellType.WIDGET.equals(metadata.getType()) + && metadata.getWidget() != null + && metadata.getWidget().isAttached()) { + + getGrid().detachWidget(metadata.getWidget()); + } + } + } + } + + protected Grid getGrid() { + return section.grid; + } + + @Override + public void postDetach(Row row, Iterable detachedCells) { + } + }; + + /** + * Creates a new instance. + */ + public Grid() { + initWidget(escalator); + getElement().setTabIndex(0); + cellFocusHandler = new CellFocusHandler(); + + setStylePrimaryName(STYLE_NAME); + + escalator.getHeader().setEscalatorUpdater(createHeaderUpdater()); + escalator.getBody().setEscalatorUpdater(createBodyUpdater()); + escalator.getFooter().setEscalatorUpdater(createFooterUpdater()); + + header.setGrid(this); + HeaderRow defaultRow = header.appendRow(); + header.setDefaultRow(defaultRow); + + footer.setGrid(this); + + editor.setGrid(this); + + setSelectionMode(SelectionMode.SINGLE); + + escalator.getBody().setSpacerUpdater(gridSpacerUpdater); + + escalator.addScrollHandler(new ScrollHandler() { + @Override + public void onScroll(ScrollEvent event) { + fireEvent(new ScrollEvent()); + } + }); + + escalator + .addRowVisibilityChangeHandler(new RowVisibilityChangeHandler() { + @Override + public void onRowVisibilityChange( + RowVisibilityChangeEvent event) { + if (dataSource != null && dataSource.size() != 0) { + dataIsBeingFetched = true; + dataSource.ensureAvailability( + event.getFirstVisibleRow(), + event.getVisibleRowCount()); + } + } + }); + + // Default action on SelectionEvents. Refresh the body so changed + // become visible. + addSelectionHandler(new SelectionHandler() { + + @Override + public void onSelect(SelectionEvent event) { + refreshBody(); + } + }); + + // Sink header events and key events + sinkEvents(getHeader().getConsumedEvents()); + sinkEvents(Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.KEYUP, + BrowserEvents.KEYPRESS, BrowserEvents.DBLCLICK, + BrowserEvents.MOUSEDOWN, BrowserEvents.CLICK)); + + // Make ENTER and SHIFT+ENTER in the header perform sorting + addHeaderKeyUpHandler(new HeaderKeyUpHandler() { + @Override + public void onKeyUp(GridKeyUpEvent event) { + if (event.getNativeKeyCode() != KeyCodes.KEY_ENTER) { + return; + } + if (getHeader().getRow(event.getFocusedCell().getRowIndex()) + .isDefault()) { + // Only sort for enter on the default header + sorter.sort(event.getFocusedCell().getColumn(), + event.isShiftKeyDown()); + } + } + }); + + addDataAvailableHandler(new DataAvailableHandler() { + @Override + public void onDataAvailable(DataAvailableEvent event) { + dataIsBeingFetched = false; + } + }); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void setEnabled(boolean enabled) { + if (enabled == this.enabled) { + return; + } + + this.enabled = enabled; + getElement().setTabIndex(enabled ? 0 : -1); + + // Editor save and cancel buttons need to be disabled. + boolean editorOpen = editor.getState() != State.INACTIVE; + if (editorOpen) { + editor.setGridEnabled(enabled); + } + + sidebar.setEnabled(enabled); + + getEscalator().setScrollLocked(Direction.VERTICAL, + !enabled || editorOpen); + getEscalator().setScrollLocked(Direction.HORIZONTAL, !enabled); + } + + @Override + public void setStylePrimaryName(String style) { + super.setStylePrimaryName(style); + escalator.setStylePrimaryName(style); + editor.setStylePrimaryName(style); + sidebar.setStylePrimaryName(style + "-sidebar"); + sidebar.addStyleName("v-contextmenu"); + + String rowStyle = getStylePrimaryName() + "-row"; + rowHasDataStyleName = rowStyle + "-has-data"; + rowSelectedStyleName = rowStyle + "-selected"; + rowStripeStyleName = rowStyle + "-stripe"; + + cellFocusStyleName = getStylePrimaryName() + "-cell-focused"; + rowFocusStyleName = getStylePrimaryName() + "-row-focused"; + + if (isAttached()) { + refreshHeader(); + refreshBody(); + refreshFooter(); + } + } + + /** + * Creates the escalator updater used to update the header rows in this + * grid. The updater is invoked when header rows or columns are added or + * removed, or the content of existing header cells is changed. + * + * @return the new header updater instance + * + * @see GridHeader + * @see Grid#getHeader() + */ + protected EscalatorUpdater createHeaderUpdater() { + return new StaticSectionUpdater(header, escalator.getHeader()); + } + + /** + * Creates the escalator updater used to update the body rows in this grid. + * The updater is invoked when body rows or columns are added or removed, + * the content of body cells is changed, or the body is scrolled to expose + * previously hidden content. + * + * @return the new body updater instance + */ + protected EscalatorUpdater createBodyUpdater() { + return new BodyUpdater(); + } + + /** + * Creates the escalator updater used to update the footer rows in this + * grid. The updater is invoked when header rows or columns are added or + * removed, or the content of existing header cells is changed. + * + * @return the new footer updater instance + * + * @see GridFooter + * @see #getFooter() + */ + protected EscalatorUpdater createFooterUpdater() { + return new StaticSectionUpdater(footer, escalator.getFooter()); + } + + /** + * Refreshes header or footer rows on demand + * + * @param rows + * The row container + * @param firstRowIsVisible + * is the first row visible + * @param isHeader + * true if we refreshing the header, else assumed + * the footer + */ + private void refreshRowContainer(RowContainer rows, StaticSection section) { + + // Add or Remove rows on demand + int rowDiff = section.getVisibleRowCount() - rows.getRowCount(); + if (rowDiff > 0) { + rows.insertRows(0, rowDiff); + } else if (rowDiff < 0) { + rows.removeRows(0, -rowDiff); + } + + // Refresh all the rows + if (rows.getRowCount() > 0) { + rows.refreshRows(0, rows.getRowCount()); + } + } + + /** + * Focus a body cell by row and column index. + * + * @param rowIndex + * index of row to focus + * @param columnIndex + * index of cell to focus + */ + void focusCell(int rowIndex, int columnIndex) { + final Range rowRange = Range.between(0, dataSource.size()); + final Range columnRange = Range.between(0, getVisibleColumns().size()); + + assert rowRange.contains(rowIndex) : "Illegal row index. Should be in range " + + rowRange; + assert columnRange.contains(columnIndex) : "Illegal column index. Should be in range " + + columnRange; + + if (rowRange.contains(rowIndex) && columnRange.contains(columnIndex)) { + cellFocusHandler.setCellFocus(rowIndex, columnIndex, + escalator.getBody()); + WidgetUtil.focus(getElement()); + } + } + + /** + * Refreshes all header rows + */ + void refreshHeader() { + refreshRowContainer(escalator.getHeader(), header); + } + + /** + * Refreshes all body rows + */ + private void refreshBody() { + escalator.getBody().refreshRows(0, escalator.getBody().getRowCount()); + } + + /** + * Refreshes all footer rows + */ + void refreshFooter() { + refreshRowContainer(escalator.getFooter(), footer); + } + + /** + * Adds columns as the last columns in the grid. + * + * @param columns + * the columns to add + */ + public void addColumns(Column... columns) { + int count = getColumnCount(); + for (Column column : columns) { + addColumn(column, count++); + } + } + + /** + * Adds a column as the last column in the grid. + * + * @param column + * the column to add + * @return given column + */ + public > C addColumn(C column) { + addColumn(column, getColumnCount()); + return column; + } + + /** + * Inserts a column into a specific position in the grid. + * + * @param index + * the index where the column should be inserted into + * @param column + * the column to add + * @return given column + * + * @throws IllegalStateException + * if Grid's current selection model renders a selection column, + * and {@code index} is 0. + */ + public > C addColumn(C column, int index) { + if (column == selectionColumn) { + throw new IllegalArgumentException("The selection column many " + + "not be added manually"); + } else if (selectionColumn != null && index == 0) { + throw new IllegalStateException("A column cannot be inserted " + + "before the selection column"); + } + + addColumnSkipSelectionColumnCheck(column, index); + return column; + } + + private void addColumnSkipSelectionColumnCheck(Column column, + int index) { + // Register column with grid + columns.add(index, column); + + header.addColumn(column); + footer.addColumn(column); + + // Register this grid instance with the column + ((Column) column).setGrid(this); + + // Grid knows about hidden columns, Escalator only knows about what is + // visible so column indexes do not match + if (!column.isHidden()) { + int escalatorIndex = index; + for (int existingColumn = 0; existingColumn < index; existingColumn++) { + if (getColumn(existingColumn).isHidden()) { + escalatorIndex--; + } + } + escalator.getColumnConfiguration().insertColumns(escalatorIndex, 1); + } + + // Reapply column width + column.reapplyWidth(); + + // Sink all renderer events + Set events = new HashSet(); + events.addAll(getConsumedEventsForRenderer(column.getRenderer())); + + if (column.isHidable()) { + columnHider.updateColumnHidable(column); + } + + sinkEvents(events); + } + + private void sinkEvents(Collection events) { + assert events != null; + + int eventsToSink = 0; + for (String typeName : events) { + int typeInt = Event.getTypeInt(typeName); + if (typeInt < 0) { + // Type not recognized by typeInt + sinkBitlessEvent(typeName); + } else { + eventsToSink |= typeInt; + } + } + + if (eventsToSink > 0) { + sinkEvents(eventsToSink); + } + } + + private Renderer findRenderer(FlyweightCell cell) { + Column column = getVisibleColumn(cell.getColumn()); + assert column != null : "Could not find column at index:" + + cell.getColumn(); + return column.getRenderer(); + } + + /** + * Removes a column from the grid. + * + * @param column + * the column to remove + */ + public void removeColumn(Column column) { + if (column != null && column.equals(selectionColumn)) { + throw new IllegalArgumentException( + "The selection column may not be removed manually."); + } + + removeColumnSkipSelectionColumnCheck(column); + } + + private void removeColumnSkipSelectionColumnCheck(Column column) { + int columnIndex = columns.indexOf(column); + + // Remove from column configuration + escalator.getColumnConfiguration().removeColumns( + getVisibleColumns().indexOf(column), 1); + + updateFrozenColumns(); + + header.removeColumn(column); + footer.removeColumn(column); + + // de-register column with grid + ((Column) column).setGrid(null); + + columns.remove(columnIndex); + + if (column.isHidable()) { + columnHider.removeColumnHidingToggle(column); + } + } + + /** + * Returns the amount of columns in the grid. + *

+ * NOTE: this includes the hidden columns in the count. + * + * @return The number of columns in the grid + */ + public int getColumnCount() { + return columns.size(); + } + + /** + * Returns a list columns in the grid, including hidden columns. + *

+ * For currently visible columns, use {@link #getVisibleColumns()}. + * + * @return A unmodifiable list of the columns in the grid + */ + public List> getColumns() { + return Collections + .unmodifiableList(new ArrayList>(columns)); + } + + /** + * Returns a list of the currently visible columns in the grid. + *

+ * No {@link Column#isHidden() hidden} columns included. + * + * @since 7.5.0 + * @return A unmodifiable list of the currently visible columns in the grid + */ + public List> getVisibleColumns() { + ArrayList> visible = new ArrayList>(); + for (Column c : columns) { + if (!c.isHidden()) { + visible.add(c); + } + } + return Collections.unmodifiableList(visible); + } + + /** + * Returns a column by its index in the grid. + *

+ * NOTE: The indexing includes hidden columns. + * + * @param index + * the index of the column + * @return The column in the given index + * @throws IllegalArgumentException + * if the column index does not exist in the grid + */ + public Column getColumn(int index) throws IllegalArgumentException { + if (index < 0 || index >= columns.size()) { + throw new IllegalStateException("Column not found."); + } + return columns.get(index); + } + + private Column getVisibleColumn(int index) + throws IllegalArgumentException { + List> visibleColumns = getVisibleColumns(); + if (index < 0 || index >= visibleColumns.size()) { + throw new IllegalStateException("Column not found."); + } + return visibleColumns.get(index); + } + + /** + * Returns the header section of this grid. The default header contains a + * single row displaying the column captions. + * + * @return the header + */ + protected Header getHeader() { + return header; + } + + /** + * Gets the header row at given index. + * + * @param rowIndex + * 0 based index for row. Counted from top to bottom + * @return header row at given index + * @throws IllegalArgumentException + * if no row exists at given index + */ + public HeaderRow getHeaderRow(int rowIndex) { + return header.getRow(rowIndex); + } + + /** + * Inserts a new row at the given position to the header section. Shifts the + * row currently at that position and any subsequent rows down (adds one to + * their indices). + * + * @param index + * the position at which to insert the row + * @return the new row + * + * @throws IllegalArgumentException + * if the index is less than 0 or greater than row count + * @see #appendHeaderRow() + * @see #prependHeaderRow() + * @see #removeHeaderRow(HeaderRow) + * @see #removeHeaderRow(int) + */ + public HeaderRow addHeaderRowAt(int index) { + return header.addRowAt(index); + } + + /** + * Adds a new row at the bottom of the header section. + * + * @return the new row + * @see #prependHeaderRow() + * @see #addHeaderRowAt(int) + * @see #removeHeaderRow(HeaderRow) + * @see #removeHeaderRow(int) + */ + public HeaderRow appendHeaderRow() { + return header.appendRow(); + } + + /** + * Returns the current default row of the header section. The default row is + * a special header row providing a user interface for sorting columns. + * Setting a header caption for column updates cells in the default header. + * + * @return the default row or null if no default row set + */ + public HeaderRow getDefaultHeaderRow() { + return header.getDefaultRow(); + } + + /** + * Gets the row count for the header section. + * + * @return row count + */ + public int getHeaderRowCount() { + return header.getRowCount(); + } + + /** + * Adds a new row at the top of the header section. + * + * @return the new row + * @see #appendHeaderRow() + * @see #addHeaderRowAt(int) + * @see #removeHeaderRow(HeaderRow) + * @see #removeHeaderRow(int) + */ + public HeaderRow prependHeaderRow() { + return header.prependRow(); + } + + /** + * Removes the given row from the header section. + * + * @param row + * the row to be removed + * + * @throws IllegalArgumentException + * if the row does not exist in this section + * @see #removeHeaderRow(int) + * @see #addHeaderRowAt(int) + * @see #appendHeaderRow() + * @see #prependHeaderRow() + */ + public void removeHeaderRow(HeaderRow row) { + header.removeRow(row); + } + + /** + * Removes the row at the given position from the header section. + * + * @param index + * the position of the row + * + * @throws IllegalArgumentException + * if no row exists at given index + * @see #removeHeaderRow(HeaderRow) + * @see #addHeaderRowAt(int) + * @see #appendHeaderRow() + * @see #prependHeaderRow() + */ + public void removeHeaderRow(int rowIndex) { + header.removeRow(rowIndex); + } + + /** + * Sets the default row of the header. The default row is a special header + * row providing a user interface for sorting columns. + *

+ * Note: Setting the default header row will reset all cell contents to + * Column defaults. + * + * @param row + * the new default row, or null for no default row + * + * @throws IllegalArgumentException + * header does not contain the row + */ + public void setDefaultHeaderRow(HeaderRow row) { + header.setDefaultRow(row); + } + + /** + * Sets the visibility of the header section. + * + * @param visible + * true to show header section, false to hide + */ + public void setHeaderVisible(boolean visible) { + header.setVisible(visible); + } + + /** + * Returns the visibility of the header section. + * + * @return true if visible, false otherwise. + */ + public boolean isHeaderVisible() { + return header.isVisible(); + } + + /* Grid Footers */ + + /** + * Returns the footer section of this grid. The default footer is empty. + * + * @return the footer + */ + protected Footer getFooter() { + return footer; + } + + /** + * Gets the footer row at given index. + * + * @param rowIndex + * 0 based index for row. Counted from top to bottom + * @return footer row at given index + * @throws IllegalArgumentException + * if no row exists at given index + */ + public FooterRow getFooterRow(int rowIndex) { + return footer.getRow(rowIndex); + } + + /** + * Inserts a new row at the given position to the footer section. Shifts the + * row currently at that position and any subsequent rows down (adds one to + * their indices). + * + * @param index + * the position at which to insert the row + * @return the new row + * + * @throws IllegalArgumentException + * if the index is less than 0 or greater than row count + * @see #appendFooterRow() + * @see #prependFooterRow() + * @see #removeFooterRow(FooterRow) + * @see #removeFooterRow(int) + */ + public FooterRow addFooterRowAt(int index) { + return footer.addRowAt(index); + } + + /** + * Adds a new row at the bottom of the footer section. + * + * @return the new row + * @see #prependFooterRow() + * @see #addFooterRowAt(int) + * @see #removeFooterRow(FooterRow) + * @see #removeFooterRow(int) + */ + public FooterRow appendFooterRow() { + return footer.appendRow(); + } + + /** + * Gets the row count for the footer. + * + * @return row count + */ + public int getFooterRowCount() { + return footer.getRowCount(); + } + + /** + * Adds a new row at the top of the footer section. + * + * @return the new row + * @see #appendFooterRow() + * @see #addFooterRowAt(int) + * @see #removeFooterRow(FooterRow) + * @see #removeFooterRow(int) + */ + public FooterRow prependFooterRow() { + return footer.prependRow(); + } + + /** + * Removes the given row from the footer section. + * + * @param row + * the row to be removed + * + * @throws IllegalArgumentException + * if the row does not exist in this section + * @see #removeFooterRow(int) + * @see #addFooterRowAt(int) + * @see #appendFooterRow() + * @see #prependFooterRow() + */ + public void removeFooterRow(FooterRow row) { + footer.removeRow(row); + } + + /** + * Removes the row at the given position from the footer section. + * + * @param index + * the position of the row + * + * @throws IllegalArgumentException + * if no row exists at given index + * @see #removeFooterRow(FooterRow) + * @see #addFooterRowAt(int) + * @see #appendFooterRow() + * @see #prependFooterRow() + */ + public void removeFooterRow(int rowIndex) { + footer.removeRow(rowIndex); + } + + /** + * Sets the visibility of the footer section. + * + * @param visible + * true to show footer section, false to hide + */ + public void setFooterVisible(boolean visible) { + footer.setVisible(visible); + } + + /** + * Returns the visibility of the footer section. + * + * @return true if visible, false otherwise. + */ + public boolean isFooterVisible() { + return footer.isVisible(); + } + + public Editor getEditor() { + return editor; + } + + protected Escalator getEscalator() { + return escalator; + } + + /** + * {@inheritDoc} + *

+ * Note: This method will change the widget's size in the browser + * only if {@link #getHeightMode()} returns {@link HeightMode#CSS}. + * + * @see #setHeightMode(HeightMode) + */ + @Override + public void setHeight(String height) { + escalator.setHeight(height); + } + + @Override + public void setWidth(String width) { + escalator.setWidth(width); + } + + /** + * Sets the data source used by this grid. + * + * @param dataSource + * the data source to use, not null + * @throws IllegalArgumentException + * if dataSource is null + */ + public void setDataSource(final DataSource dataSource) + throws IllegalArgumentException { + if (dataSource == null) { + throw new IllegalArgumentException("dataSource can't be null."); + } + + selectionModel.reset(); + + if (this.dataSource != null) { + this.dataSource.setDataChangeHandler(null); + } + + this.dataSource = dataSource; + dataSource.setDataChangeHandler(new DataChangeHandler() { + @Override + public void dataUpdated(int firstIndex, int numberOfItems) { + escalator.getBody().refreshRows(firstIndex, numberOfItems); + } + + @Override + public void dataRemoved(int firstIndex, int numberOfItems) { + escalator.getBody().removeRows(firstIndex, numberOfItems); + Range removed = Range.withLength(firstIndex, numberOfItems); + cellFocusHandler.rowsRemovedFromBody(removed); + } + + @Override + public void dataAdded(int firstIndex, int numberOfItems) { + escalator.getBody().insertRows(firstIndex, numberOfItems); + Range added = Range.withLength(firstIndex, numberOfItems); + cellFocusHandler.rowsAddedToBody(added); + } + + @Override + public void dataAvailable(int firstIndex, int numberOfItems) { + currentDataAvailable = Range.withLength(firstIndex, + numberOfItems); + fireEvent(new DataAvailableEvent(currentDataAvailable)); + } + + @Override + public void resetDataAndSize(int newSize) { + RowContainer body = escalator.getBody(); + int oldSize = body.getRowCount(); + + // Hide all details. + Set oldDetails = new HashSet(visibleDetails); + for (int i : oldDetails) { + setDetailsVisible(i, false); + } + + if (newSize > oldSize) { + body.insertRows(oldSize, newSize - oldSize); + cellFocusHandler.rowsAddedToBody(Range.withLength(oldSize, + newSize - oldSize)); + } else if (newSize < oldSize) { + body.removeRows(newSize, oldSize - newSize); + cellFocusHandler.rowsRemovedFromBody(Range.withLength( + newSize, oldSize - newSize)); + } + + if (newSize > 0) { + dataIsBeingFetched = true; + Range visibleRowRange = escalator.getVisibleRowRange(); + dataSource.ensureAvailability(visibleRowRange.getStart(), + visibleRowRange.length()); + } else { + // We won't expect any data more data updates, so just make + // the bookkeeping happy + dataAvailable(0, 0); + } + + assert body.getRowCount() == newSize; + } + }); + + int previousRowCount = escalator.getBody().getRowCount(); + if (previousRowCount != 0) { + escalator.getBody().removeRows(0, previousRowCount); + } + + setEscalatorSizeFromDataSource(); + } + + private void setEscalatorSizeFromDataSource() { + assert escalator.getBody().getRowCount() == 0; + + int size = dataSource.size(); + if (size == -1 && isAttached()) { + // Exact size is not yet known, start with some reasonable guess + // just to get an initial backend request going + size = getEscalator().getMaxVisibleRowCount(); + } + if (size > 0) { + escalator.getBody().insertRows(0, size); + } + } + + /** + * Gets the {@Link DataSource} for this Grid. + * + * @return the data source used by this grid + */ + public DataSource getDataSource() { + return dataSource; + } + + /** + * Sets the number of frozen columns in this grid. Setting the count to 0 + * means that no data columns will be frozen, but the built-in selection + * checkbox column will still be frozen if it's in use. Setting the count to + * -1 will also disable the selection column. + *

+ * The default value is 0. + * + * @param numberOfColumns + * the number of columns that should be frozen + * + * @throws IllegalArgumentException + * if the column count is < -1 or > the number of visible + * columns + */ + public void setFrozenColumnCount(int numberOfColumns) { + if (numberOfColumns < -1 || numberOfColumns > getColumnCount()) { + throw new IllegalArgumentException( + "count must be between -1 and the current number of columns (" + + getColumnCount() + ")"); + } + + frozenColumnCount = numberOfColumns; + updateFrozenColumns(); + } + + private void updateFrozenColumns() { + escalator.getColumnConfiguration().setFrozenColumnCount( + getVisibleFrozenColumnCount()); + } + + private int getVisibleFrozenColumnCount() { + int numberOfColumns = getFrozenColumnCount(); + + // for the escalator the hidden columns are not in the frozen column + // count, but for grid they are. thus need to convert the index + for (int i = 0; i < frozenColumnCount; i++) { + if (getColumn(i).isHidden()) { + numberOfColumns--; + } + } + + if (numberOfColumns == -1) { + numberOfColumns = 0; + } else if (selectionColumn != null) { + numberOfColumns++; + } + return numberOfColumns; + } + + /** + * Gets the number of frozen columns in this grid. 0 means that no data + * columns will be frozen, but the built-in selection checkbox column will + * still be frozen if it's in use. -1 means that not even the selection + * column is frozen. + *

+ * NOTE: This includes {@link Column#isHidden() hidden columns} in + * the count. + * + * @return the number of frozen columns + */ + public int getFrozenColumnCount() { + return frozenColumnCount; + } + + public HandlerRegistration addRowVisibilityChangeHandler( + RowVisibilityChangeHandler handler) { + /* + * Reusing Escalator's RowVisibilityChangeHandler, since a scroll + * concept is too abstract. e.g. the event needs to be re-sent when the + * widget is resized. + */ + return escalator.addRowVisibilityChangeHandler(handler); + } + + /** + * Scrolls to a certain row, using {@link ScrollDestination#ANY}. + *

+ * If the details for that row are visible, those will be taken into account + * as well. + * + * @param rowIndex + * zero-based index of the row to scroll to. + * @throws IllegalArgumentException + * if rowIndex is below zero, or above the maximum value + * supported by the data source. + */ + public void scrollToRow(int rowIndex) throws IllegalArgumentException { + scrollToRow(rowIndex, ScrollDestination.ANY, + GridConstants.DEFAULT_PADDING); + } + + /** + * Scrolls to a certain row, using user-specified scroll destination. + *

+ * If the details for that row are visible, those will be taken into account + * as well. + * + * @param rowIndex + * zero-based index of the row to scroll to. + * @param destination + * desired destination placement of scrolled-to-row. See + * {@link ScrollDestination} for more information. + * @throws IllegalArgumentException + * if rowIndex is below zero, or above the maximum value + * supported by the data source. + */ + public void scrollToRow(int rowIndex, ScrollDestination destination) + throws IllegalArgumentException { + scrollToRow(rowIndex, destination, + destination == ScrollDestination.MIDDLE ? 0 + : GridConstants.DEFAULT_PADDING); + } + + /** + * Scrolls to a certain row using only user-specified parameters. + *

+ * If the details for that row are visible, those will be taken into account + * as well. + * + * @param rowIndex + * zero-based index of the row to scroll to. + * @param destination + * desired destination placement of scrolled-to-row. See + * {@link ScrollDestination} for more information. + * @param paddingPx + * number of pixels to overscroll. Behavior depends on + * destination. + * @throws IllegalArgumentException + * if {@code destination} is {@link ScrollDestination#MIDDLE} + * and padding is nonzero, because having a padding on a + * centered row is undefined behavior, or if rowIndex is below + * zero or above the row count of the data source. + */ + private void scrollToRow(int rowIndex, ScrollDestination destination, + int paddingPx) throws IllegalArgumentException { + int maxsize = escalator.getBody().getRowCount() - 1; + + if (rowIndex < 0) { + throw new IllegalArgumentException("Row index (" + rowIndex + + ") is below zero!"); + } + + if (rowIndex > maxsize) { + throw new IllegalArgumentException("Row index (" + rowIndex + + ") is above maximum (" + maxsize + ")!"); + } + + escalator.scrollToRowAndSpacer(rowIndex, destination, paddingPx); + } + + /** + * Scrolls to the beginning of the very first row. + */ + public void scrollToStart() { + scrollToRow(0, ScrollDestination.START); + } + + /** + * Scrolls to the end of the very last row. + */ + public void scrollToEnd() { + scrollToRow(escalator.getBody().getRowCount() - 1, + ScrollDestination.END); + } + + /** + * Sets the vertical scroll offset. + * + * @param px + * the number of pixels this grid should be scrolled down + */ + public void setScrollTop(double px) { + escalator.setScrollTop(px); + } + + /** + * Gets the vertical scroll offset + * + * @return the number of pixels this grid is scrolled down + */ + public double getScrollTop() { + return escalator.getScrollTop(); + } + + /** + * Sets the horizontal scroll offset + * + * @since 7.5.0 + * @param px + * the number of pixels this grid should be scrolled right + */ + public void setScrollLeft(double px) { + escalator.setScrollLeft(px); + } + + /** + * Gets the horizontal scroll offset + * + * @return the number of pixels this grid is scrolled to the right + */ + public double getScrollLeft() { + return escalator.getScrollLeft(); + } + + /** + * Returns the height of the scrollable area in pixels. + * + * @since 7.5.0 + * @return the height of the scrollable area in pixels + */ + public double getScrollHeight() { + return escalator.getScrollHeight(); + } + + /** + * Returns the width of the scrollable area in pixels. + * + * @since 7.5.0 + * @return the width of the scrollable area in pixels. + */ + public double getScrollWidth() { + return escalator.getScrollWidth(); + } + + private static final Logger getLogger() { + return Logger.getLogger(Grid.class.getName()); + } + + /** + * Sets the number of rows that should be visible in Grid's body, while + * {@link #getHeightMode()} is {@link HeightMode#ROW}. + *

+ * If Grid is currently not in {@link HeightMode#ROW}, the given value is + * remembered, and applied once the mode is applied. + * + * @param rows + * The height in terms of number of rows displayed in Grid's + * body. If Grid doesn't contain enough rows, white space is + * displayed instead. + * @throws IllegalArgumentException + * if {@code rows} is zero or less + * @throws IllegalArgumentException + * if {@code rows} is {@link Double#isInifinite(double) + * infinite} + * @throws IllegalArgumentException + * if {@code rows} is {@link Double#isNaN(double) NaN} + * + * @see #setHeightMode(HeightMode) + */ + public void setHeightByRows(double rows) throws IllegalArgumentException { + escalator.setHeightByRows(rows); + } + + /** + * Gets the amount of rows in Grid's body that are shown, while + * {@link #getHeightMode()} is {@link HeightMode#ROW}. + *

+ * By default, it is {@value Escalator#DEFAULT_HEIGHT_BY_ROWS}. + * + * @return the amount of rows that should be shown in Grid's body, while in + * {@link HeightMode#ROW}. + * @see #setHeightByRows(double) + */ + public double getHeightByRows() { + return escalator.getHeightByRows(); + } + + /** + * Defines the mode in which the Grid widget's height is calculated. + *

+ * If {@link HeightMode#CSS} is given, Grid will respect the values given + * via {@link #setHeight(String)}, and behave as a traditional Widget. + *

+ * If {@link HeightMode#ROW} is given, Grid will make sure that the body + * will display as many rows as {@link #getHeightByRows()} defines. + * Note: If headers/footers are inserted or removed, the widget + * will resize itself to still display the required amount of rows in its + * body. It also takes the horizontal scrollbar into account. + * + * @param heightMode + * the mode in to which Grid should be set + */ + public void setHeightMode(HeightMode heightMode) { + /* + * This method is a workaround for the fact that Vaadin re-applies + * widget dimensions (height/width) on each state change event. The + * original design was to have setHeight an setHeightByRow be equals, + * and whichever was called the latest was considered in effect. + * + * But, because of Vaadin always calling setHeight on the widget, this + * approach doesn't work. + */ + + escalator.setHeightMode(heightMode); + } + + /** + * Returns the current {@link HeightMode} the Grid is in. + *

+ * Defaults to {@link HeightMode#CSS}. + * + * @return the current HeightMode + */ + public HeightMode getHeightMode() { + return escalator.getHeightMode(); + } + + private Set getConsumedEventsForRenderer(Renderer renderer) { + Set events = new HashSet(); + if (renderer instanceof ComplexRenderer) { + Collection consumedEvents = ((ComplexRenderer) renderer) + .getConsumedEvents(); + if (consumedEvents != null) { + events.addAll(consumedEvents); + } + } + return events; + } + + @Override + public void onBrowserEvent(Event event) { + if (!isEnabled()) { + return; + } + + String eventType = event.getType(); + + if (eventType.equals(BrowserEvents.FOCUS) + || eventType.equals(BrowserEvents.BLUR)) { + super.onBrowserEvent(event); + return; + } + + EventTarget target = event.getEventTarget(); + + if (!Element.is(target) || isOrContainsInSpacer(Element.as(target))) { + return; + } + + Element e = Element.as(target); + RowContainer container = escalator.findRowContainer(e); + Cell cell; + + if (container == null) { + if (eventType.equals(BrowserEvents.KEYDOWN) + || eventType.equals(BrowserEvents.KEYUP) + || eventType.equals(BrowserEvents.KEYPRESS)) { + cell = cellFocusHandler.getFocusedCell(); + container = cellFocusHandler.containerWithFocus; + } else { + // Click might be in an editor cell, should still map. + if (editor.editorOverlay != null + && editor.editorOverlay.isOrHasChild(e)) { + container = escalator.getBody(); + int rowIndex = editor.getRow(); + int colIndex = editor.getElementColumn(e); + + if (colIndex < 0) { + // Click in editor, but not for any column. + return; + } + + TableCellElement cellElement = container + .getRowElement(rowIndex).getCells() + .getItem(colIndex); + + cell = new Cell(rowIndex, colIndex, cellElement); + } else { + if (escalator.getElement().isOrHasChild(e)) { + eventCell.set(new Cell(-1, -1, null), Section.BODY); + // Fire native events. + super.onBrowserEvent(event); + } + return; + } + } + } else { + cell = container.getCell(e); + if (eventType.equals(BrowserEvents.MOUSEDOWN)) { + cellOnPrevMouseDown = cell; + } else if (cell == null && eventType.equals(BrowserEvents.CLICK)) { + /* + * Chrome has an interesting idea on click targets (see + * cellOnPrevMouseDown javadoc). Firefox, on the other hand, has + * the mousedown target as the click target. + */ + cell = cellOnPrevMouseDown; + } + } + + assert cell != null : "received " + eventType + + "-event with a null cell target"; + eventCell.set(cell, getSectionFromContainer(container)); + + // Editor can steal focus from Grid and is still handled + if (isEditorEnabled() && handleEditorEvent(event, container)) { + return; + } + + // Fire GridKeyEvents and GridClickEvents. Pass the event to escalator. + super.onBrowserEvent(event); + + if (!isElementInChildWidget(e)) { + + if (handleHeaderCellDragStartEvent(event, container)) { + return; + } + + // Sorting through header Click / KeyUp + if (handleHeaderDefaultRowEvent(event, container)) { + return; + } + + if (handleRendererEvent(event, container)) { + return; + } + + if (handleCellFocusEvent(event, container)) { + return; + } + } + } + + private Section getSectionFromContainer(RowContainer container) { + assert container != null : "RowContainer should not be null"; + + if (container == escalator.getBody()) { + return Section.BODY; + } else if (container == escalator.getFooter()) { + return Section.FOOTER; + } else if (container == escalator.getHeader()) { + return Section.HEADER; + } + assert false : "RowContainer was not header, footer or body."; + return null; + } + + private boolean isOrContainsInSpacer(Node node) { + Node n = node; + while (n != null && n != getElement()) { + boolean isElement = Element.is(n); + if (isElement) { + String className = Element.as(n).getClassName(); + if (className.contains(getStylePrimaryName() + "-spacer")) { + return true; + } + } + n = n.getParentNode(); + } + return false; + } + + private boolean isElementInChildWidget(Element e) { + Widget w = WidgetUtil.findWidget(e, null); + + if (w == this) { + return false; + } + + /* + * If e is directly inside this grid, but the grid is wrapped in a + * Composite, findWidget is not going to find this, only the wrapper. + * Thus we need to check its parents to see if we encounter this; if we + * don't, the found widget is actually a parent of this, so we should + * return false. + */ + while (w != null && w != this) { + w = w.getParent(); + } + return w != null; + } + + private boolean handleEditorEvent(Event event, RowContainer container) { + Widget w; + if (editor.focusedColumnIndex < 0) { + w = null; + } else { + w = editor.getWidget(getColumn(editor.focusedColumnIndex)); + } + + EditorDomEvent editorEvent = new EditorDomEvent(event, + getEventCell(), w); + + return getEditor().getEventHandler().handleEvent(editorEvent); + } + + private boolean handleRendererEvent(Event event, RowContainer container) { + + if (container == escalator.getBody()) { + Column gridColumn = eventCell.getColumn(); + boolean enterKey = event.getType().equals(BrowserEvents.KEYDOWN) + && event.getKeyCode() == KeyCodes.KEY_ENTER; + boolean doubleClick = event.getType() + .equals(BrowserEvents.DBLCLICK); + + if (gridColumn.getRenderer() instanceof ComplexRenderer) { + ComplexRenderer cplxRenderer = (ComplexRenderer) gridColumn + .getRenderer(); + if (cplxRenderer.getConsumedEvents().contains(event.getType())) { + if (cplxRenderer.onBrowserEvent(eventCell, event)) { + return true; + } + } + + // Calls onActivate if KeyDown and Enter or double click + if ((enterKey || doubleClick) + && cplxRenderer.onActivate(eventCell)) { + return true; + } + } + } + return false; + } + + private boolean handleCellFocusEvent(Event event, RowContainer container) { + Collection navigation = cellFocusHandler.getNavigationEvents(); + if (navigation.contains(event.getType())) { + cellFocusHandler.handleNavigationEvent(event, eventCell); + } + return false; + } + + private boolean handleHeaderCellDragStartEvent(Event event, + RowContainer container) { + if (!isColumnReorderingAllowed()) { + return false; + } + if (container != escalator.getHeader()) { + return false; + } + if (eventCell.getColumnIndex() < escalator.getColumnConfiguration() + .getFrozenColumnCount()) { + return false; + } + + if (event.getTypeInt() == Event.ONMOUSEDOWN + && event.getButton() == NativeEvent.BUTTON_LEFT + || event.getTypeInt() == Event.ONTOUCHSTART) { + dndHandler.onDragStartOnDraggableElement(event, + headerCellDndCallback); + event.preventDefault(); + event.stopPropagation(); + return true; + } + return false; + } + + private Point rowEventTouchStartingPoint; + private CellStyleGenerator cellStyleGenerator; + private RowStyleGenerator rowStyleGenerator; + private RowReference rowReference = new RowReference(this); + private CellReference cellReference = new CellReference(rowReference); + private RendererCellReference rendererCellReference = new RendererCellReference( + (RowReference) rowReference); + + private boolean handleHeaderDefaultRowEvent(Event event, + RowContainer container) { + if (container != escalator.getHeader()) { + return false; + } + if (!getHeader().getRow(eventCell.getRowIndex()).isDefault()) { + return false; + } + if (!eventCell.getColumn().isSortable()) { + // Only handle sorting events if the column is sortable + return false; + } + + if (BrowserEvents.MOUSEDOWN.equals(event.getType()) + && event.getShiftKey()) { + // Don't select text when shift clicking on a header. + event.preventDefault(); + } + + if (BrowserEvents.TOUCHSTART.equals(event.getType())) { + if (event.getTouches().length() > 1) { + return false; + } + + event.preventDefault(); + + Touch touch = event.getChangedTouches().get(0); + rowEventTouchStartingPoint = new Point(touch.getClientX(), + touch.getClientY()); + + sorter.sortAfterDelay(GridConstants.LONG_TAP_DELAY, true); + + return true; + + } else if (BrowserEvents.TOUCHMOVE.equals(event.getType())) { + if (event.getTouches().length() > 1) { + return false; + } + + event.preventDefault(); + + Touch touch = event.getChangedTouches().get(0); + double diffX = Math.abs(touch.getClientX() + - rowEventTouchStartingPoint.getX()); + double diffY = Math.abs(touch.getClientY() + - rowEventTouchStartingPoint.getY()); + + // Cancel long tap if finger strays too far from + // starting point + if (diffX > GridConstants.LONG_TAP_THRESHOLD + || diffY > GridConstants.LONG_TAP_THRESHOLD) { + sorter.cancelDelayedSort(); + } + + return true; + + } else if (BrowserEvents.TOUCHEND.equals(event.getType())) { + if (event.getTouches().length() > 1) { + return false; + } + + if (sorter.isDelayedSortScheduled()) { + // Not a long tap yet, perform single sort + sorter.cancelDelayedSort(); + sorter.sort(eventCell.getColumn(), false); + } + + return true; + + } else if (BrowserEvents.TOUCHCANCEL.equals(event.getType())) { + if (event.getTouches().length() > 1) { + return false; + } + + sorter.cancelDelayedSort(); + + return true; + + } else if (BrowserEvents.CLICK.equals(event.getType())) { + + sorter.sort(eventCell.getColumn(), event.getShiftKey()); + + // Click events should go onward to cell focus logic + return false; + } else { + return false; + } + } + + @Override + @SuppressWarnings("deprecation") + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + + /* + * handles details[] (translated to spacer[] for Escalator), cell[], + * header[] and footer[] + */ + + // "#header[0][0]/DRAGhANDLE" + Element escalatorElement = escalator.getSubPartElement(subPart + .replaceFirst("^details\\[", "spacer[")); + + if (escalatorElement != null) { + + int detailIdx = subPart.indexOf("/"); + if (detailIdx > 0) { + String detail = subPart.substring(detailIdx + 1); + getLogger().severe( + "Looking up detail from index " + detailIdx + + " onward: \"" + detail + "\""); + if (detail.equalsIgnoreCase("content")) { + // XXX: Fix this to look up by class name! + return DOM.asOld(Element.as(escalatorElement.getChild(0))); + } + if (detail.equalsIgnoreCase("draghandle")) { + // XXX: Fix this to look up by class name! + return DOM.asOld(Element.as(escalatorElement.getChild(1))); + } + } + + return DOM.asOld(escalatorElement); + } + + SubPartArguments args = SubPartArguments.create(subPart); + Element editor = getSubPartElementEditor(args); + if (editor != null) { + return DOM.asOld(editor); + } + + return null; + } + + private Element getSubPartElementEditor(SubPartArguments args) { + + if (!args.getType().equalsIgnoreCase("editor") + || editor.getState() != State.ACTIVE) { + return null; + } + + if (args.getIndicesLength() == 0) { + return editor.editorOverlay; + } else if (args.getIndicesLength() == 1) { + int index = args.getIndex(0); + if (index >= columns.size()) { + return null; + } + + escalator.scrollToColumn(index, ScrollDestination.ANY, 0); + Widget widget = editor.getWidget(columns.get(index)); + + if (widget != null) { + return widget.getElement(); + } + + // No widget for the column. + return null; + } + + return null; + } + + @Override + @SuppressWarnings("deprecation") + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + + String escalatorStructureName = escalator.getSubPartName(subElement); + if (escalatorStructureName != null) { + return escalatorStructureName.replaceFirst("^spacer", "details"); + } + + String editorName = getSubPartNameEditor(subElement); + if (editorName != null) { + return editorName; + } + + return null; + } + + private String getSubPartNameEditor(Element subElement) { + + if (editor.getState() != State.ACTIVE + || !editor.editorOverlay.isOrHasChild(subElement)) { + return null; + } + + int i = 0; + for (Column column : columns) { + if (editor.getWidget(column).getElement().isOrHasChild(subElement)) { + return "editor[" + i + "]"; + } + ++i; + } + + return "editor"; + } + + private void setSelectColumnRenderer( + final Renderer selectColumnRenderer) { + if (this.selectColumnRenderer == selectColumnRenderer) { + return; + } + + if (this.selectColumnRenderer != null) { + if (this.selectColumnRenderer instanceof ComplexRenderer) { + // End of Life for the old selection column renderer. + ((ComplexRenderer) this.selectColumnRenderer).destroy(); + } + + // Clear field so frozen column logic in the remove method knows + // what to do + Column colToRemove = selectionColumn; + selectionColumn = null; + removeColumnSkipSelectionColumnCheck(colToRemove); + cellFocusHandler.offsetRangeBy(-1); + } + + this.selectColumnRenderer = selectColumnRenderer; + + if (selectColumnRenderer != null) { + cellFocusHandler.offsetRangeBy(1); + selectionColumn = new SelectionColumn(selectColumnRenderer); + + addColumnSkipSelectionColumnCheck(selectionColumn, 0); + selectionColumn.initDone(); + } else { + selectionColumn = null; + refreshBody(); + } + + updateFrozenColumns(); + } + + /** + * Sets the current selection model. + *

+ * This function will call {@link SelectionModel#setGrid(Grid)}. + * + * @param selectionModel + * a selection model implementation. + * @throws IllegalArgumentException + * if selection model argument is null + */ + public void setSelectionModel(SelectionModel selectionModel) { + + if (selectionModel == null) { + throw new IllegalArgumentException("Selection model can't be null"); + } + + if (this.selectionModel != null) { + // Detach selection model from Grid. + this.selectionModel.setGrid(null); + } + + this.selectionModel = selectionModel; + selectionModel.setGrid(this); + setSelectColumnRenderer(this.selectionModel + .getSelectionColumnRenderer()); + + // Refresh rendered rows to update selection, if it has changed + refreshBody(); + } + + /** + * Gets a reference to the current selection model. + * + * @return the currently used SelectionModel instance. + */ + public SelectionModel getSelectionModel() { + return selectionModel; + } + + /** + * Sets current selection mode. + *

+ * This is a shorthand method for {@link Grid#setSelectionModel}. + * + * @param mode + * a selection mode value + * @see {@link SelectionMode}. + */ + public void setSelectionMode(SelectionMode mode) { + SelectionModel model = mode.createModel(); + setSelectionModel(model); + } + + /** + * Test if a row is selected. + * + * @param row + * a row object + * @return true, if the current selection model considers the provided row + * object selected. + */ + public boolean isSelected(T row) { + return selectionModel.isSelected(row); + } + + /** + * Select a row using the current selection model. + *

+ * Only selection models implementing {@link SelectionModel.Single} and + * {@link SelectionModel.Multi} are supported; for anything else, an + * exception will be thrown. + * + * @param row + * a row object + * @return true iff the current selection changed + * @throws IllegalStateException + * if the current selection model is not an instance of + * {@link SelectionModel.Single} or {@link SelectionModel.Multi} + */ + public boolean select(T row) { + if (selectionModel instanceof SelectionModel.Single) { + return ((SelectionModel.Single) selectionModel).select(row); + } else if (selectionModel instanceof SelectionModel.Multi) { + return ((SelectionModel.Multi) selectionModel) + .select(Collections.singleton(row)); + } else { + throw new IllegalStateException("Unsupported selection model"); + } + } + + /** + * Deselect a row using the current selection model. + *

+ * Only selection models implementing {@link SelectionModel.Single} and + * {@link SelectionModel.Multi} are supported; for anything else, an + * exception will be thrown. + * + * @param row + * a row object + * @return true iff the current selection changed + * @throws IllegalStateException + * if the current selection model is not an instance of + * {@link SelectionModel.Single} or {@link SelectionModel.Multi} + */ + public boolean deselect(T row) { + if (selectionModel instanceof SelectionModel.Single) { + return ((SelectionModel.Single) selectionModel).deselect(row); + } else if (selectionModel instanceof SelectionModel.Multi) { + return ((SelectionModel.Multi) selectionModel) + .deselect(Collections.singleton(row)); + } else { + throw new IllegalStateException("Unsupported selection model"); + } + } + + /** + * Deselect all rows using the current selection model. + * + * @param row + * a row object + * @return true iff the current selection changed + * @throws IllegalStateException + * if the current selection model is not an instance of + * {@link SelectionModel.Single} or {@link SelectionModel.Multi} + */ + public boolean deselectAll() { + if (selectionModel instanceof SelectionModel.Single) { + Single single = ((SelectionModel.Single) selectionModel); + if (single.getSelectedRow() != null) { + return single.deselect(single.getSelectedRow()); + } else { + return false; + } + } else if (selectionModel instanceof SelectionModel.Multi) { + return ((SelectionModel.Multi) selectionModel).deselectAll(); + } else { + throw new IllegalStateException("Unsupported selection model"); + } + } + + /** + * Gets last selected row from the current SelectionModel. + *

+ * Only selection models implementing {@link SelectionModel.Single} are + * valid for this method; for anything else, use the + * {@link Grid#getSelectedRows()} method. + * + * @return a selected row reference, or null, if no row is selected + * @throws IllegalStateException + * if the current selection model is not an instance of + * {@link SelectionModel.Single} + */ + public T getSelectedRow() { + if (selectionModel instanceof SelectionModel.Single) { + return ((SelectionModel.Single) selectionModel).getSelectedRow(); + } else { + throw new IllegalStateException( + "Unsupported selection model; can not get single selected row"); + } + } + + /** + * Gets currently selected rows from the current selection model. + * + * @return a non-null collection containing all currently selected rows. + */ + public Collection getSelectedRows() { + return selectionModel.getSelectedRows(); + } + + @Override + public HandlerRegistration addSelectionHandler( + final SelectionHandler handler) { + return addHandler(handler, SelectionEvent.getType()); + } + + /** + * Sets the current sort order using the fluid Sort API. Read the + * documentation for {@link Sort} for more information. + * + * @param s + * a sort instance + */ + public void sort(Sort s) { + setSortOrder(s.build()); + } + + /** + * Sorts the Grid data in ascending order along one column. + * + * @param column + * a grid column reference + */ + public void sort(Column column) { + sort(column, SortDirection.ASCENDING); + } + + /** + * Sorts the Grid data along one column. + * + * @param column + * a grid column reference + * @param direction + * a sort direction value + */ + public void sort(Column column, SortDirection direction) { + sort(Sort.by(column, direction)); + } + + /** + * Sets the sort order to use. Setting this causes the Grid to re-sort + * itself. + * + * @param order + * a sort order list. If set to null, the sort order is cleared. + */ + public void setSortOrder(List order) { + setSortOrder(order, false); + } + + /** + * Clears the sort order and indicators without re-sorting. + */ + private void clearSortOrder() { + sortOrder.clear(); + refreshHeader(); + } + + private void setSortOrder(List order, boolean userOriginated) { + if (order != sortOrder) { + sortOrder.clear(); + if (order != null) { + sortOrder.addAll(order); + } + } + sort(userOriginated); + } + + /** + * Get a copy of the current sort order array. + * + * @return a copy of the current sort order array + */ + public List getSortOrder() { + return Collections.unmodifiableList(sortOrder); + } + + /** + * Finds the sorting order for this column + */ + private SortOrder getSortOrder(Column column) { + for (SortOrder order : getSortOrder()) { + if (order.getColumn() == column) { + return order; + } + } + return null; + } + + /** + * Register a GWT event handler for a sorting event. This handler gets + * called whenever this Grid needs its data source to provide data sorted in + * a specific order. + * + * @param handler + * a sort event handler + * @return the registration for the event + */ + public HandlerRegistration addSortHandler(SortHandler handler) { + return addHandler(handler, SortEvent.getType()); + } + + /** + * Register a GWT event handler for a select all event. This handler gets + * called whenever Grid needs all rows selected. + * + * @param handler + * a select all event handler + */ + public HandlerRegistration addSelectAllHandler(SelectAllHandler handler) { + return addHandler(handler, SelectAllEvent.getType()); + } + + /** + * Register a GWT event handler for a data available event. This handler + * gets called whenever the {@link DataSource} for this Grid has new data + * available. + *

+ * This handle will be fired with the current available data after + * registration is done. + * + * @param handler + * a data available event handler + * @return the registartion for the event + */ + public HandlerRegistration addDataAvailableHandler( + final DataAvailableHandler handler) { + // Deferred call to handler with current row range + Scheduler.get().scheduleFinally(new ScheduledCommand() { + @Override + public void execute() { + if (!dataIsBeingFetched) { + handler.onDataAvailable(new DataAvailableEvent( + currentDataAvailable)); + } + } + }); + return addHandler(handler, DataAvailableEvent.TYPE); + } + + /** + * Register a BodyKeyDownHandler to this Grid. The event for this handler is + * fired when a KeyDown event occurs while cell focus is in the Body of this + * Grid. + * + * @param handler + * the key handler to register + * @return the registration for the event + */ + public HandlerRegistration addBodyKeyDownHandler(BodyKeyDownHandler handler) { + return addHandler(handler, keyDown.getAssociatedType()); + } + + /** + * Register a BodyKeyUpHandler to this Grid. The event for this handler is + * fired when a KeyUp event occurs while cell focus is in the Body of this + * Grid. + * + * @param handler + * the key handler to register + * @return the registration for the event + */ + public HandlerRegistration addBodyKeyUpHandler(BodyKeyUpHandler handler) { + return addHandler(handler, keyUp.getAssociatedType()); + } + + /** + * Register a BodyKeyPressHandler to this Grid. The event for this handler + * is fired when a KeyPress event occurs while cell focus is in the Body of + * this Grid. + * + * @param handler + * the key handler to register + * @return the registration for the event + */ + public HandlerRegistration addBodyKeyPressHandler( + BodyKeyPressHandler handler) { + return addHandler(handler, keyPress.getAssociatedType()); + } + + /** + * Register a HeaderKeyDownHandler to this Grid. The event for this handler + * is fired when a KeyDown event occurs while cell focus is in the Header of + * this Grid. + * + * @param handler + * the key handler to register + * @return the registration for the event + */ + public HandlerRegistration addHeaderKeyDownHandler( + HeaderKeyDownHandler handler) { + return addHandler(handler, keyDown.getAssociatedType()); + } + + /** + * Register a HeaderKeyUpHandler to this Grid. The event for this handler is + * fired when a KeyUp event occurs while cell focus is in the Header of this + * Grid. + * + * @param handler + * the key handler to register + * @return the registration for the event + */ + public HandlerRegistration addHeaderKeyUpHandler(HeaderKeyUpHandler handler) { + return addHandler(handler, keyUp.getAssociatedType()); + } + + /** + * Register a HeaderKeyPressHandler to this Grid. The event for this handler + * is fired when a KeyPress event occurs while cell focus is in the Header + * of this Grid. + * + * @param handler + * the key handler to register + * @return the registration for the event + */ + public HandlerRegistration addHeaderKeyPressHandler( + HeaderKeyPressHandler handler) { + return addHandler(handler, keyPress.getAssociatedType()); + } + + /** + * Register a FooterKeyDownHandler to this Grid. The event for this handler + * is fired when a KeyDown event occurs while cell focus is in the Footer of + * this Grid. + * + * @param handler + * the key handler to register + * @return the registration for the event + */ + public HandlerRegistration addFooterKeyDownHandler( + FooterKeyDownHandler handler) { + return addHandler(handler, keyDown.getAssociatedType()); + } + + /** + * Register a FooterKeyUpHandler to this Grid. The event for this handler is + * fired when a KeyUp event occurs while cell focus is in the Footer of this + * Grid. + * + * @param handler + * the key handler to register + * @return the registration for the event + */ + public HandlerRegistration addFooterKeyUpHandler(FooterKeyUpHandler handler) { + return addHandler(handler, keyUp.getAssociatedType()); + } + + /** + * Register a FooterKeyPressHandler to this Grid. The event for this handler + * is fired when a KeyPress event occurs while cell focus is in the Footer + * of this Grid. + * + * @param handler + * the key handler to register + * @return the registration for the event + */ + public HandlerRegistration addFooterKeyPressHandler( + FooterKeyPressHandler handler) { + return addHandler(handler, keyPress.getAssociatedType()); + } + + /** + * Register a BodyClickHandler to this Grid. The event for this handler is + * fired when a Click event occurs in the Body of this Grid. + * + * @param handler + * the click handler to register + * @return the registration for the event + */ + public HandlerRegistration addBodyClickHandler(BodyClickHandler handler) { + return addHandler(handler, clickEvent.getAssociatedType()); + } + + /** + * Register a HeaderClickHandler to this Grid. The event for this handler is + * fired when a Click event occurs in the Header of this Grid. + * + * @param handler + * the click handler to register + * @return the registration for the event + */ + public HandlerRegistration addHeaderClickHandler(HeaderClickHandler handler) { + return addHandler(handler, clickEvent.getAssociatedType()); + } + + /** + * Register a FooterClickHandler to this Grid. The event for this handler is + * fired when a Click event occurs in the Footer of this Grid. + * + * @param handler + * the click handler to register + * @return the registration for the event + */ + public HandlerRegistration addFooterClickHandler(FooterClickHandler handler) { + return addHandler(handler, clickEvent.getAssociatedType()); + } + + /** + * Register a BodyDoubleClickHandler to this Grid. The event for this + * handler is fired when a double click event occurs in the Body of this + * Grid. + * + * @param handler + * the double click handler to register + * @return the registration for the event + */ + public HandlerRegistration addBodyDoubleClickHandler( + BodyDoubleClickHandler handler) { + return addHandler(handler, doubleClickEvent.getAssociatedType()); + } + + /** + * Register a HeaderDoubleClickHandler to this Grid. The event for this + * handler is fired when a double click event occurs in the Header of this + * Grid. + * + * @param handler + * the double click handler to register + * @return the registration for the event + */ + public HandlerRegistration addHeaderDoubleClickHandler( + HeaderDoubleClickHandler handler) { + return addHandler(handler, doubleClickEvent.getAssociatedType()); + } + + /** + * Register a FooterDoubleClickHandler to this Grid. The event for this + * handler is fired when a double click event occurs in the Footer of this + * Grid. + * + * @param handler + * the double click handler to register + * @return the registration for the event + */ + public HandlerRegistration addFooterDoubleClickHandler( + FooterDoubleClickHandler handler) { + return addHandler(handler, doubleClickEvent.getAssociatedType()); + } + + /** + * Register a column reorder handler to this Grid. The event for this + * handler is fired when the Grid's columns are reordered. + * + * @since 7.5.0 + * @param handler + * the handler for the event + * @return the registration for the event + */ + public HandlerRegistration addColumnReorderHandler( + ColumnReorderHandler handler) { + return addHandler(handler, ColumnReorderEvent.getType()); + } + + /** + * Register a column visibility change handler to this Grid. The event for + * this handler is fired when the Grid's columns change visibility. + * + * @since 7.5.0 + * @param handler + * the handler for the event + * @return the registration for the event + */ + public HandlerRegistration addColumnVisibilityChangeHandler( + ColumnVisibilityChangeHandler handler) { + return addHandler(handler, ColumnVisibilityChangeEvent.getType()); + } + + /** + * Register a column resize handler to this Grid. The event for this handler + * is fired when the Grid's columns are resized. + * + * @since 7.6 + * @param handler + * the handler for the event + * @return the registration for the event + */ + public HandlerRegistration addColumnResizeHandler( + ColumnResizeHandler handler) { + return addHandler(handler, ColumnResizeEvent.getType()); + } + + /** + * Apply sorting to data source. + */ + private void sort(boolean userOriginated) { + refreshHeader(); + fireEvent(new SortEvent(this, + Collections.unmodifiableList(sortOrder), userOriginated)); + } + + private int getLastVisibleRowIndex() { + int lastRowIndex = escalator.getVisibleRowRange().getEnd(); + int footerTop = escalator.getFooter().getElement().getAbsoluteTop(); + Element lastRow; + + do { + lastRow = escalator.getBody().getRowElement(--lastRowIndex); + } while (lastRow.getAbsoluteTop() > footerTop); + + return lastRowIndex; + } + + private int getFirstVisibleRowIndex() { + int firstRowIndex = escalator.getVisibleRowRange().getStart(); + int headerBottom = escalator.getHeader().getElement() + .getAbsoluteBottom(); + Element firstRow = escalator.getBody().getRowElement(firstRowIndex); + + while (firstRow.getAbsoluteBottom() < headerBottom) { + firstRow = escalator.getBody().getRowElement(++firstRowIndex); + } + + return firstRowIndex; + } + + /** + * Adds a scroll handler to this grid + * + * @param handler + * the scroll handler to add + * @return a handler registration for the registered scroll handler + */ + public HandlerRegistration addScrollHandler(ScrollHandler handler) { + return addHandler(handler, ScrollEvent.TYPE); + } + + @Override + public boolean isWorkPending() { + return escalator.isWorkPending() || dataIsBeingFetched + || autoColumnWidthsRecalculator.isScheduled() + || editor.isWorkPending(); + } + + /** + * Returns whether columns can be reordered with drag and drop. + * + * @since 7.5.0 + * @return true if columns can be reordered, false otherwise + */ + public boolean isColumnReorderingAllowed() { + return columnReorderingAllowed; + } + + /** + * Sets whether column reordering with drag and drop is allowed or not. + * + * @since 7.5.0 + * @param columnReorderingAllowed + * specifies whether column reordering is allowed + */ + public void setColumnReorderingAllowed(boolean columnReorderingAllowed) { + this.columnReorderingAllowed = columnReorderingAllowed; + } + + /** + * Sets a new column order for the grid. All columns which are not ordered + * here will remain in the order they were before as the last columns of + * grid. + * + * @param orderedColumns + * array of columns in wanted order + */ + public void setColumnOrder(Column... orderedColumns) { + ColumnConfiguration conf = getEscalator().getColumnConfiguration(); + + // Trigger ComplexRenderer.destroy for old content + conf.removeColumns(0, conf.getColumnCount()); + + List> newOrder = new ArrayList>(); + if (selectionColumn != null) { + newOrder.add(selectionColumn); + } + + int i = 0; + for (Column column : orderedColumns) { + if (columns.contains(column)) { + newOrder.add(column); + ++i; + } else { + throw new IllegalArgumentException("Given column at index " + i + + " does not exist in Grid"); + } + } + + if (columns.size() != newOrder.size()) { + columns.removeAll(newOrder); + newOrder.addAll(columns); + } + columns = newOrder; + + List> visibleColumns = getVisibleColumns(); + + // Do ComplexRenderer.init and render new content + conf.insertColumns(0, visibleColumns.size()); + + // Number of frozen columns should be kept same #16901 + updateFrozenColumns(); + + // Update column widths. + for (Column column : columns) { + column.reapplyWidth(); + } + + // Recalculate all the colspans + for (HeaderRow row : header.getRows()) { + row.calculateColspans(); + } + for (FooterRow row : footer.getRows()) { + row.calculateColspans(); + } + + columnHider.updateTogglesOrder(); + + fireEvent(new ColumnReorderEvent()); + } + + /** + * Sets the style generator that is used for generating styles for cells + * + * @param cellStyleGenerator + * the cell style generator to set, or null to + * remove a previously set generator + */ + public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) { + this.cellStyleGenerator = cellStyleGenerator; + refreshBody(); + } + + /** + * Gets the style generator that is used for generating styles for cells + * + * @return the cell style generator, or null if no generator is + * set + */ + public CellStyleGenerator getCellStyleGenerator() { + return cellStyleGenerator; + } + + /** + * Sets the style generator that is used for generating styles for rows + * + * @param rowStyleGenerator + * the row style generator to set, or null to remove + * a previously set generator + */ + public void setRowStyleGenerator(RowStyleGenerator rowStyleGenerator) { + this.rowStyleGenerator = rowStyleGenerator; + refreshBody(); + } + + /** + * Gets the style generator that is used for generating styles for rows + * + * @return the row style generator, or null if no generator is + * set + */ + public RowStyleGenerator getRowStyleGenerator() { + return rowStyleGenerator; + } + + private static void setCustomStyleName(Element element, String styleName) { + assert element != null; + + String oldStyleName = element + .getPropertyString(CUSTOM_STYLE_PROPERTY_NAME); + + if (!SharedUtil.equals(oldStyleName, styleName)) { + if (oldStyleName != null && !oldStyleName.isEmpty()) { + element.removeClassName(oldStyleName); + } + if (styleName != null && !styleName.isEmpty()) { + element.addClassName(styleName); + } + element.setPropertyString(CUSTOM_STYLE_PROPERTY_NAME, styleName); + } + + } + + /** + * Opens the editor over the row with the given index. + * + * @param rowIndex + * the index of the row to be edited + * + * @throws IllegalStateException + * if the editor is not enabled + * @throws IllegalStateException + * if the editor is already in edit mode + */ + public void editRow(int rowIndex) { + editor.editRow(rowIndex); + } + + /** + * Returns whether the editor is currently open on some row. + * + * @return {@code true} if the editor is active, {@code false} otherwise. + */ + public boolean isEditorActive() { + return editor.getState() != State.INACTIVE; + } + + /** + * Saves any unsaved changes in the editor to the data source. + * + * @throws IllegalStateException + * if the editor is not enabled + * @throws IllegalStateException + * if the editor is not in edit mode + */ + public void saveEditor() { + editor.save(); + } + + /** + * Cancels the currently active edit and hides the editor. Any changes that + * are not {@link #saveEditor() saved} are lost. + * + * @throws IllegalStateException + * if the editor is not enabled + * @throws IllegalStateException + * if the editor is not in edit mode + */ + public void cancelEditor() { + editor.cancel(); + } + + /** + * Returns the handler responsible for binding data and editor widgets to + * the editor. + * + * @return the editor handler or null if not set + */ + public EditorHandler getEditorHandler() { + return editor.getHandler(); + } + + /** + * Sets the handler responsible for binding data and editor widgets to the + * editor. + * + * @param rowHandler + * the new editor handler + * + * @throws IllegalStateException + * if the editor is currently in edit mode + */ + public void setEditorHandler(EditorHandler handler) { + editor.setHandler(handler); + } + + /** + * Returns the enabled state of the editor. + * + * @return true if editing is enabled, false otherwise + */ + public boolean isEditorEnabled() { + return editor.isEnabled(); + } + + /** + * Sets the enabled state of the editor. + * + * @param enabled + * true to enable editing, false to disable + * + * @throws IllegalStateException + * if in edit mode and trying to disable + * @throws IllegalStateException + * if the editor handler is not set + */ + public void setEditorEnabled(boolean enabled) { + editor.setEnabled(enabled); + } + + /** + * Returns the editor widget associated with the given column. If the editor + * is not active, returns null. + * + * @param column + * the column + * @return the widget if the editor is open, null otherwise + */ + public Widget getEditorWidget(Column column) { + return editor.getWidget(column); + } + + /** + * Sets the caption on the save button in the Grid editor. + * + * @param saveCaption + * the caption to set + * @throws IllegalArgumentException + * if {@code saveCaption} is {@code null} + */ + public void setEditorSaveCaption(String saveCaption) + throws IllegalArgumentException { + editor.setSaveCaption(saveCaption); + } + + /** + * Gets the current caption on the save button in the Grid editor. + * + * @return the current caption on the save button + */ + public String getEditorSaveCaption() { + return editor.getSaveCaption(); + } + + /** + * Sets the caption on the cancel button in the Grid editor. + * + * @param cancelCaption + * the caption to set + * @throws IllegalArgumentException + * if {@code cancelCaption} is {@code null} + */ + public void setEditorCancelCaption(String cancelCaption) + throws IllegalArgumentException { + editor.setCancelCaption(cancelCaption); + } + + /** + * Gets the caption on the cancel button in the Grid editor. + * + * @return the current caption on the cancel button + */ + public String getEditorCancelCaption() { + return editor.getCancelCaption(); + } + + @Override + protected void onAttach() { + super.onAttach(); + + if (getEscalator().getBody().getRowCount() == 0 && dataSource != null) { + setEscalatorSizeFromDataSource(); + } + + // Grid was just attached to DOM. Column widths should be calculated. + recalculateColumnWidths(); + } + + @Override + protected void onDetach() { + Set details = new HashSet(visibleDetails); + for (int row : details) { + setDetailsVisible(row, false); + } + + super.onDetach(); + } + + @Override + public void onResize() { + super.onResize(); + + /* + * Delay calculation to be deferred so Escalator can do it's magic. + */ + Scheduler.get().scheduleFinally(new ScheduledCommand() { + + @Override + public void execute() { + if (escalator.getInnerWidth() != autoColumnWidthsRecalculator.lastCalculatedInnerWidth) { + recalculateColumnWidths(); + } + + // Vertical resizing could make editor positioning invalid so it + // needs to be recalculated on resize + if (isEditorActive()) { + editor.updateVerticalScrollPosition(); + } + } + }); + } + + /** + * Grid does not support adding Widgets this way. + *

+ * This method is implemented only because removing widgets from Grid (added + * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface. + * + * @param w + * irrelevant + * @throws UnsupportedOperationException + * always + */ + @Override + @Deprecated + public void add(Widget w) { + throw new UnsupportedOperationException( + "Cannot add widgets to Grid with this method"); + } + + /** + * Grid does not support clearing Widgets this way. + *

+ * This method is implemented only because removing widgets from Grid (added + * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface. + * + * @throws UnsupportedOperationException + * always + */ + @Override + @Deprecated + public void clear() { + throw new UnsupportedOperationException( + "Cannot clear widgets from Grid this way"); + } + + /** + * Grid does not support iterating through Widgets this way. + *

+ * This method is implemented only because removing widgets from Grid (added + * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface. + * + * @return never + * @throws UnsupportedOperationException + * always + */ + @Override + @Deprecated + public Iterator iterator() { + throw new UnsupportedOperationException( + "Cannot iterate through widgets in Grid this way"); + } + + /** + * Grid does not support removing Widgets this way. + *

+ * This method is implemented only because removing widgets from Grid (added + * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface. + * + * @return always false + */ + @Override + @Deprecated + public boolean remove(Widget w) { + /* + * This is the method that is the sole reason to have Grid implement + * HasWidget - when Vaadin removes a Component from the hierarchy, the + * corresponding Widget will call removeFromParent() on itself. GWT will + * check there that its parent (i.e. Grid) implements HasWidgets, and + * will call this remove(Widget) method. + * + * tl;dr: all this song and dance to make sure GWT's sanity checks + * aren't triggered, even though they effectively do nothing interesting + * from Grid's perspective. + */ + return false; + } + + /** + * Accesses the package private method Widget#setParent() + * + * @param widget + * The widget to access + * @param parent + * The parent to set + */ + private static native final void setParent(Widget widget, Grid parent) + /*-{ + widget.@com.google.gwt.user.client.ui.Widget::setParent(Lcom/google/gwt/user/client/ui/Widget;)(parent); + }-*/; + + private static native final void onAttach(Widget widget) + /*-{ + widget.@Widget::onAttach()(); + }-*/; + + private static native final void onDetach(Widget widget) + /*-{ + widget.@Widget::onDetach()(); + }-*/; + + @Override + protected void doAttachChildren() { + if (sidebar.getParent() == this) { + onAttach(sidebar); + } + } + + @Override + protected void doDetachChildren() { + if (sidebar.getParent() == this) { + onDetach(sidebar); + } + } + + private void attachWidget(Widget w, Element parent) { + assert w.getParent() == null; + + parent.appendChild(w.getElement()); + setParent(w, this); + } + + private void detachWidget(Widget w) { + assert w.getParent() == this; + + setParent(w, null); + w.getElement().removeFromParent(); + } + + /** + * Resets all cached pixel sizes and reads new values from the DOM. This + * methods should be used e.g. when styles affecting the dimensions of + * elements in this grid have been changed. + */ + public void resetSizesFromDom() { + getEscalator().resetSizesFromDom(); + } + + /** + * Sets a new details generator for row details. + *

+ * The currently opened row details will be re-rendered. + * + * @since 7.5.0 + * @param detailsGenerator + * the details generator to set + * @throws IllegalArgumentException + * if detailsGenerator is null; + */ + public void setDetailsGenerator(DetailsGenerator detailsGenerator) + throws IllegalArgumentException { + + if (detailsGenerator == null) { + throw new IllegalArgumentException( + "Details generator may not be null"); + } + + for (Integer index : visibleDetails) { + setDetailsVisible(index, false); + } + + this.detailsGenerator = detailsGenerator; + + // this will refresh all visible spacers + escalator.getBody().setSpacerUpdater(gridSpacerUpdater); + } + + /** + * Gets the current details generator for row details. + * + * @since 7.5.0 + * @return the detailsGenerator the current details generator + */ + public DetailsGenerator getDetailsGenerator() { + return detailsGenerator; + } + + /** + * Shows or hides the details for a specific row. + *

+ * This method does nothing if trying to set show already-visible details, + * or hide already-hidden details. + * + * @since 7.5.0 + * @param rowIndex + * the index of the affected row + * @param visible + * true to show the details, or false + * to hide them + * @see #isDetailsVisible(int) + */ + public void setDetailsVisible(int rowIndex, boolean visible) { + if (DetailsGenerator.NULL.equals(detailsGenerator)) { + return; + } + + Integer rowIndexInteger = Integer.valueOf(rowIndex); + + /* + * We want to prevent opening a details row twice, so any subsequent + * openings (or closings) of details is a NOOP. + * + * When a details row is opened, it is given an arbitrary height + * (because Escalator requires a height upon opening). Only when it's + * opened, Escalator will ask the generator to generate a widget, which + * we then can measure. When measured, we correct the initial height by + * the original height. + * + * Without this check, we would override the measured height, and revert + * back to the initial, arbitrary, height which would most probably be + * wrong. + * + * see GridSpacerUpdater.init for implementation details. + */ + + boolean isVisible = isDetailsVisible(rowIndex); + if (visible && !isVisible) { + escalator.getBody().setSpacer(rowIndex, DETAILS_ROW_INITIAL_HEIGHT); + visibleDetails.add(rowIndexInteger); + } + + else if (!visible && isVisible) { + escalator.getBody().setSpacer(rowIndex, -1); + visibleDetails.remove(rowIndexInteger); + } + } + + /** + * Check whether the details for a row is visible or not. + * + * @since 7.5.0 + * @param rowIndex + * the index of the row for which to check details + * @return true iff the details for the given row is visible + * @see #setDetailsVisible(int, boolean) + */ + public boolean isDetailsVisible(int rowIndex) { + return visibleDetails.contains(Integer.valueOf(rowIndex)); + } + + /** + * Requests that the column widths should be recalculated. + *

+ * The actual recalculation is not necessarily done immediately so you + * cannot rely on the columns being the correct width after the call + * returns. + * + * @since 7.4.1 + */ + public void recalculateColumnWidths() { + autoColumnWidthsRecalculator.schedule(); + } + + /** + * Gets the customizable menu bar that is by default used for toggling + * column hidability. The application developer is allowed to add their + * custom items to the end of the menu, but should try to avoid modifying + * the items in the beginning of the menu that control the column hiding if + * any columns are marked as hidable. A toggle for opening the menu will be + * displayed whenever the menu contains at least one item. + * + * @since 7.5.0 + * @return the menu bar + */ + public MenuBar getSidebarMenu() { + return sidebar.menuBar; + } + + /** + * Tests whether the sidebar menu is currently open. + * + * @since 7.5.0 + * @see #getSidebarMenu() + * @return true if the sidebar is open; false if + * it is closed + */ + public boolean isSidebarOpen() { + return sidebar.isOpen(); + } + + /** + * Sets whether the sidebar menu is open. + * + * + * @since 7.5.0 + * @see #getSidebarMenu() + * @see #isSidebarOpen() + * @param sidebarOpen + * true to open the sidebar; false to + * close it + */ + public void setSidebarOpen(boolean sidebarOpen) { + if (sidebarOpen) { + sidebar.open(); + } else { + sidebar.close(); + } + } + + @Override + public int getTabIndex() { + return FocusUtil.getTabIndex(this); + } + + @Override + public void setAccessKey(char key) { + FocusUtil.setAccessKey(this, key); + } + + @Override + public void setFocus(boolean focused) { + FocusUtil.setFocus(this, focused); + } + + @Override + public void setTabIndex(int index) { + FocusUtil.setTabIndex(this, index); + } + + @Override + public void focus() { + setFocus(true); + } + + /** + * Sets the buffered editor mode. + * + * @since 7.6 + * @param editorUnbuffered + * true to enable buffered editor, + * false to disable it + */ + public void setEditorBuffered(boolean editorBuffered) { + editor.setBuffered(editorBuffered); + } + + /** + * Gets the buffered editor mode. + * + * @since 7.6 + * @return true if buffered editor is enabled, + * false otherwise + */ + public boolean isEditorBuffered() { + return editor.isBuffered(); + } + + /** + * Returns the {@link EventCellReference} for the latest event fired from + * this Grid. + *

+ * Note: This cell reference will be updated when firing the next event. + * + * @since 7.5 + * @return event cell reference + */ + public EventCellReference getEventCell() { + return eventCell; + } + + /** + * Returns a CellReference for the cell to which the given element belongs + * to. + * + * @since 7.6 + * @param element + * Element to find from the cell's content. + * @return CellReference or null if cell was not found. + */ + public CellReference getCellReference(Element element) { + RowContainer container = getEscalator().findRowContainer(element); + if (container != null) { + Cell cell = container.getCell(element); + if (cell != null) { + EventCellReference cellRef = new EventCellReference(this); + cellRef.set(cell, getSectionFromContainer(container)); + return cellRef; + } + } + return null; + } +} diff --git a/client/src/main/java/com/vaadin/client/widgets/Overlay.java b/client/src/main/java/com/vaadin/client/widgets/Overlay.java new file mode 100644 index 0000000000..a5e29c6774 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widgets/Overlay.java @@ -0,0 +1,1012 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.widgets; + +import com.google.gwt.animation.client.Animation; +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.IFrameElement; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.BorderStyle; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.AnimationUtil; +import com.vaadin.client.AnimationUtil.AnimationEndListener; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComputedStyle; +import com.vaadin.client.WidgetUtil; + +/** + * Overlay widget extending the PopupPanel. Overlay is used to float elements on + * top of other elements temporarily. + *

+ * Note: This class should always be constructed with + * {@link GWT#create(Class)}. + * + *

Shadow

+ *

+ * The separate shadow element underneath the main overlay element is + * deprecated, and should not be used for new overlay + * components. CSS box-shadow should be used instead of a separate shadow + * element. Remember to include any vendor-prefixed versions to support all + * browsers that you need to. To cover all possible browsers that Vaadin 7 + * supports, add -webkit-box-shadow and the standard + * box-shadow properties. + *

+ * + *

+ * For IE8, which doesn't support CSS box-shadow, you can use the proprietary + * DropShadow filter. It doesn't provide the exact same features as box-shadow, + * but it is suitable for graceful degradation. Other options are to use a + * border or a pseudo-element underneath the overlay which mimics a shadow, or + * any combination of these. + *

+ * + *

+ * Read more about the DropShadow filter from Microsoft Developer Network + *

+ * + * @since 7.6.1 + */ +public class Overlay extends PopupPanel implements CloseHandler { + + @Override + protected void onAttach() { + // Move the overlay to the appropriate overlay container + final Overlay overlay = Overlay.current; + if (overlay != null) { + final Element e = overlay.getOverlayContainer(); + e.appendChild(getElement()); + } + + super.onAttach(); + } + + public static class PositionAndSize { + private int left, top, width, height; + + public PositionAndSize(int left, int top, int width, int height) { + super(); + setLeft(left); + setTop(top); + setWidth(width); + setHeight(height); + } + + public int getLeft() { + return left; + } + + public void setLeft(int left) { + this.left = left; + } + + public int getTop() { + return top; + } + + public void setTop(int top) { + this.top = top; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + if (width < 0) { + width = 0; + } + + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + if (height < 0) { + height = 0; + } + + this.height = height; + } + + public void setAnimationFromCenterProgress(double progress) { + left += (int) (width * (1.0 - progress) / 2.0); + top += (int) (height * (1.0 - progress) / 2.0); + width = (int) (width * progress); + height = (int) (height * progress); + } + } + + /* + * The z-index value from where all overlays live. This can be overridden in + * any extending class. + */ + public static int Z_INDEX = 20000; + + private static int leftFix = -1; + + private static int topFix = -1; + + /** + * Shadow element style. If an extending class wishes to use a different + * style of shadow, it can use setShadowStyle(String) to give the shadow + * element a new style name. + * + * @deprecated See main JavaDoc for Overlay + */ + @Deprecated + public static final String CLASSNAME_SHADOW = "v-shadow"; + + /** + * Style name for the overlay container element (see + * {@link #getOverlayContainer()} + */ + public static final String CLASSNAME_CONTAINER = "v-overlay-container"; + + /** + * @since 7.3 + */ + public static final String ADDITIONAL_CLASSNAME_ANIMATE_IN = "animate-in"; + /** + * @since 7.3 + */ + public static final String ADDITIONAL_CLASSNAME_ANIMATE_OUT = "animate-out"; + + /** + * The shadow element for this overlay. + * + * @deprecated See main JavaDoc for Overlay + * + */ + @Deprecated + private Element shadow; + + /* + * The creator of this Overlay (the widget that made the instance, not the + * layout parent) + */ + private Widget owner; + + /** + * The shim iframe behind the overlay, allowing PDFs and applets to be + * covered by overlays. + */ + private IFrameElement shimElement; + + /** + * The HTML snippet that is used to render the actual shadow. In consists of + * nine different DIV-elements with the following class names: + * + *
+     *   .v-shadow[-stylename]
+     *   ----------------------------------------------
+     *   | .top-left     |   .top    |     .top-right |
+     *   |---------------|-----------|----------------|
+     *   |               |           |                |
+     *   | .left         |  .center  |         .right |
+     *   |               |           |                |
+     *   |---------------|-----------|----------------|
+     *   | .bottom-left  |  .bottom  |  .bottom-right |
+     *   ----------------------------------------------
+     * 
+ * + * See default theme 'shadow.css' for implementation example. + * + * @deprecated See main JavaDoc for Overlay + */ + @Deprecated + private static final String SHADOW_HTML = "
"; + + /** + * Matches {@link PopupPanel}.ANIMATION_DURATION + */ + private static final int POPUP_PANEL_ANIMATION_DURATION = 200; + + /** + * @deprecated See main JavaDoc for Overlay + */ + @Deprecated + private boolean sinkShadowEvents = false; + + public Overlay() { + super(); + adjustZIndex(); + } + + public Overlay(boolean autoHide) { + super(autoHide); + adjustZIndex(); + } + + public Overlay(boolean autoHide, boolean modal) { + super(autoHide, modal); + adjustZIndex(); + } + + /** + * @deprecated See main JavaDoc for Overlay. Use the other constructors + * without the showShadow parameter. + */ + @Deprecated + public Overlay(boolean autoHide, boolean modal, boolean showShadow) { + super(autoHide, modal); + setShadowEnabled(showShadow && useShadowDiv()); + adjustZIndex(); + } + + /** + * Return true if a separate shadow div should be used. Since Vaadin 7.3, + * shadows are implemented with CSS box-shadow. Thus, a shadow div is only + * used for IE8 by default. + * + * @deprecated See main JavaDoc for Overlay + * @since 7.3 + * @return true to use a shadow div + */ + @Deprecated + protected boolean useShadowDiv() { + return BrowserInfo.get().isIE8(); + } + + /** + * Method to control whether DOM elements for shadow are added. With this + * method subclasses can control displaying of shadow also after the + * constructor. + * + * @param enabled + * true if shadow should be displayed + * + * @deprecated See main JavaDoc for Overlay + */ + @Deprecated + protected void setShadowEnabled(boolean enabled) { + if (enabled != isShadowEnabled()) { + if (enabled) { + shadow = DOM.createDiv(); + shadow.setClassName(CLASSNAME_SHADOW); + shadow.setInnerHTML(SHADOW_HTML); + shadow.getStyle().setPosition(Position.ABSOLUTE); + addCloseHandler(this); + } else { + removeShadowIfPresent(); + shadow = null; + } + } + } + + /** + * @deprecated See main JavaDoc for Overlay + */ + @Deprecated + protected boolean isShadowEnabled() { + return shadow != null; + } + + protected boolean isShimElementEnabled() { + return shimElement != null; + } + + private void removeShimElement() { + if (shimElement != null) { + shimElement.removeFromParent(); + } + } + + /** + * @deprecated See main JavaDoc for Overlay + */ + @Deprecated + private void removeShadowIfPresent() { + if (isShadowAttached()) { + // Remove event listener from the shadow + unsinkShadowEvents(); + + shadow.removeFromParent(); + } + } + + /** + * @deprecated See main JavaDoc for Overlay + */ + @Deprecated + private boolean isShadowAttached() { + return isShadowEnabled() && shadow.getParentElement() != null; + } + + private void adjustZIndex() { + setZIndex(Z_INDEX); + } + + /** + * Set the z-index (visual stack position) for this overlay. + * + * @param zIndex + * The new z-index + */ + protected void setZIndex(int zIndex) { + getElement().getStyle().setZIndex(zIndex); + if (isShadowEnabled()) { + shadow.getStyle().setZIndex(zIndex); + } + } + + @Override + public void setPopupPosition(int left, int top) { + // TODO, this should in fact be part of + // Document.get().getBodyOffsetLeft/Top(). Would require overriding DOM + // for all permutations. Now adding fix as margin instead of fixing + // left/top because parent class saves the position. + Style style = getElement().getStyle(); + style.setMarginLeft(-adjustByRelativeLeftBodyMargin(), Unit.PX); + style.setMarginTop(-adjustByRelativeTopBodyMargin(), Unit.PX); + super.setPopupPosition(left, top); + positionOrSizeUpdated(isAnimationEnabled() ? 0 : 1); + } + + private IFrameElement getShimElement() { + if (shimElement == null && needsShimElement()) { + shimElement = Document.get().createIFrameElement(); + + // Insert shim iframe before the main overlay element. It does not + // matter if it is in front or behind the shadow as we cannot put a + // shim behind the shadow due to its transparency. + shimElement.getStyle().setPosition(Position.ABSOLUTE); + shimElement.getStyle().setBorderStyle(BorderStyle.NONE); + shimElement.setTabIndex(-1); + shimElement.setFrameBorder(0); + shimElement.setMarginHeight(0); + } + return shimElement; + } + + private int getActualTop() { + int y = getAbsoluteTop(); + + /* This is needed for IE7 at least */ + // Account for the difference between absolute position and the + // body's positioning context. + y -= Document.get().getBodyOffsetTop(); + y -= adjustByRelativeTopBodyMargin(); + + return y; + } + + private int getActualLeft() { + int x = getAbsoluteLeft(); + + /* This is needed for IE7 at least */ + // Account for the difference between absolute position and the + // body's positioning context. + x -= Document.get().getBodyOffsetLeft(); + x -= adjustByRelativeLeftBodyMargin(); + + return x; + } + + private static int adjustByRelativeTopBodyMargin() { + if (topFix == -1) { + topFix = detectRelativeBodyFixes("top"); + } + return topFix; + } + + private native static int detectRelativeBodyFixes(String axis) + /*-{ + try { + var b = $wnd.document.body; + var cstyle = b.currentStyle ? b.currentStyle : getComputedStyle(b); + if(cstyle && cstyle.position == 'relative') { + return b.getBoundingClientRect()[axis]; + } + } catch(e){} + return 0; + }-*/; + + private static int adjustByRelativeLeftBodyMargin() { + if (leftFix == -1) { + leftFix = detectRelativeBodyFixes("left"); + + } + return leftFix; + } + + /* + * A "thread local" of sorts, set temporarily so that OverlayImpl knows + * which Overlay is using it, so that it can be attached to the correct + * overlay container. + * + * TODO this is a strange pattern that we should get rid of when possible. + */ + protected static Overlay current; + + @Override + public void show() { + current = this; + + maybeShowWithAnimation(); + + if (isAnimationEnabled()) { + new ResizeAnimation().run(POPUP_PANEL_ANIMATION_DURATION); + } else { + positionOrSizeUpdated(1.0); + } + current = null; + } + + private JavaScriptObject animateInListener; + + private boolean maybeShowWithAnimation() { + boolean isAttached = isAttached() && isShowing(); + super.show(); + + // Don't animate if already visible or browser is IE8 or IE9 (no CSS + // animation support) + if (isAttached || BrowserInfo.get().isIE8() + || BrowserInfo.get().isIE9()) { + return false; + } else { + // Check if animations are used + setVisible(false); + addStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); + if (isShadowEnabled()) { + shadow.addClassName(CLASSNAME_SHADOW + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_IN); + } + + ComputedStyle cs = new ComputedStyle(getElement()); + String animationName = AnimationUtil.getAnimationName(cs); + if (animationName == null) { + animationName = ""; + } + setVisible(true); + + if (animationName.contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) { + // Disable GWT PopupPanel animation if used + setAnimationEnabled(false); + animateInListener = AnimationUtil.addAnimationEndListener( + getElement(), new AnimationEndListener() { + @Override + public void onAnimationEnd(NativeEvent event) { + String animationName = AnimationUtil + .getAnimationName(event); + if (animationName + .contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) { + AnimationUtil.removeAnimationEndListener( + getElement(), animateInListener); + removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); + if (isShadowEnabled()) { + shadow.removeClassName(CLASSNAME_SHADOW + + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_IN); + } + } + } + }); + return true; + } else { + removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); + if (isShadowEnabled()) { + shadow.removeClassName(CLASSNAME_SHADOW + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_IN); + } + return false; + } + } + } + + @Override + protected void onDetach() { + super.onDetach(); + + // Always ensure shadow is removed when the overlay is removed. + removeShadowIfPresent(); + removeShimElement(); + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (isShadowEnabled()) { + shadow.getStyle().setProperty("visibility", + visible ? "visible" : "hidden"); + } + if (isShimElementEnabled()) { + shimElement.getStyle().setProperty("visibility", + visible ? "visible" : "hidden"); + } + } + + @Override + public void setWidth(String width) { + super.setWidth(width); + positionOrSizeUpdated(1.0); + } + + @Override + public void setHeight(String height) { + super.setHeight(height); + positionOrSizeUpdated(1.0); + } + + /** + * Sets the shadow style for this overlay. Will override any previous style + * for the shadow. The default style name is defined by CLASSNAME_SHADOW. + * The given style will be prefixed with CLASSNAME_SHADOW. + * + * @param style + * The new style name for the shadow element. Will be prefixed by + * CLASSNAME_SHADOW, e.g. style=='foobar' -> actual style + * name=='v-shadow-foobar'. + * + * @deprecated See main JavaDoc for Overlay + */ + @Deprecated + protected void setShadowStyle(String style) { + if (isShadowEnabled()) { + shadow.setClassName(CLASSNAME_SHADOW + "-" + style); + } + } + + /** + * Extending classes should always call this method after they change the + * size of overlay without using normal 'setWidth(String)' and + * 'setHeight(String)' methods (if not calling super.setWidth/Height). + * + */ + public void positionOrSizeUpdated() { + positionOrSizeUpdated(1.0); + } + + /** + * @deprecated Call {@link #positionOrSizeUpdated()} instead. + */ + @Deprecated + protected void updateShadowSizeAndPosition() { + positionOrSizeUpdated(); + } + + /** + * Recalculates proper position and dimensions for the shadow and shim + * elements. Can be used to animate the related elements, using the + * 'progress' parameter (used to animate the shadow in sync with GWT + * PopupPanel's default animation 'PopupPanel.AnimationType.CENTER'). + * + * @param progress + * A value between 0.0 and 1.0, indicating the progress of the + * animation (0=start, 1=end). + */ + private void positionOrSizeUpdated(final double progress) { + // Don't do anything if overlay element is not attached + if (!isAttached()) { + return; + } + // Calculate proper z-index + int zIndex = -1; + try { + // Odd behaviour with Windows Hosted Mode forces us to use + // this redundant try/catch block (See dev.vaadin.com #2011) + zIndex = Integer.parseInt(getElement().getStyle().getZIndex()); + } catch (Exception ignore) { + // Ignored, will cause no harm + zIndex = 1000; + } + if (zIndex == -1) { + zIndex = Z_INDEX; + } + // Calculate position and size + if (BrowserInfo.get().isIE()) { + // Shake IE + getOffsetHeight(); + getOffsetWidth(); + } + + if (isShadowEnabled() || needsShimElement()) { + + PositionAndSize positionAndSize = new PositionAndSize( + getActualLeft(), getActualTop(), getOffsetWidth(), + getOffsetHeight()); + + // Animate the size + positionAndSize.setAnimationFromCenterProgress(progress); + + Element container = getElement().getParentElement(); + + if (isShadowEnabled()) { + updateShadowPosition(progress, zIndex, positionAndSize); + if (shadow.getParentElement() == null) { + container.insertBefore(shadow, getElement()); + sinkShadowEvents(); + } + } + + if (needsShimElement()) { + updateShimPosition(positionAndSize); + if (shimElement.getParentElement() == null) { + container.insertBefore(shimElement, getElement()); + } + } + } + // Fix for #14173 + // IE9 and IE10 have a bug, when resize an a element with box-shadow. + // IE9 and IE10 need explicit update to remove extra box-shadows + if (BrowserInfo.get().isIE9() || BrowserInfo.get().isIE10()) { + WidgetUtil.forceIERedraw(getElement()); + } + } + + /** + * @deprecated See main JavaDoc for Overlay + */ + @Deprecated + private void updateShadowPosition(final double progress, int zIndex, + PositionAndSize positionAndSize) { + // Opera needs some shaking to get parts of the shadow showing + // properly (ticket #2704) + if (BrowserInfo.get().isOpera()) { + // Clear the height of all middle elements + DOM.getChild(shadow, 3).getStyle().setProperty("height", "auto"); + DOM.getChild(shadow, 4).getStyle().setProperty("height", "auto"); + DOM.getChild(shadow, 5).getStyle().setProperty("height", "auto"); + } + + updatePositionAndSize(shadow, positionAndSize); + shadow.getStyle().setZIndex(zIndex); + shadow.getStyle().setProperty("display", progress < 0.9 ? "none" : ""); + + // Opera fix, part 2 (ticket #2704) + if (BrowserInfo.get().isOpera()) { + // We'll fix the height of all the middle elements + DOM.getChild(shadow, 3) + .getStyle() + .setPropertyPx("height", + DOM.getChild(shadow, 3).getOffsetHeight()); + DOM.getChild(shadow, 4) + .getStyle() + .setPropertyPx("height", + DOM.getChild(shadow, 4).getOffsetHeight()); + DOM.getChild(shadow, 5) + .getStyle() + .setPropertyPx("height", + DOM.getChild(shadow, 5).getOffsetHeight()); + } + } + + private void updateShimPosition(PositionAndSize positionAndSize) { + updatePositionAndSize(getShimElement(), positionAndSize); + } + + /** + * Returns true if we should add a shim iframe below the overlay to deal + * with zindex issues with PDFs and applets. Can be overriden to disable + * shim iframes if they are not needed. + * + * @return true if a shim iframe should be added, false otherwise + */ + protected boolean needsShimElement() { + BrowserInfo info = BrowserInfo.get(); + return info.isIE() && info.isBrowserVersionNewerOrEqual(8, 0); + } + + private void updatePositionAndSize(Element e, + PositionAndSize positionAndSize) { + e.getStyle().setLeft(positionAndSize.getLeft(), Unit.PX); + e.getStyle().setTop(positionAndSize.getTop(), Unit.PX); + e.getStyle().setWidth(positionAndSize.getWidth(), Unit.PX); + e.getStyle().setHeight(positionAndSize.getHeight(), Unit.PX); + } + + protected class ResizeAnimation extends Animation { + @Override + protected void onUpdate(double progress) { + positionOrSizeUpdated(progress); + } + } + + @Override + public void onClose(CloseEvent event) { + removeShadowIfPresent(); + } + + @Override + public void sinkEvents(int eventBitsToAdd) { + super.sinkEvents(eventBitsToAdd); + // Also sink events on the shadow if present + sinkShadowEvents(); + } + + /** + * @deprecated See main JavaDoc for Overlay + */ + @Deprecated + private void sinkShadowEvents() { + if (isSinkShadowEvents() && isShadowAttached()) { + // Sink the same events as the actual overlay has sunk + DOM.sinkEvents(shadow, DOM.getEventsSunk(getElement())); + // Send events to Overlay.onBrowserEvent + DOM.setEventListener(shadow, this); + } + } + + /** + * @deprecated See main JavaDoc for Overlay + */ + @Deprecated + private void unsinkShadowEvents() { + if (isShadowAttached()) { + DOM.setEventListener(shadow, null); + DOM.sinkEvents(shadow, 0); + } + } + + /** + * Enables or disables sinking the events of the shadow to the same + * onBrowserEvent as events to the actual overlay goes. + * + * Please note, that if you enable this, you can't assume that e.g. + * event.getEventTarget returns an element inside the DOM structure of the + * overlay + * + * @param sinkShadowEvents + * + * @deprecated See main JavaDoc for Overlay + */ + @Deprecated + protected void setSinkShadowEvents(boolean sinkShadowEvents) { + this.sinkShadowEvents = sinkShadowEvents; + if (sinkShadowEvents) { + sinkShadowEvents(); + } else { + unsinkShadowEvents(); + } + } + + /** + * @deprecated See main JavaDoc for Overlay + */ + @Deprecated + protected boolean isSinkShadowEvents() { + return sinkShadowEvents; + } + + /** + * Get owner (Widget that made this Overlay, not the layout parent) of + * Overlay + * + * @return Owner (creator) or null if not defined + */ + public Widget getOwner() { + return owner; + } + + /** + * Set owner (Widget that made this Overlay, not the layout parent) of + * Overlay + * + * @param owner + * Owner (creator) of Overlay + */ + public void setOwner(Widget owner) { + this.owner = owner; + } + + /** + * Gets the 'overlay container' element. + * + * @return the overlay container element + */ + public com.google.gwt.user.client.Element getOverlayContainer() { + return RootPanel.get().getElement(); + } + + @Override + public void center() { + super.center(); + + // Some devices can be zoomed in, we should center to the visual + // viewport for those devices + BrowserInfo b = BrowserInfo.get(); + if (b.isAndroid() || b.isIOS()) { + int left = (getVisualViewportWidth() - getOffsetWidth()) >> 1; + int top = (getVisualViewportHeight() - getOffsetHeight()) >> 1; + setPopupPosition(Math.max(Window.getScrollLeft() + left, 0), + Math.max(Window.getScrollTop() + top, 0)); + } + + } + + /** + * Gets the visual viewport width, which is useful for e.g iOS where the + * view can be zoomed in while keeping the layout viewport intact. + * + * Falls back to layout viewport; for those browsers/devices the difference + * is that the scrollbar with is included (if there is a scrollbar). + * + * @since 7.0.7 + * @return + */ + private int getVisualViewportWidth() { + int w = (int) getSubpixelInnerWidth(); + if (w < 0) { + return Window.getClientWidth(); + } else { + return w; + } + } + + /** + * Gets the visual viewport height, which is useful for e.g iOS where the + * view can be zoomed in while keeping the layout viewport intact. + * + * Falls back to layout viewport; for those browsers/devices the difference + * is that the scrollbar with is included (if there is a scrollbar). + * + * @since 7.0.7 + * @return + */ + private int getVisualViewportHeight() { + int h = (int) getSubpixelInnerHeight(); + if (h < 0) { + return Window.getClientHeight(); + } else { + return h; + } + } + + private native double getSubpixelInnerWidth() + /*-{ + return $wnd.innerWidth !== undefined ? $wnd.innerWidth : -1; + }-*/; + + private native double getSubpixelInnerHeight() + /*-{ + return $wnd.innerHeight !== undefined ? $wnd.innerHeight :-1; + }-*/; + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.PopupPanel#hide() + */ + @Override + public void hide() { + hide(false); + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.PopupPanel#hide(boolean) + */ + @Override + public void hide(final boolean autoClosed) { + hide(autoClosed, true, true); + } + + /** + * + * Hides the popup and detaches it from the page. This has no effect if it + * is not currently showing. Animation-in, animation-out can be + * enable/disabled for different use cases. + * + * @see com.google.gwt.user.client.ui.PopupPanel#hide(boolean) + * + * @param autoClosed + * the value that will be passed to + * {@link CloseHandler#onClose(CloseEvent)} when the popup is + * closed + * @param animateIn + * enable/disable animate-in animation + * @param animateOut + * enable/disable animate-out animation + * @since 7.3.7 + */ + public void hide(final boolean autoClosed, final boolean animateIn, + final boolean animateOut) { + if (BrowserInfo.get().isIE8() || BrowserInfo.get().isIE9()) { + super.hide(autoClosed); + } else { + if (animateIn + && getStyleName().contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) { + AnimationUtil.addAnimationEndListener(getElement(), + new AnimationEndListener() { + @Override + public void onAnimationEnd(NativeEvent event) { + if (AnimationUtil + .getAnimationName(event) + .contains( + ADDITIONAL_CLASSNAME_ANIMATE_IN)) { + Overlay.this.hide(autoClosed); + } + } + }); + } else { + // Check if animations are used + addStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_OUT); + if (isShadowEnabled()) { + shadow.addClassName(CLASSNAME_SHADOW + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_OUT); + } + ComputedStyle cs = new ComputedStyle(getElement()); + String animationName = AnimationUtil.getAnimationName(cs); + if (animationName == null) { + animationName = ""; + } + + if (animateOut + && animationName + .contains(ADDITIONAL_CLASSNAME_ANIMATE_OUT)) { + // Disable GWT PopupPanel closing animation if used + setAnimationEnabled(false); + + AnimationUtil.addAnimationEndListener(getElement(), + new AnimationEndListener() { + @Override + public void onAnimationEnd(NativeEvent event) { + String animationName = AnimationUtil + .getAnimationName(event); + if (animationName + .contains(ADDITIONAL_CLASSNAME_ANIMATE_OUT)) { + AnimationUtil + .removeAllAnimationEndListeners(getElement()); + // Remove both animation styles just in + // case + removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); + removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_OUT); + if (isShadowEnabled()) { + shadow.removeClassName(CLASSNAME_SHADOW + + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_IN); + shadow.removeClassName(CLASSNAME_SHADOW + + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_OUT); + } + Overlay.super.hide(autoClosed); + } + } + }); + // No event previews should happen after the animation has + // started + Overlay.this.setPreviewingAllNativeEvents(false); + } else { + removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_OUT); + if (isShadowEnabled()) { + shadow.removeClassName(CLASSNAME_SHADOW + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_OUT); + } + super.hide(autoClosed); + } + } + } + } + +} diff --git a/client/src/main/resources/com/vaadin/DefaultWidgetSet.gwt.xml b/client/src/main/resources/com/vaadin/DefaultWidgetSet.gwt.xml new file mode 100755 index 0000000000..ad345e44e2 --- /dev/null +++ b/client/src/main/resources/com/vaadin/DefaultWidgetSet.gwt.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/main/resources/com/vaadin/Vaadin.gwt.xml b/client/src/main/resources/com/vaadin/Vaadin.gwt.xml new file mode 100644 index 0000000000..35b225560e --- /dev/null +++ b/client/src/main/resources/com/vaadin/Vaadin.gwt.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/main/resources/com/vaadin/VaadinBrowserSpecificOverrides.gwt.xml b/client/src/main/resources/com/vaadin/VaadinBrowserSpecificOverrides.gwt.xml new file mode 100644 index 0000000000..ceedde50a6 --- /dev/null +++ b/client/src/main/resources/com/vaadin/VaadinBrowserSpecificOverrides.gwt.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/main/resources/com/vaadin/client/debug/internal/theme/debugwindow.css b/client/src/main/resources/com/vaadin/client/debug/internal/theme/debugwindow.css new file mode 100644 index 0000000000..78e537140e --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/debug/internal/theme/debugwindow.css @@ -0,0 +1,322 @@ +.v-debug-console { + background: #fff; + opacity: .9; + border: 1px solid #000; + font-family: sans-serif; +} + +.v-debug-console-caption { + background: #000; + border-bottom: 1px solid grey; + color: white; + font-weight: bold; +} + +.v-debug-console-content { + font-size: x-small; + overflow: auto; + white-space: pre; +} + +.v-debug-console-content input { + font-size: xx-small; +} + +/* Debug style */ +.v-app .invalidlayout, +.v-app .invalidlayout * { + background: #f99 !important; +} + +/* NEW debug window */ + +@def mainbg #fff; +@def darkborder #666; +@def lightborder #999; +@def maincolor #666; +@def maincolor-lighten-5pc #737373; +@def maincolor-lighten-10pc gray; +@def maincolor-lighten-15pc #8c8c8c; +@def activecolor #000; + +@url urlForTtf iconFontTtf; +@url urlForWoff iconFontWoff; +@url urlForEot iconFontEot; +@url urlForSvg iconFontSvg; + +@font-face { + font-family: 'vdebugfont'; + src: urlForEot; +} + +@font-face { + font-family: 'vdebugfont'; + src: urlForWoff format('woff'), + urlForTtf format('truetype'), + urlForSvg format('svg'); + font-weight: normal; + font-style: normal; +} + +.v-debugwindow [data-icon]:before, +.v-debugwindow-menu [data-icon]:before { + font-family: 'vdebugfont'; + content: attr(data-icon); + speak: none; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + font-style: normal; + vertical-align: text-bottom; +} + +.v-debugwindow { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + opacity: .8; + color: maincolor; + font-family: Arial, Helvetica, Tahoma, Verdana, sans-serif; + font-size: 13px; +} + +.v-debugwindow-handle { + position: absolute; + bottom: 0; + background-color: #fff; + opacity: 0; + z-index: 1000; +} + +.v-debugwindow-handle-sw { + width: 7px; + height: 7px; +} + +.v-debugwindow-handle-se { + right: 0; + width: 14px; + height: 14px; +} + +.v-debugwindow:hover { + opacity: 1; +} + +.v-debugwindow * { + font-size: inherit !important; +} + +.v-debugwindow-size0, .v-debugwindow-menu .v-debugwindow-button-size0 { + font-size: 10px; +} + +.v-debugwindow-size1, .v-debugwindow-menu .v-debugwindow-button-size1 { + font-size: 13px; +} + +.v-debugwindow-size2, .v-debugwindow-menu .v-debugwindow-button-size2 { + font-size: 16px; +} + +.v-debugwindow-head { + text-align: right; + background-color: transparent; +} + +.v-debugwindow-tabs { + display: inline-block; +} + +.v-debugwindow-tab, .v-debugwindow-controls > * { + width: 2em; + border: none; + margin: 0; + line-height: 1.5em; + background-color: mainbg; + color: maincolor; +} + +.v-debugwindow-tab { + position: relative; + top: 1px; + border-width: 1px 0 1px 1px; + border-style: solid; + border-color: darkborder; + border-radius: 2px 2px 0 0; +} + +.v-debugwindow-tab-selected { + color: maincolor; + background-color: mainbg; + border-bottom: 1px solid #fff; +} + +.v-debugwindow-controls { + position: relative; + top: 1px; + display: inline-block; + background-color: mainbg; + border: 1px solid darkborder; + border-radius: 2px 2px 0 0; +} + +.v-debugwindow-section-head { + text-align: left; + background-color: mainbg; + border: 1px solid darkborder; + border-bottom: 1px solid lightborder; + box-shadow: 0px 0px 7px 0 rgba(55,55,55,0.6); + min-height: 1.5em; + line-height: 1.5em; + padding-left: 5px; +} + +.v-debugwindow-button { + border: none; + background-color: transparent; + color: maincolor; +} + +.v-debugwindow-button:hover { + color: activecolor; + text-decoration: underline; +} + +.v-debugwindow-button-active { + color: maincolor; + box-shadow: 1px 1px 3px 0 inset; +} + +.v-debugwindow-content { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + box-shadow: 0px 0px 7px 0 rgba(55,55,55,0.6); + background-color: mainbg; + border: 1px solid darkborder; + border-top: none; +} + +.v-debugwindow-menu { + background-color: mainbg; + padding: 4px; + border: 1px solid lightborder; + border-top: none; + border-radius: 0 0 5px 5px; + box-shadow: 0px 0px 7px 0 rgba(55,55,55,0.6); +} + +.v-debugwindow-menu-content { + min-width: 100px; +} + +.v-debugwindow-menu-content .v-debugwindow-button { + line-height: 22px; +} + +.v-debugwindow-menu-content > div > .v-debugwindow-button { + width: 33%; +} + +/* GLOBAL color every other row */ +.v-debugwindow-row { + display: table-row; +} + +/* Escape function signature so that this gets past GWT compiler */ +.v-debugwindow-row:nth-child\(odd\) { + background-color: rgba(0, 61, 255, 0.11); +} + +.v-debugwindow-row > span { + display: table-cell; + padding: 4px; +} + +.v-debugwindow-row.SEVERE { + color: #550000; + background-color: #FFC5C5; +} + +.v-debugwindow-row.WARNING { + background-color: #FFFF99; +} + +.v-debugwindow-row.FINE { + color: maincolor-lighten-5pc; +} + +.v-debugwindow-row.FINER { + color: maincolor-lighten-10pc; +} + +.v-debugwindow-row.FINEST { + color: maincolor-lighten-15pc; +} + +.v-debugwindow-row > span.caption { + color: #999; + text-align: right; + white-space: nowrap; +} + +.v-debugwindow-row > span.value { + width: 100%; +} + +.v-debugwindow-selector > span.value { + width: 100%; +} + +.v-debugwindow-selector :hover { + background: rgba(255,32,32,0.5); +} + +/* LOG */ +.v-debugwindow-log { + font-family: monospace; +} + +.v-debugwindow-log .v-debugwindow-reset { + color: #fff; + background-color: #4C92ED; + padding: 4px; +} + +.v-debugwindow-log .v-debugwindow-time { + text-align: right; + color: #999; +} + +.v-debugwindow-log .v-debugwindow-message { + white-space: nowrap; + width: 100% +} + +.v-debugwindow-log .v-debugwindow-message:hover { + white-space: normal; + word-wrap: break-word; +} + +.v-debugwindow-log .v-debugwindow-message em { + background-color: #C4E6F8; +} + + +/* HIERARCHY */ +.v-debugwindow-hierarchy .v-debugwindow-info { + padding: 1em; +} + + +/* NETWORK */ +.v-debugwindow-network .v-debugwindow-row { + display: block !important; +} + +.v-debugwindow-network .v-debugwindow-row > span { + display: inline; +} diff --git a/client/src/main/resources/com/vaadin/client/debug/internal/theme/font.eot b/client/src/main/resources/com/vaadin/client/debug/internal/theme/font.eot new file mode 100755 index 0000000000..0244d9afcc Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/debug/internal/theme/font.eot differ diff --git a/client/src/main/resources/com/vaadin/client/debug/internal/theme/font.svg b/client/src/main/resources/com/vaadin/client/debug/internal/theme/font.svg new file mode 100755 index 0000000000..313645c78f --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/debug/internal/theme/font.svg @@ -0,0 +1,37 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/main/resources/com/vaadin/client/debug/internal/theme/font.ttf b/client/src/main/resources/com/vaadin/client/debug/internal/theme/font.ttf new file mode 100755 index 0000000000..56ed0cfbff Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/debug/internal/theme/font.ttf differ diff --git a/client/src/main/resources/com/vaadin/client/debug/internal/theme/font.woff b/client/src/main/resources/com/vaadin/client/debug/internal/theme/font.woff new file mode 100755 index 0000000000..9f5ed668ec Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/debug/internal/theme/font.woff differ diff --git a/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_alignment.png b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_alignment.png new file mode 100644 index 0000000000..49b918ec0c Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_alignment.png differ diff --git a/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_component_handles_the_caption.png b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_component_handles_the_caption.png new file mode 100644 index 0000000000..9fd6635765 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_component_handles_the_caption.png differ diff --git a/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_h150.png b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_h150.png new file mode 100644 index 0000000000..7cd07369dc Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_h150.png differ diff --git a/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_horizontal.png b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_horizontal.png new file mode 100644 index 0000000000..c2e1f49efe Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_horizontal.png differ diff --git a/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_horizontal_spacing.png b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_horizontal_spacing.png new file mode 100644 index 0000000000..417c9aecfd Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_horizontal_spacing.png differ diff --git a/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_margin.png b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_margin.png new file mode 100644 index 0000000000..2f1e461b0a Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_margin.png differ diff --git a/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_no_caption.png b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_no_caption.png new file mode 100644 index 0000000000..63984cdee7 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_no_caption.png differ diff --git a/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_normal_caption.png b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_normal_caption.png new file mode 100644 index 0000000000..1e730c072b Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_normal_caption.png differ diff --git a/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_special-margin.png b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_special-margin.png new file mode 100644 index 0000000000..34e47d1551 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_special-margin.png differ diff --git a/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_vertical.png b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_vertical.png new file mode 100644 index 0000000000..99e3709acc Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_vertical.png differ diff --git a/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_vertical_spacing.png b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_vertical_spacing.png new file mode 100644 index 0000000000..be9a4cd8c5 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_vertical_spacing.png differ diff --git a/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_w300.png b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_w300.png new file mode 100644 index 0000000000..0b555ad1e7 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_w300.png differ diff --git a/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_w300_h150.png b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_w300_h150.png new file mode 100644 index 0000000000..8ff42ed0f4 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/doc-files/IOrderedLayout_w300_h150.png differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties b/client/src/main/resources/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties new file mode 100644 index 0000000000..363b704584 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties @@ -0,0 +1,35 @@ +bold = Toggle Bold +createLink = Create Link +hr = Insert Horizontal Rule +indent = Indent Right +insertImage = Insert Image +italic = Toggle Italic +justifyCenter = Center +justifyLeft = Left Justify +justifyRight = Right Justify +ol = Insert Ordered List +outdent = Indent Left +removeFormat = Remove Formatting +removeLink = Remove Link +strikeThrough = Toggle Strikethrough +subscript = Toggle Subscript +superscript = Toggle Superscript +ul = Insert Unordered List +underline = Toggle Underline +color = Color +black = Black +white = White +red = Red +green = Green +yellow = Yellow +blue = Blue +font = Font +normal = Normal +size = Size +xxsmall = XX-Small +xsmall = X-Small +small = Small +medium = Medium +large = Large +xlarge = X-Large +xxlarge = XX-Large \ No newline at end of file diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/backColors.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/backColors.gif new file mode 100644 index 0000000000..ddfc1cea2c Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/backColors.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/bold.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/bold.gif new file mode 100644 index 0000000000..7c22eaac68 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/bold.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/createLink.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/createLink.gif new file mode 100644 index 0000000000..1a1412fe0e Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/createLink.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/fontSizes.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/fontSizes.gif new file mode 100644 index 0000000000..c2f4c8cb21 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/fontSizes.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/fonts.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/fonts.gif new file mode 100644 index 0000000000..1629cabb78 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/fonts.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/foreColors.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/foreColors.gif new file mode 100644 index 0000000000..2bb89ef189 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/foreColors.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/gwtLogo.png b/client/src/main/resources/com/vaadin/client/ui/richtextarea/gwtLogo.png new file mode 100644 index 0000000000..80728186d8 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/gwtLogo.png differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/hr.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/hr.gif new file mode 100644 index 0000000000..d507082cf1 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/hr.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/indent.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/indent.gif new file mode 100644 index 0000000000..905421ed76 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/indent.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/insertImage.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/insertImage.gif new file mode 100644 index 0000000000..394ec432a5 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/insertImage.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/italic.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/italic.gif new file mode 100644 index 0000000000..ffe0e97284 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/italic.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyCenter.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyCenter.gif new file mode 100644 index 0000000000..f7d4c4693d Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyCenter.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyLeft.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyLeft.gif new file mode 100644 index 0000000000..bc37a3ed5a Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyLeft.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyRight.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyRight.gif new file mode 100644 index 0000000000..892d569384 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyRight.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/ol.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/ol.gif new file mode 100644 index 0000000000..54f8e4f551 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/ol.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/outdent.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/outdent.gif new file mode 100644 index 0000000000..78fd1b5722 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/outdent.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeFormat.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeFormat.gif new file mode 100644 index 0000000000..cf92c9774f Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeFormat.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeLink.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeLink.gif new file mode 100644 index 0000000000..40721a7bca Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeLink.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/strikeThrough.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/strikeThrough.gif new file mode 100644 index 0000000000..a7a233c023 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/strikeThrough.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/subscript.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/subscript.gif new file mode 100644 index 0000000000..58b6fbb816 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/subscript.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/superscript.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/superscript.gif new file mode 100644 index 0000000000..a6270f6e21 Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/superscript.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/ul.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/ul.gif new file mode 100644 index 0000000000..83f1562bcb Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/ul.gif differ diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/underline.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/underline.gif new file mode 100644 index 0000000000..06f0200fdd Binary files /dev/null and b/client/src/main/resources/com/vaadin/client/ui/richtextarea/underline.gif differ diff --git a/client/src/main/resources/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml b/client/src/main/resources/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml new file mode 100644 index 0000000000..f3cc2bca1c --- /dev/null +++ b/client/src/main/resources/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/client/src/test/java/com/vaadin/client/ApplicationConnectionURLGenerationTest.java b/client/src/test/java/com/vaadin/client/ApplicationConnectionURLGenerationTest.java new file mode 100644 index 0000000000..4dd2ddbaa4 --- /dev/null +++ b/client/src/test/java/com/vaadin/client/ApplicationConnectionURLGenerationTest.java @@ -0,0 +1,74 @@ +package com.vaadin.client; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.vaadin.shared.util.SharedUtil; + +public class ApplicationConnectionURLGenerationTest { + + private static final String[] URIS = new String[] { + "http://demo.vaadin.com/", // + "https://demo.vaadin.com/", + "http://demo.vaadin.com/foo", + "http://demo.vaadin.com/foo?f", + "http://demo.vaadin.com/foo?f=1", + "http://demo.vaadin.com:1234/foo?a", + "http://demo.vaadin.com:1234/foo#frag?fakeparam", + // Jetspeed + "http://localhost:8080/jetspeed/portal/_ns:Z3RlbXBsYXRlLXRvcDJfX3BhZ2UtdGVtcGxhdGVfX2RwLTFfX1AtMTJjNTRkYjdlYjUtMTAwMDJ8YzB8ZDF8aVVJREx8Zg__", + // Liferay generated url + "http://vaadin.com/directory?p_p_id=Directory_WAR_Directory&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=UIDL&p_p_cacheability=cacheLevelPage&p_p_col_id=row-1&p_p_col_count=1", + + }; + private static final String[] URIS_WITH_ABCD_PARAM = new String[] { + "http://demo.vaadin.com/?a=b&c=d", + "https://demo.vaadin.com/?a=b&c=d", + "http://demo.vaadin.com/foo?a=b&c=d", + "http://demo.vaadin.com/foo?f&a=b&c=d", + "http://demo.vaadin.com/foo?f=1&a=b&c=d", + "http://demo.vaadin.com:1234/foo?a&a=b&c=d", + "http://demo.vaadin.com:1234/foo?a=b&c=d#frag?fakeparam", + "http://localhost:8080/jetspeed/portal/_ns:Z3RlbXBsYXRlLXRvcDJfX3BhZ2UtdGVtcGxhdGVfX2RwLTFfX1AtMTJjNTRkYjdlYjUtMTAwMDJ8YzB8ZDF8aVVJREx8Zg__?a=b&c=d", + "http://vaadin.com/directory?p_p_id=Directory_WAR_Directory&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=UIDL&p_p_cacheability=cacheLevelPage&p_p_col_id=row-1&p_p_col_count=1&a=b&c=d", + + }; + + private static final String[] URIS_WITH_ABCD_PARAM_AND_FRAGMENT = new String[] { + "http://demo.vaadin.com/?a=b&c=d#fragment", + "https://demo.vaadin.com/?a=b&c=d#fragment", + "http://demo.vaadin.com/foo?a=b&c=d#fragment", + "http://demo.vaadin.com/foo?f&a=b&c=d#fragment", + "http://demo.vaadin.com/foo?f=1&a=b&c=d#fragment", + "http://demo.vaadin.com:1234/foo?a&a=b&c=d#fragment", + "", + "http://localhost:8080/jetspeed/portal/_ns:Z3RlbXBsYXRlLXRvcDJfX3BhZ2UtdGVtcGxhdGVfX2RwLTFfX1AtMTJjNTRkYjdlYjUtMTAwMDJ8YzB8ZDF8aVVJREx8Zg__?a=b&c=d#fragment", + "http://vaadin.com/directory?p_p_id=Directory_WAR_Directory&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=UIDL&p_p_cacheability=cacheLevelPage&p_p_col_id=row-1&p_p_col_count=1&a=b&c=d#fragment", + + }; + + @Test + public void testParameterAdding() { + for (int i = 0; i < URIS.length; i++) { + // Adding nothing + assertEquals(URIS[i], SharedUtil.addGetParameters(URIS[i], "")); + + // Adding a=b&c=d + assertEquals(URIS_WITH_ABCD_PARAM[i], + SharedUtil.addGetParameters(URIS[i], "a=b&c=d")); + + // Fragments + if (URIS_WITH_ABCD_PARAM_AND_FRAGMENT[i].length() > 0) { + assertEquals(URIS_WITH_ABCD_PARAM_AND_FRAGMENT[i], + SharedUtil.addGetParameters(URIS[i] + "#fragment", + "a=b&c=d")); + + // Empty fragment + assertEquals(URIS_WITH_ABCD_PARAM_AND_FRAGMENT[i].replace( + "#fragment", "#"), SharedUtil.addGetParameters(URIS[i] + + "#", "a=b&c=d")); + } + } + } +} diff --git a/client/src/test/java/com/vaadin/client/DateTimeServiceTest.java b/client/src/test/java/com/vaadin/client/DateTimeServiceTest.java new file mode 100755 index 0000000000..e5e6cbd7db --- /dev/null +++ b/client/src/test/java/com/vaadin/client/DateTimeServiceTest.java @@ -0,0 +1,122 @@ +package com.vaadin.client; + +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + +public class DateTimeServiceTest { + + final long MILLISECONDS_PER_DAY = 24 * 3600 * 1000; + + static Map isoWeekNumbers = new HashMap(); + static { + isoWeekNumbers.put(getDate(2005, 02, 02), 5); + + isoWeekNumbers.put(getDate(2005, 1, 1), 53); + isoWeekNumbers.put(getDate(2005, 1, 2), 53); + isoWeekNumbers.put(getDate(2005, 1, 3), 1); + isoWeekNumbers.put(getDate(2005, 1, 4), 1); + isoWeekNumbers.put(getDate(2005, 1, 5), 1); + isoWeekNumbers.put(getDate(2005, 1, 6), 1); + isoWeekNumbers.put(getDate(2005, 1, 7), 1); + isoWeekNumbers.put(getDate(2005, 1, 8), 1); + isoWeekNumbers.put(getDate(2005, 1, 9), 1); + isoWeekNumbers.put(getDate(2005, 1, 10), 2); + isoWeekNumbers.put(getDate(2005, 12, 31), 52); + isoWeekNumbers.put(getDate(2005, 12, 30), 52); + isoWeekNumbers.put(getDate(2005, 12, 29), 52); + isoWeekNumbers.put(getDate(2005, 12, 28), 52); + isoWeekNumbers.put(getDate(2005, 12, 27), 52); + isoWeekNumbers.put(getDate(2005, 12, 26), 52); + isoWeekNumbers.put(getDate(2005, 12, 25), 51); + isoWeekNumbers.put(getDate(2007, 1, 1), 1); + isoWeekNumbers.put(getDate(2007, 12, 30), 52); + isoWeekNumbers.put(getDate(2007, 12, 31), 1); + isoWeekNumbers.put(getDate(2008, 1, 1), 1); + isoWeekNumbers.put(getDate(2008, 12, 28), 52); + isoWeekNumbers.put(getDate(2008, 12, 29), 1); + isoWeekNumbers.put(getDate(2008, 12, 30), 1); + isoWeekNumbers.put(getDate(2008, 12, 31), 1); + isoWeekNumbers.put(getDate(2009, 1, 1), 1); + isoWeekNumbers.put(getDate(2009, 12, 31), 53); + isoWeekNumbers.put(getDate(2010, 1, 1), 53); + isoWeekNumbers.put(getDate(2010, 1, 2), 53); + isoWeekNumbers.put(getDate(2010, 1, 3), 53); + isoWeekNumbers.put(getDate(2010, 1, 4), 1); + isoWeekNumbers.put(getDate(2010, 1, 5), 1); + isoWeekNumbers.put(getDate(2010, 10, 10), 40); + isoWeekNumbers.put(getDate(2015, 3, 24), 13); + isoWeekNumbers.put(getDate(2015, 3, 31), 14); + isoWeekNumbers.put(getDate(2015, 10, 13), 42); + isoWeekNumbers.put(getDate(2015, 10, 20), 43); + isoWeekNumbers.put(getDate(2015, 10, 27), 44); + isoWeekNumbers.put(getDate(2026, 3, 24), 13); + isoWeekNumbers.put(getDate(2026, 3, 31), 14); + isoWeekNumbers.put(getDate(2026, 10, 13), 42); + isoWeekNumbers.put(getDate(2026, 10, 20), 43); + isoWeekNumbers.put(getDate(2026, 10, 27), 44); + + } + + /** + * Test all dates from 1990-1992 + some more and see that {@link Calendar} + * calculates the ISO week number like we do. + * + */ + @Test + public void testISOWeekNumbers() { + Calendar c = Calendar.getInstance(); + c.set(1990, 1, 1); + long start = c.getTimeInMillis(); + + for (int i = 0; i < 1000; i++) { + Date d = new Date(start + i * MILLISECONDS_PER_DAY); + int expected = getCalendarISOWeekNr(d); + int calculated = DateTimeService.getISOWeekNumber(d); + Assert.assertEquals(d + " should be week " + expected, expected, + calculated); + + } + } + + /** + * Verify that special cases are handled correctly by us (and + * {@link Calendar}). + * + */ + @Test + public void testSampleISOWeekNumbers() { + for (Date d : isoWeekNumbers.keySet()) { + // System.out.println("Sample: " + d); + int expected = isoWeekNumbers.get(d); + int calculated = DateTimeService.getISOWeekNumber(d); + Assert.assertEquals(d + " should be week " + expected + + " (Java Calendar is wrong?)", expected, + getCalendarISOWeekNr(d)); + Assert.assertEquals(d + " should be week " + expected, expected, + calculated); + + } + } + + private int getCalendarISOWeekNr(Date d) { + Calendar c = Calendar.getInstance(); + c.setFirstDayOfWeek(Calendar.MONDAY); + c.setMinimalDaysInFirstWeek(4); + c.setTime(d); + + return c.get(Calendar.WEEK_OF_YEAR); + } + + private static Date getDate(int year, int month, int date) { + Calendar c = Calendar.getInstance(); + c.clear(); + c.set(year, month - 1, date); + return c.getTime(); + } + +} diff --git a/client/src/test/java/com/vaadin/client/LocatorUtilTest.java b/client/src/test/java/com/vaadin/client/LocatorUtilTest.java new file mode 100644 index 0000000000..971688e1c5 --- /dev/null +++ b/client/src/test/java/com/vaadin/client/LocatorUtilTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.client.componentlocator.LocatorUtil; + +/* + * Test LocatorUtil.isUIElement() & isNotificaitonElement methods + */ +public class LocatorUtilTest { + + @Test + public void testIsUI1() { + boolean isUI = LocatorUtil.isUIElement("com.vaadin.ui.UI"); + Assert.assertTrue(isUI); + } + + @Test + public void testIsUI2() { + boolean isUI = LocatorUtil.isUIElement("/com.vaadin.ui.UI"); + Assert.assertTrue(isUI); + } + + @Test + public void testIsUI3() { + boolean isUI = LocatorUtil + .isUIElement("//com.vaadin.ui.UI[RandomString"); + Assert.assertTrue(isUI); + } + + @Test + public void testIsUI4() { + boolean isUI = LocatorUtil.isUIElement("//com.vaadin.ui.UI[0]"); + Assert.assertTrue(isUI); + } + + @Test + public void testIsNotification1() { + boolean isUI = LocatorUtil + .isNotificationElement("com.vaadin.ui.VNotification"); + Assert.assertTrue(isUI); + } + + @Test + public void testIsNotification2() { + boolean isUI = LocatorUtil + .isNotificationElement("com.vaadin.ui.Notification"); + Assert.assertTrue(isUI); + } + + @Test + public void testIsNotification3() { + boolean isUI = LocatorUtil + .isNotificationElement("/com.vaadin.ui.VNotification["); + Assert.assertTrue(isUI); + } + + @Test + public void testIsNotification4() { + boolean isUI = LocatorUtil + .isNotificationElement("//com.vaadin.ui.VNotification[0]"); + Assert.assertTrue(isUI); + } +} diff --git a/client/src/test/java/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java b/client/src/test/java/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java new file mode 100644 index 0000000000..5522b8e2bc --- /dev/null +++ b/client/src/test/java/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java @@ -0,0 +1,651 @@ +package com.vaadin.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.shared.VBrowserDetails; + +public class VBrowserDetailsUserAgentParserTest { + + private static final String FIREFOX30_WINDOWS = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.0.6) Gecko/2009011913 Firefox/3.0.6"; + private static final String FIREFOX30_LINUX = "Mozilla/5.0 (X11; U; Linux x86_64; es-ES; rv:1.9.0.12) Gecko/2009070811 Ubuntu/9.04 (jaunty) Firefox/3.0.12"; + private static final String FIREFOX33_ANDROID = "Mozilla/5.0 (Android; Tablet; rv:33.0) Gecko/33.0 Firefox/33.0"; + private static final String FIREFOX35_WINDOWS = "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8 (.NET CLR 3.5.30729) FirePHP/0.4"; + private static final String FIREFOX36_WINDOWS = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2) Gecko/20100115 Firefox/3.6 (.NET CLR 3.5.30729)"; + private static final String FIREFOX36B_MAC = "UAString mozilla/5.0 (macintosh; u; intel mac os x 10.6; en-us; rv:1.9.2) gecko/20100115 firefox/3.6"; + private static final String FIREFOX_30B5_MAC = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9b5) Gecko/2008032619 Firefox/3.0b5"; + private static final String FIREFOX_40B7_WIN = "Mozilla/5.0 (Windows NT 5.1; rv:2.0b7) Gecko/20100101 Firefox/4.0b7"; + private static final String FIREFOX_40B11_WIN = "Mozilla/5.0 (Windows NT 5.1; rv:2.0b11) Gecko/20100101 Firefox/4.0b11"; + private static final String KONQUEROR_LINUX = "Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Exabot-Thumbnails)"; + + private static final String IE6_WINDOWS = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)"; + private static final String IE7_WINDOWS = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"; + + private static final String IE8_WINDOWS = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; InfoPath.2)"; + private static final String IE8_IN_IE7_MODE_WINDOWS = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; InfoPath.2)"; + + private static final String IE9_IN_IE7_MODE_WINDOWS_7 = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)"; + private static final String IE9_BETA_IN_IE8_MODE_WINDOWS_7 = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)"; + private static final String IE9_BETA_WINDOWS_7 = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"; + + private static final String IE10_WINDOWS_8 = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)"; + private static final String IE11_WINDOWS_7 = "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; rv:11.0) like Gecko"; + private static final String IE11_WINDOWS_PHONE_8_1_UPDATE = "Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920) Like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537"; + + // "Version/" was added in 10.00 + private static final String OPERA964_WINDOWS = "Opera/9.64(Windows NT 5.1; U; en) Presto/2.1.1"; + private static final String OPERA1010_WINDOWS = "Opera/9.80 (Windows NT 5.1; U; en) Presto/2.2.15 Version/10.10"; + private static final String OPERA1050_WINDOWS = "Opera/9.80 (Windows NT 5.1; U; en) Presto/2.5.22 Version/10.50"; + + private static final String CHROME3_MAC = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/3.0.198 Safari/532.0"; + private static final String CHROME4_WINDOWS = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.89 Safari/532.5"; + + private static final String SAFARI3_WINDOWS = "Mozilla/5.0 (Windows; U; Windows NT 5.1; cs-CZ) AppleWebKit/525.28.3 (KHTML, like Gecko) Version/3.2.3 Safari/525.29"; + private static final String SAFARI4_MAC = "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_5_8; en-us) AppleWebKit/531.22.7 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7"; + + private static final String IPHONE_IOS_5_1 = "Mozilla/5.0 (iPhone; CPU iPhone OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B179 Safari/7534.48.3"; + private static final String IPHONE_IOS_4_0 = "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7"; + private static final String IPAD_IOS_4_3_1 = "Mozilla/5.0 (iPad; U; CPU OS 4_3_1 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8G4 Safari/6533.18.5"; + + // application on the home screen, without Safari in user agent + private static final String IPHONE_IOS_6_1_HOMESCREEN_SIMULATOR = "Mozilla/5.0 (iPhone; CPU iPhone OS 6_1 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Mobile/10B141"; + + private static final String ANDROID_HTC_2_1 = "Mozilla/5.0 (Linux; U; Android 2.1-update1; en-us; ADR6300 Build/ERE27) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17"; + private static final String ANDROID_GOOGLE_NEXUS_2_2 = "Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"; + private static final String ANDROID_MOTOROLA_3_0 = "Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13"; + private static final String ANDROID_GALAXY_NEXUS_4_0_4_CHROME = "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"; + + private static final String EDGE_WINDOWS_10 = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"; + + @Test + public void testSafari3() { + VBrowserDetails bd = new VBrowserDetails(SAFARI3_WINDOWS); + assertWebKit(bd); + assertSafari(bd); + assertBrowserMajorVersion(bd, 3); + assertBrowserMinorVersion(bd, 2); + assertEngineVersion(bd, 525.0f); + assertWindows(bd); + } + + @Test + public void testSafari4() { + VBrowserDetails bd = new VBrowserDetails(SAFARI4_MAC); + assertWebKit(bd); + assertSafari(bd); + assertBrowserMajorVersion(bd, 4); + assertBrowserMinorVersion(bd, 0); + assertEngineVersion(bd, 531f); + assertMacOSX(bd); + } + + @Test + public void testIPhoneIOS6Homescreen() { + VBrowserDetails bd = new VBrowserDetails( + IPHONE_IOS_6_1_HOMESCREEN_SIMULATOR); + assertWebKit(bd); + // not identified as Safari, no browser version available + // assertSafari(bd); + // assertBrowserMajorVersion(bd, 6); + // assertBrowserMinorVersion(bd, 1); + assertEngineVersion(bd, 536f); + assertIOS(bd, 6, 1); + assertIPhone(bd); + } + + @Test + public void testIPhoneIOS5() { + VBrowserDetails bd = new VBrowserDetails(IPHONE_IOS_5_1); + assertWebKit(bd); + assertSafari(bd); + assertBrowserMajorVersion(bd, 5); + assertBrowserMinorVersion(bd, 1); + assertEngineVersion(bd, 534f); + assertIOS(bd, 5, 1); + assertIPhone(bd); + } + + @Test + public void testIPhoneIOS4() { + VBrowserDetails bd = new VBrowserDetails(IPHONE_IOS_4_0); + assertWebKit(bd); + assertSafari(bd); + assertBrowserMajorVersion(bd, 4); + assertBrowserMinorVersion(bd, 0); + assertEngineVersion(bd, 532f); + assertIOS(bd, 4, 0); + assertIPhone(bd); + } + + @Test + public void testIPadIOS4() { + VBrowserDetails bd = new VBrowserDetails(IPAD_IOS_4_3_1); + assertWebKit(bd); + assertSafari(bd); + assertBrowserMajorVersion(bd, 5); + assertBrowserMinorVersion(bd, 0); + assertEngineVersion(bd, 533f); + assertIOS(bd, 4, 3); + assertIPad(bd); + } + + @Test + public void testAndroid21() { + VBrowserDetails bd = new VBrowserDetails(ANDROID_HTC_2_1); + assertWebKit(bd); + assertSafari(bd); + assertBrowserMajorVersion(bd, 4); + assertBrowserMinorVersion(bd, 0); + assertEngineVersion(bd, 530f); + assertAndroid(bd, 2, 1); + + } + + @Test + public void testAndroid22() { + VBrowserDetails bd = new VBrowserDetails(ANDROID_GOOGLE_NEXUS_2_2); + assertWebKit(bd); + assertSafari(bd); + assertBrowserMajorVersion(bd, 4); + assertBrowserMinorVersion(bd, 0); + assertEngineVersion(bd, 533f); + assertAndroid(bd, 2, 2); + } + + @Test + public void testAndroid30() { + VBrowserDetails bd = new VBrowserDetails(ANDROID_MOTOROLA_3_0); + assertWebKit(bd); + assertSafari(bd); + assertBrowserMajorVersion(bd, 4); + assertBrowserMinorVersion(bd, 0); + assertEngineVersion(bd, 534f); + assertAndroid(bd, 3, 0); + } + + @Test + public void testAndroid40Chrome() { + VBrowserDetails bd = new VBrowserDetails( + ANDROID_GALAXY_NEXUS_4_0_4_CHROME); + assertWebKit(bd); + assertChrome(bd); + assertBrowserMajorVersion(bd, 18); + assertBrowserMinorVersion(bd, 0); + assertEngineVersion(bd, 535f); + assertAndroid(bd, 4, 0); + } + + private void assertOSMajorVersion(VBrowserDetails bd, int i) { + assertEquals(i, bd.getOperatingSystemMajorVersion()); + } + + private void assertOSMinorVersion(VBrowserDetails bd, int i) { + assertEquals(i, bd.getOperatingSystemMinorVersion()); + } + + @Test + public void testChrome3() { + VBrowserDetails bd = new VBrowserDetails(CHROME3_MAC); + assertWebKit(bd); + assertChrome(bd); + assertBrowserMajorVersion(bd, 3); + assertBrowserMinorVersion(bd, 0); + assertEngineVersion(bd, 532.0f); + assertMacOSX(bd); + } + + @Test + public void testChrome4() { + VBrowserDetails bd = new VBrowserDetails(CHROME4_WINDOWS); + assertWebKit(bd); + assertChrome(bd); + assertBrowserMajorVersion(bd, 4); + assertBrowserMinorVersion(bd, 0); + assertEngineVersion(bd, 532f); + assertWindows(bd); + } + + @Test + public void testFirefox3() { + VBrowserDetails bd = new VBrowserDetails(FIREFOX30_WINDOWS); + assertGecko(bd); + assertFirefox(bd); + assertBrowserMajorVersion(bd, 3); + assertBrowserMinorVersion(bd, 0); + assertEngineVersion(bd, 1.9f); + assertWindows(bd); + + bd = new VBrowserDetails(FIREFOX30_LINUX); + assertGecko(bd); + assertFirefox(bd); + assertBrowserMajorVersion(bd, 3); + assertBrowserMinorVersion(bd, 0); + assertEngineVersion(bd, 1.9f); + assertLinux(bd); + } + + @Test + public void testFirefox33Android() { + VBrowserDetails bd = new VBrowserDetails(FIREFOX33_ANDROID); + assertGecko(bd); + assertFirefox(bd); + assertBrowserMajorVersion(bd, 33); + assertBrowserMinorVersion(bd, 0); + assertAndroid(bd, -1, -1); + } + + @Test + public void testFirefox35() { + VBrowserDetails bd = new VBrowserDetails(FIREFOX35_WINDOWS); + assertGecko(bd); + assertFirefox(bd); + assertBrowserMajorVersion(bd, 3); + assertBrowserMinorVersion(bd, 5); + assertEngineVersion(bd, 1.9f); + assertWindows(bd); + } + + @Test + public void testFirefox36() { + VBrowserDetails bd = new VBrowserDetails(FIREFOX36_WINDOWS); + assertGecko(bd); + assertFirefox(bd); + assertBrowserMajorVersion(bd, 3); + assertBrowserMinorVersion(bd, 6); + assertEngineVersion(bd, 1.9f); + assertWindows(bd); + } + + @Test + public void testFirefox30b5() { + VBrowserDetails bd = new VBrowserDetails(FIREFOX_30B5_MAC); + assertGecko(bd); + assertFirefox(bd); + assertBrowserMajorVersion(bd, 3); + assertBrowserMinorVersion(bd, 0); + assertEngineVersion(bd, 1.9f); + assertMacOSX(bd); + } + + @Test + public void testFirefox40b11() { + VBrowserDetails bd = new VBrowserDetails(FIREFOX_40B11_WIN); + assertGecko(bd); + assertFirefox(bd); + assertBrowserMajorVersion(bd, 4); + assertBrowserMinorVersion(bd, 0); + assertEngineVersion(bd, 2.0f); + assertWindows(bd); + } + + @Test + public void testFirefox40b7() { + VBrowserDetails bd = new VBrowserDetails(FIREFOX_40B7_WIN); + assertGecko(bd); + assertFirefox(bd); + assertBrowserMajorVersion(bd, 4); + assertBrowserMinorVersion(bd, 0); + assertEngineVersion(bd, 2.0f); + assertWindows(bd); + } + + @Test + public void testKonquerorLinux() { + // Just ensure detection does not crash + VBrowserDetails bd = new VBrowserDetails(KONQUEROR_LINUX); + assertLinux(bd); + } + + @Test + public void testFirefox36b() { + VBrowserDetails bd = new VBrowserDetails(FIREFOX36B_MAC); + assertGecko(bd); + assertFirefox(bd); + assertBrowserMajorVersion(bd, 3); + assertBrowserMinorVersion(bd, 6); + assertEngineVersion(bd, 1.9f); + assertMacOSX(bd); + } + + @Test + public void testOpera964() { + VBrowserDetails bd = new VBrowserDetails(OPERA964_WINDOWS); + assertPresto(bd); + assertOpera(bd); + assertBrowserMajorVersion(bd, 9); + assertBrowserMinorVersion(bd, 64); + assertWindows(bd); + } + + @Test + public void testOpera1010() { + VBrowserDetails bd = new VBrowserDetails(OPERA1010_WINDOWS); + assertPresto(bd); + assertOpera(bd); + assertBrowserMajorVersion(bd, 10); + assertBrowserMinorVersion(bd, 10); + assertWindows(bd); + } + + @Test + public void testOpera1050() { + VBrowserDetails bd = new VBrowserDetails(OPERA1050_WINDOWS); + assertPresto(bd); + assertOpera(bd); + assertBrowserMajorVersion(bd, 10); + assertBrowserMinorVersion(bd, 50); + assertWindows(bd); + } + + @Test + public void testIE6() { + VBrowserDetails bd = new VBrowserDetails(IE6_WINDOWS); + assertEngineVersion(bd, -1); + assertIE(bd); + assertBrowserMajorVersion(bd, 6); + assertBrowserMinorVersion(bd, 0); + assertWindows(bd); + } + + @Test + public void testIE7() { + VBrowserDetails bd = new VBrowserDetails(IE7_WINDOWS); + assertEngineVersion(bd, -1); + assertIE(bd); + assertBrowserMajorVersion(bd, 7); + assertBrowserMinorVersion(bd, 0); + assertWindows(bd); + } + + @Test + public void testIE8() { + VBrowserDetails bd = new VBrowserDetails(IE8_WINDOWS); + assertTrident(bd); + assertEngineVersion(bd, 4); + assertIE(bd); + assertBrowserMajorVersion(bd, 8); + assertBrowserMinorVersion(bd, 0); + assertWindows(bd); + } + + @Test + public void testIE8CompatibilityMode() { + VBrowserDetails bd = new VBrowserDetails(IE8_IN_IE7_MODE_WINDOWS); + bd.setIEMode(7); + + assertTrident(bd); + assertEngineVersion(bd, 4); + assertIE(bd); + assertBrowserMajorVersion(bd, 7); + assertBrowserMinorVersion(bd, 0); + + assertWindows(bd); + } + + @Test + public void testIE9() { + VBrowserDetails bd = new VBrowserDetails(IE9_BETA_WINDOWS_7); + assertTrident(bd); + assertEngineVersion(bd, 5); + assertIE(bd); + assertBrowserMajorVersion(bd, 9); + assertBrowserMinorVersion(bd, 0); + assertWindows(bd); + } + + @Test + public void testIE9InIE7CompatibilityMode() { + VBrowserDetails bd = new VBrowserDetails(IE9_IN_IE7_MODE_WINDOWS_7); + // bd.setIE8InCompatibilityMode(); + + assertTrident(bd); + assertEngineVersion(bd, 5); + assertIE(bd); + assertBrowserMajorVersion(bd, 7); + assertBrowserMinorVersion(bd, 0); + + assertWindows(bd); + } + + @Test + public void testIE9InIE8CompatibilityMode() { + VBrowserDetails bd = new VBrowserDetails(IE9_BETA_IN_IE8_MODE_WINDOWS_7); + // bd.setIE8InCompatibilityMode(); + + /* + * Trident/4.0 in example user agent string based on beta even though it + * should be Trident/5.0 in real (non-beta) user agent strings + */ + assertTrident(bd); + assertEngineVersion(bd, 4); + assertIE(bd); + assertBrowserMajorVersion(bd, 8); + assertBrowserMinorVersion(bd, 0); + + assertWindows(bd); + } + + @Test + public void testIE10() { + VBrowserDetails bd = new VBrowserDetails(IE10_WINDOWS_8); + assertTrident(bd); + assertEngineVersion(bd, 6); + assertIE(bd); + assertBrowserMajorVersion(bd, 10); + assertBrowserMinorVersion(bd, 0); + assertWindows(bd); + } + + @Test + public void testIE11() { + VBrowserDetails bd = new VBrowserDetails(IE11_WINDOWS_7); + assertTrident(bd); + assertEngineVersion(bd, 7); + assertIE(bd); + assertBrowserMajorVersion(bd, 11); + assertBrowserMinorVersion(bd, 0); + assertWindows(bd); + } + + @Test + public void testIE11WindowsPhone81Update() { + VBrowserDetails bd = new VBrowserDetails(IE11_WINDOWS_PHONE_8_1_UPDATE); + assertTrident(bd); + assertEngineVersion(bd, 7); + assertIE(bd); + assertBrowserMajorVersion(bd, 11); + assertBrowserMinorVersion(bd, 0); + assertWindows(bd, true); + } + + @Test + public void testEdgeWindows10() { + VBrowserDetails bd = new VBrowserDetails(EDGE_WINDOWS_10); + assertEdge(bd); + assertBrowserMajorVersion(bd, 12); + assertBrowserMinorVersion(bd, 10240); + assertWindows(bd, false); + } + + /* + * Helper methods below + */ + + private void assertEngineVersion(VBrowserDetails browserDetails, + float version) { + assertEquals(version, browserDetails.getBrowserEngineVersion(), 0.01d); + + } + + private void assertBrowserMajorVersion(VBrowserDetails browserDetails, + int version) { + assertEquals(version, browserDetails.getBrowserMajorVersion()); + + } + + private void assertBrowserMinorVersion(VBrowserDetails browserDetails, + int version) { + assertEquals(version, browserDetails.getBrowserMinorVersion()); + + } + + private void assertGecko(VBrowserDetails browserDetails) { + // Engine + assertTrue(browserDetails.isGecko()); + assertFalse(browserDetails.isWebKit()); + assertFalse(browserDetails.isPresto()); + assertFalse(browserDetails.isTrident()); + } + + private void assertPresto(VBrowserDetails browserDetails) { + // Engine + assertFalse(browserDetails.isGecko()); + assertFalse(browserDetails.isWebKit()); + assertTrue(browserDetails.isPresto()); + assertFalse(browserDetails.isTrident()); + } + + private void assertTrident(VBrowserDetails browserDetails) { + // Engine + assertFalse(browserDetails.isGecko()); + assertFalse(browserDetails.isWebKit()); + assertFalse(browserDetails.isPresto()); + assertTrue(browserDetails.isTrident()); + } + + private void assertWebKit(VBrowserDetails browserDetails) { + // Engine + assertFalse(browserDetails.isGecko()); + assertTrue(browserDetails.isWebKit()); + assertFalse(browserDetails.isPresto()); + assertFalse(browserDetails.isTrident()); + } + + private void assertFirefox(VBrowserDetails browserDetails) { + // Browser + assertTrue(browserDetails.isFirefox()); + assertFalse(browserDetails.isChrome()); + assertFalse(browserDetails.isIE()); + assertFalse(browserDetails.isOpera()); + assertFalse(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); + } + + private void assertChrome(VBrowserDetails browserDetails) { + // Browser + assertFalse(browserDetails.isFirefox()); + assertTrue(browserDetails.isChrome()); + assertFalse(browserDetails.isIE()); + assertFalse(browserDetails.isOpera()); + assertFalse(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); + } + + private void assertIE(VBrowserDetails browserDetails) { + // Browser + assertFalse(browserDetails.isFirefox()); + assertFalse(browserDetails.isChrome()); + assertTrue(browserDetails.isIE()); + assertFalse(browserDetails.isOpera()); + assertFalse(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); + } + + private void assertOpera(VBrowserDetails browserDetails) { + // Browser + assertFalse(browserDetails.isFirefox()); + assertFalse(browserDetails.isChrome()); + assertFalse(browserDetails.isIE()); + assertTrue(browserDetails.isOpera()); + assertFalse(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); + } + + private void assertSafari(VBrowserDetails browserDetails) { + // Browser + assertFalse(browserDetails.isFirefox()); + assertFalse(browserDetails.isChrome()); + assertFalse(browserDetails.isIE()); + assertFalse(browserDetails.isOpera()); + assertTrue(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); + } + + private void assertEdge(VBrowserDetails browserDetails) { + // Browser + assertFalse(browserDetails.isFirefox()); + assertFalse(browserDetails.isChrome()); + assertFalse(browserDetails.isIE()); + assertFalse(browserDetails.isOpera()); + assertFalse(browserDetails.isSafari()); + assertTrue(browserDetails.isEdge()); + } + + private void assertMacOSX(VBrowserDetails browserDetails) { + assertFalse(browserDetails.isLinux()); + assertFalse(browserDetails.isWindows()); + assertTrue(browserDetails.isMacOSX()); + assertFalse(browserDetails.isAndroid()); + } + + private void assertAndroid(VBrowserDetails browserDetails, + int majorVersion, int minorVersion) { + assertFalse(browserDetails.isLinux()); + assertFalse(browserDetails.isWindows()); + assertFalse(browserDetails.isMacOSX()); + assertFalse(browserDetails.isIOS()); + assertTrue(browserDetails.isAndroid()); + + assertOSMajorVersion(browserDetails, majorVersion); + assertOSMinorVersion(browserDetails, minorVersion); + } + + private void assertIOS(VBrowserDetails browserDetails, int majorVersion, + int minorVersion) { + assertFalse(browserDetails.isLinux()); + assertFalse(browserDetails.isWindows()); + assertFalse(browserDetails.isMacOSX()); + assertTrue(browserDetails.isIOS()); + assertFalse(browserDetails.isAndroid()); + + assertOSMajorVersion(browserDetails, majorVersion); + assertOSMinorVersion(browserDetails, minorVersion); + } + + private void assertIPhone(VBrowserDetails browserDetails) { + assertTrue(browserDetails.isIPhone()); + assertFalse(browserDetails.isIPad()); + } + + private void assertIPad(VBrowserDetails browserDetails) { + assertFalse(browserDetails.isIPhone()); + assertTrue(browserDetails.isIPad()); + } + + private void assertWindows(VBrowserDetails browserDetails) { + assertWindows(browserDetails, false); + } + + private void assertWindows(VBrowserDetails browserDetails, + boolean isWindowsPhone) { + assertFalse(browserDetails.isLinux()); + assertTrue(browserDetails.isWindows()); + assertFalse(browserDetails.isMacOSX()); + assertFalse(browserDetails.isIOS()); + assertFalse(browserDetails.isAndroid()); + Assert.assertEquals(isWindowsPhone, browserDetails.isWindowsPhone()); + } + + private void assertLinux(VBrowserDetails browserDetails) { + assertTrue(browserDetails.isLinux()); + assertFalse(browserDetails.isWindows()); + assertFalse(browserDetails.isMacOSX()); + assertFalse(browserDetails.isIOS()); + assertFalse(browserDetails.isAndroid()); + } + +} diff --git a/client/src/test/java/com/vaadin/client/communication/ServerMessageHandlerTest.java b/client/src/test/java/com/vaadin/client/communication/ServerMessageHandlerTest.java new file mode 100644 index 0000000000..c2752f1953 --- /dev/null +++ b/client/src/test/java/com/vaadin/client/communication/ServerMessageHandlerTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.communication; + +import org.junit.Assert; +import org.junit.Test; + +/** + * + * @since + * @author Vaadin Ltd + */ +public class ServerMessageHandlerTest { + + @Test + public void unwrapValidJson() { + String payload = "{'foo': 'bar'}"; + Assert.assertEquals(payload, + MessageHandler.stripJSONWrapping("for(;;);[" + payload + "]")); + + } + + @Test + public void unwrapUnwrappedJson() { + String payload = "{'foo': 'bar'}"; + Assert.assertNull(MessageHandler.stripJSONWrapping(payload)); + + } + + @Test + public void unwrapNull() { + Assert.assertNull(MessageHandler.stripJSONWrapping(null)); + + } + + @Test + public void unwrapEmpty() { + Assert.assertNull(MessageHandler.stripJSONWrapping("")); + + } +} diff --git a/client/src/test/java/com/vaadin/client/ui/grid/ListDataSourceTest.java b/client/src/test/java/com/vaadin/client/ui/grid/ListDataSourceTest.java new file mode 100644 index 0000000000..24ccd6c57e --- /dev/null +++ b/client/src/test/java/com/vaadin/client/ui/grid/ListDataSourceTest.java @@ -0,0 +1,192 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.grid; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Comparator; + +import org.easymock.EasyMock; +import org.junit.Test; + +import com.vaadin.client.data.DataChangeHandler; +import com.vaadin.client.widget.grid.datasources.ListDataSource; + +public class ListDataSourceTest { + + @Test + public void testDataSourceConstruction() throws Exception { + + ListDataSource ds = new ListDataSource(0, 1, 2, 3); + + assertEquals(4, ds.size()); + assertEquals(0, (int) ds.getRow(0)); + assertEquals(1, (int) ds.getRow(1)); + assertEquals(2, (int) ds.getRow(2)); + assertEquals(3, (int) ds.getRow(3)); + + ds = new ListDataSource(Arrays.asList(0, 1, 2, 3)); + + assertEquals(4, ds.size()); + assertEquals(0, (int) ds.getRow(0)); + assertEquals(1, (int) ds.getRow(1)); + assertEquals(2, (int) ds.getRow(2)); + assertEquals(3, (int) ds.getRow(3)); + } + + @Test + public void testListAddOperation() throws Exception { + + ListDataSource ds = new ListDataSource(0, 1, 2, 3); + + DataChangeHandler handler = EasyMock + .createNiceMock(DataChangeHandler.class); + ds.setDataChangeHandler(handler); + + handler.dataAdded(4, 1); + EasyMock.expectLastCall(); + + EasyMock.replay(handler); + + ds.asList().add(4); + + assertEquals(5, ds.size()); + assertEquals(0, (int) ds.getRow(0)); + assertEquals(1, (int) ds.getRow(1)); + assertEquals(2, (int) ds.getRow(2)); + assertEquals(3, (int) ds.getRow(3)); + assertEquals(4, (int) ds.getRow(4)); + } + + @Test + public void testListAddAllOperation() throws Exception { + + ListDataSource ds = new ListDataSource(0, 1, 2, 3); + + DataChangeHandler handler = EasyMock + .createNiceMock(DataChangeHandler.class); + ds.setDataChangeHandler(handler); + + handler.dataAdded(4, 3); + EasyMock.expectLastCall(); + + EasyMock.replay(handler); + + ds.asList().addAll(Arrays.asList(4, 5, 6)); + + assertEquals(7, ds.size()); + assertEquals(0, (int) ds.getRow(0)); + assertEquals(1, (int) ds.getRow(1)); + assertEquals(2, (int) ds.getRow(2)); + assertEquals(3, (int) ds.getRow(3)); + assertEquals(4, (int) ds.getRow(4)); + assertEquals(5, (int) ds.getRow(5)); + assertEquals(6, (int) ds.getRow(6)); + } + + @Test + public void testListRemoveOperation() throws Exception { + + ListDataSource ds = new ListDataSource(0, 1, 2, 3); + + DataChangeHandler handler = EasyMock + .createNiceMock(DataChangeHandler.class); + ds.setDataChangeHandler(handler); + + handler.dataRemoved(3, 1); + EasyMock.expectLastCall(); + + EasyMock.replay(handler); + + ds.asList().remove(2); + + assertEquals(3, ds.size()); + assertEquals(0, (int) ds.getRow(0)); + assertEquals(1, (int) ds.getRow(1)); + assertEquals(3, (int) ds.getRow(2)); + } + + @Test + public void testListRemoveAllOperation() throws Exception { + + ListDataSource ds = new ListDataSource(0, 1, 2, 3); + + DataChangeHandler handler = EasyMock + .createNiceMock(DataChangeHandler.class); + ds.setDataChangeHandler(handler); + + handler.dataRemoved(0, 3); + EasyMock.expectLastCall(); + + EasyMock.replay(handler); + + ds.asList().removeAll(Arrays.asList(0, 2, 3)); + + assertEquals(1, ds.size()); + assertEquals(1, (int) ds.getRow(0)); + } + + @Test + public void testListClearOperation() throws Exception { + + ListDataSource ds = new ListDataSource(0, 1, 2, 3); + + DataChangeHandler handler = EasyMock + .createNiceMock(DataChangeHandler.class); + ds.setDataChangeHandler(handler); + + handler.dataRemoved(0, 4); + EasyMock.expectLastCall(); + + EasyMock.replay(handler); + + ds.asList().clear(); + + assertEquals(0, ds.size()); + } + + @Test(expected = IllegalStateException.class) + public void testFetchingNonExistantItem() { + ListDataSource ds = new ListDataSource(0, 1, 2, 3); + ds.ensureAvailability(5, 1); + } + + @Test(expected = UnsupportedOperationException.class) + public void testUnsupportedIteratorRemove() { + ListDataSource ds = new ListDataSource(0, 1, 2, 3); + ds.asList().iterator().remove(); + } + + @Test + public void sortColumn() { + ListDataSource ds = new ListDataSource(3, 4, 2, 3, 1); + + // TODO Should be simplified to sort(). No point in providing these + // trivial comparators. + ds.sort(new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return o1.compareTo(o2); + } + }); + + assertTrue(Arrays.equals(ds.asList().toArray(), new Integer[] { 1, 2, + 3, 3, 4 })); + } + +} diff --git a/client/src/test/java/com/vaadin/client/ui/grid/PartitioningTest.java b/client/src/test/java/com/vaadin/client/ui/grid/PartitioningTest.java new file mode 100644 index 0000000000..e97bb339e4 --- /dev/null +++ b/client/src/test/java/com/vaadin/client/ui/grid/PartitioningTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui.grid; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.vaadin.shared.ui.grid.Range; + +@SuppressWarnings("static-method") +public class PartitioningTest { + + @Test + public void selfRangeTest() { + final Range range = Range.between(0, 10); + final Range[] partitioning = range.partitionWith(range); + + assertTrue("before is empty", partitioning[0].isEmpty()); + assertTrue("inside is self", partitioning[1].equals(range)); + assertTrue("after is empty", partitioning[2].isEmpty()); + } + + @Test + public void beforeRangeTest() { + final Range beforeRange = Range.between(0, 10); + final Range afterRange = Range.between(10, 20); + final Range[] partitioning = beforeRange.partitionWith(afterRange); + + assertTrue("before is self", partitioning[0].equals(beforeRange)); + assertTrue("inside is empty", partitioning[1].isEmpty()); + assertTrue("after is empty", partitioning[2].isEmpty()); + } + + @Test + public void afterRangeTest() { + final Range beforeRange = Range.between(0, 10); + final Range afterRange = Range.between(10, 20); + final Range[] partitioning = afterRange.partitionWith(beforeRange); + + assertTrue("before is empty", partitioning[0].isEmpty()); + assertTrue("inside is empty", partitioning[1].isEmpty()); + assertTrue("after is self", partitioning[2].equals(afterRange)); + } + + @Test + public void beforeAndInsideRangeTest() { + final Range beforeRange = Range.between(0, 10); + final Range afterRange = Range.between(5, 15); + final Range[] partitioning = beforeRange.partitionWith(afterRange); + + assertEquals("before", Range.between(0, 5), partitioning[0]); + assertEquals("inside", Range.between(5, 10), partitioning[1]); + assertTrue("after is empty", partitioning[2].isEmpty()); + } + + @Test + public void insideRangeTest() { + final Range fullRange = Range.between(0, 20); + final Range insideRange = Range.between(5, 15); + final Range[] partitioning = insideRange.partitionWith(fullRange); + + assertTrue("before is empty", partitioning[0].isEmpty()); + assertEquals("inside", Range.between(5, 15), partitioning[1]); + assertTrue("after is empty", partitioning[2].isEmpty()); + } + + @Test + public void insideAndBelowTest() { + final Range beforeRange = Range.between(0, 10); + final Range afterRange = Range.between(5, 15); + final Range[] partitioning = afterRange.partitionWith(beforeRange); + + assertTrue("before is empty", partitioning[0].isEmpty()); + assertEquals("inside", Range.between(5, 10), partitioning[1]); + assertEquals("after", Range.between(10, 15), partitioning[2]); + } + + @Test + public void aboveAndBelowTest() { + final Range fullRange = Range.between(0, 20); + final Range insideRange = Range.between(5, 15); + final Range[] partitioning = fullRange.partitionWith(insideRange); + + assertEquals("before", Range.between(0, 5), partitioning[0]); + assertEquals("inside", Range.between(5, 15), partitioning[1]); + assertEquals("after", Range.between(15, 20), partitioning[2]); + } +} diff --git a/client/tests/src/com/vaadin/client/ApplicationConnectionURLGenerationTest.java b/client/tests/src/com/vaadin/client/ApplicationConnectionURLGenerationTest.java deleted file mode 100644 index 4dd2ddbaa4..0000000000 --- a/client/tests/src/com/vaadin/client/ApplicationConnectionURLGenerationTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.vaadin.client; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -import com.vaadin.shared.util.SharedUtil; - -public class ApplicationConnectionURLGenerationTest { - - private static final String[] URIS = new String[] { - "http://demo.vaadin.com/", // - "https://demo.vaadin.com/", - "http://demo.vaadin.com/foo", - "http://demo.vaadin.com/foo?f", - "http://demo.vaadin.com/foo?f=1", - "http://demo.vaadin.com:1234/foo?a", - "http://demo.vaadin.com:1234/foo#frag?fakeparam", - // Jetspeed - "http://localhost:8080/jetspeed/portal/_ns:Z3RlbXBsYXRlLXRvcDJfX3BhZ2UtdGVtcGxhdGVfX2RwLTFfX1AtMTJjNTRkYjdlYjUtMTAwMDJ8YzB8ZDF8aVVJREx8Zg__", - // Liferay generated url - "http://vaadin.com/directory?p_p_id=Directory_WAR_Directory&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=UIDL&p_p_cacheability=cacheLevelPage&p_p_col_id=row-1&p_p_col_count=1", - - }; - private static final String[] URIS_WITH_ABCD_PARAM = new String[] { - "http://demo.vaadin.com/?a=b&c=d", - "https://demo.vaadin.com/?a=b&c=d", - "http://demo.vaadin.com/foo?a=b&c=d", - "http://demo.vaadin.com/foo?f&a=b&c=d", - "http://demo.vaadin.com/foo?f=1&a=b&c=d", - "http://demo.vaadin.com:1234/foo?a&a=b&c=d", - "http://demo.vaadin.com:1234/foo?a=b&c=d#frag?fakeparam", - "http://localhost:8080/jetspeed/portal/_ns:Z3RlbXBsYXRlLXRvcDJfX3BhZ2UtdGVtcGxhdGVfX2RwLTFfX1AtMTJjNTRkYjdlYjUtMTAwMDJ8YzB8ZDF8aVVJREx8Zg__?a=b&c=d", - "http://vaadin.com/directory?p_p_id=Directory_WAR_Directory&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=UIDL&p_p_cacheability=cacheLevelPage&p_p_col_id=row-1&p_p_col_count=1&a=b&c=d", - - }; - - private static final String[] URIS_WITH_ABCD_PARAM_AND_FRAGMENT = new String[] { - "http://demo.vaadin.com/?a=b&c=d#fragment", - "https://demo.vaadin.com/?a=b&c=d#fragment", - "http://demo.vaadin.com/foo?a=b&c=d#fragment", - "http://demo.vaadin.com/foo?f&a=b&c=d#fragment", - "http://demo.vaadin.com/foo?f=1&a=b&c=d#fragment", - "http://demo.vaadin.com:1234/foo?a&a=b&c=d#fragment", - "", - "http://localhost:8080/jetspeed/portal/_ns:Z3RlbXBsYXRlLXRvcDJfX3BhZ2UtdGVtcGxhdGVfX2RwLTFfX1AtMTJjNTRkYjdlYjUtMTAwMDJ8YzB8ZDF8aVVJREx8Zg__?a=b&c=d#fragment", - "http://vaadin.com/directory?p_p_id=Directory_WAR_Directory&p_p_lifecycle=2&p_p_state=normal&p_p_mode=view&p_p_resource_id=UIDL&p_p_cacheability=cacheLevelPage&p_p_col_id=row-1&p_p_col_count=1&a=b&c=d#fragment", - - }; - - @Test - public void testParameterAdding() { - for (int i = 0; i < URIS.length; i++) { - // Adding nothing - assertEquals(URIS[i], SharedUtil.addGetParameters(URIS[i], "")); - - // Adding a=b&c=d - assertEquals(URIS_WITH_ABCD_PARAM[i], - SharedUtil.addGetParameters(URIS[i], "a=b&c=d")); - - // Fragments - if (URIS_WITH_ABCD_PARAM_AND_FRAGMENT[i].length() > 0) { - assertEquals(URIS_WITH_ABCD_PARAM_AND_FRAGMENT[i], - SharedUtil.addGetParameters(URIS[i] + "#fragment", - "a=b&c=d")); - - // Empty fragment - assertEquals(URIS_WITH_ABCD_PARAM_AND_FRAGMENT[i].replace( - "#fragment", "#"), SharedUtil.addGetParameters(URIS[i] - + "#", "a=b&c=d")); - } - } - } -} diff --git a/client/tests/src/com/vaadin/client/DateTimeServiceTest.java b/client/tests/src/com/vaadin/client/DateTimeServiceTest.java deleted file mode 100755 index e5e6cbd7db..0000000000 --- a/client/tests/src/com/vaadin/client/DateTimeServiceTest.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.vaadin.client; - -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import org.junit.Assert; -import org.junit.Test; - -public class DateTimeServiceTest { - - final long MILLISECONDS_PER_DAY = 24 * 3600 * 1000; - - static Map isoWeekNumbers = new HashMap(); - static { - isoWeekNumbers.put(getDate(2005, 02, 02), 5); - - isoWeekNumbers.put(getDate(2005, 1, 1), 53); - isoWeekNumbers.put(getDate(2005, 1, 2), 53); - isoWeekNumbers.put(getDate(2005, 1, 3), 1); - isoWeekNumbers.put(getDate(2005, 1, 4), 1); - isoWeekNumbers.put(getDate(2005, 1, 5), 1); - isoWeekNumbers.put(getDate(2005, 1, 6), 1); - isoWeekNumbers.put(getDate(2005, 1, 7), 1); - isoWeekNumbers.put(getDate(2005, 1, 8), 1); - isoWeekNumbers.put(getDate(2005, 1, 9), 1); - isoWeekNumbers.put(getDate(2005, 1, 10), 2); - isoWeekNumbers.put(getDate(2005, 12, 31), 52); - isoWeekNumbers.put(getDate(2005, 12, 30), 52); - isoWeekNumbers.put(getDate(2005, 12, 29), 52); - isoWeekNumbers.put(getDate(2005, 12, 28), 52); - isoWeekNumbers.put(getDate(2005, 12, 27), 52); - isoWeekNumbers.put(getDate(2005, 12, 26), 52); - isoWeekNumbers.put(getDate(2005, 12, 25), 51); - isoWeekNumbers.put(getDate(2007, 1, 1), 1); - isoWeekNumbers.put(getDate(2007, 12, 30), 52); - isoWeekNumbers.put(getDate(2007, 12, 31), 1); - isoWeekNumbers.put(getDate(2008, 1, 1), 1); - isoWeekNumbers.put(getDate(2008, 12, 28), 52); - isoWeekNumbers.put(getDate(2008, 12, 29), 1); - isoWeekNumbers.put(getDate(2008, 12, 30), 1); - isoWeekNumbers.put(getDate(2008, 12, 31), 1); - isoWeekNumbers.put(getDate(2009, 1, 1), 1); - isoWeekNumbers.put(getDate(2009, 12, 31), 53); - isoWeekNumbers.put(getDate(2010, 1, 1), 53); - isoWeekNumbers.put(getDate(2010, 1, 2), 53); - isoWeekNumbers.put(getDate(2010, 1, 3), 53); - isoWeekNumbers.put(getDate(2010, 1, 4), 1); - isoWeekNumbers.put(getDate(2010, 1, 5), 1); - isoWeekNumbers.put(getDate(2010, 10, 10), 40); - isoWeekNumbers.put(getDate(2015, 3, 24), 13); - isoWeekNumbers.put(getDate(2015, 3, 31), 14); - isoWeekNumbers.put(getDate(2015, 10, 13), 42); - isoWeekNumbers.put(getDate(2015, 10, 20), 43); - isoWeekNumbers.put(getDate(2015, 10, 27), 44); - isoWeekNumbers.put(getDate(2026, 3, 24), 13); - isoWeekNumbers.put(getDate(2026, 3, 31), 14); - isoWeekNumbers.put(getDate(2026, 10, 13), 42); - isoWeekNumbers.put(getDate(2026, 10, 20), 43); - isoWeekNumbers.put(getDate(2026, 10, 27), 44); - - } - - /** - * Test all dates from 1990-1992 + some more and see that {@link Calendar} - * calculates the ISO week number like we do. - * - */ - @Test - public void testISOWeekNumbers() { - Calendar c = Calendar.getInstance(); - c.set(1990, 1, 1); - long start = c.getTimeInMillis(); - - for (int i = 0; i < 1000; i++) { - Date d = new Date(start + i * MILLISECONDS_PER_DAY); - int expected = getCalendarISOWeekNr(d); - int calculated = DateTimeService.getISOWeekNumber(d); - Assert.assertEquals(d + " should be week " + expected, expected, - calculated); - - } - } - - /** - * Verify that special cases are handled correctly by us (and - * {@link Calendar}). - * - */ - @Test - public void testSampleISOWeekNumbers() { - for (Date d : isoWeekNumbers.keySet()) { - // System.out.println("Sample: " + d); - int expected = isoWeekNumbers.get(d); - int calculated = DateTimeService.getISOWeekNumber(d); - Assert.assertEquals(d + " should be week " + expected - + " (Java Calendar is wrong?)", expected, - getCalendarISOWeekNr(d)); - Assert.assertEquals(d + " should be week " + expected, expected, - calculated); - - } - } - - private int getCalendarISOWeekNr(Date d) { - Calendar c = Calendar.getInstance(); - c.setFirstDayOfWeek(Calendar.MONDAY); - c.setMinimalDaysInFirstWeek(4); - c.setTime(d); - - return c.get(Calendar.WEEK_OF_YEAR); - } - - private static Date getDate(int year, int month, int date) { - Calendar c = Calendar.getInstance(); - c.clear(); - c.set(year, month - 1, date); - return c.getTime(); - } - -} diff --git a/client/tests/src/com/vaadin/client/LocatorUtilTest.java b/client/tests/src/com/vaadin/client/LocatorUtilTest.java deleted file mode 100644 index 971688e1c5..0000000000 --- a/client/tests/src/com/vaadin/client/LocatorUtilTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client; - -import org.junit.Assert; -import org.junit.Test; - -import com.vaadin.client.componentlocator.LocatorUtil; - -/* - * Test LocatorUtil.isUIElement() & isNotificaitonElement methods - */ -public class LocatorUtilTest { - - @Test - public void testIsUI1() { - boolean isUI = LocatorUtil.isUIElement("com.vaadin.ui.UI"); - Assert.assertTrue(isUI); - } - - @Test - public void testIsUI2() { - boolean isUI = LocatorUtil.isUIElement("/com.vaadin.ui.UI"); - Assert.assertTrue(isUI); - } - - @Test - public void testIsUI3() { - boolean isUI = LocatorUtil - .isUIElement("//com.vaadin.ui.UI[RandomString"); - Assert.assertTrue(isUI); - } - - @Test - public void testIsUI4() { - boolean isUI = LocatorUtil.isUIElement("//com.vaadin.ui.UI[0]"); - Assert.assertTrue(isUI); - } - - @Test - public void testIsNotification1() { - boolean isUI = LocatorUtil - .isNotificationElement("com.vaadin.ui.VNotification"); - Assert.assertTrue(isUI); - } - - @Test - public void testIsNotification2() { - boolean isUI = LocatorUtil - .isNotificationElement("com.vaadin.ui.Notification"); - Assert.assertTrue(isUI); - } - - @Test - public void testIsNotification3() { - boolean isUI = LocatorUtil - .isNotificationElement("/com.vaadin.ui.VNotification["); - Assert.assertTrue(isUI); - } - - @Test - public void testIsNotification4() { - boolean isUI = LocatorUtil - .isNotificationElement("//com.vaadin.ui.VNotification[0]"); - Assert.assertTrue(isUI); - } -} diff --git a/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java b/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java deleted file mode 100644 index 5522b8e2bc..0000000000 --- a/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java +++ /dev/null @@ -1,651 +0,0 @@ -package com.vaadin.client; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import org.junit.Assert; -import org.junit.Test; - -import com.vaadin.shared.VBrowserDetails; - -public class VBrowserDetailsUserAgentParserTest { - - private static final String FIREFOX30_WINDOWS = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.0.6) Gecko/2009011913 Firefox/3.0.6"; - private static final String FIREFOX30_LINUX = "Mozilla/5.0 (X11; U; Linux x86_64; es-ES; rv:1.9.0.12) Gecko/2009070811 Ubuntu/9.04 (jaunty) Firefox/3.0.12"; - private static final String FIREFOX33_ANDROID = "Mozilla/5.0 (Android; Tablet; rv:33.0) Gecko/33.0 Firefox/33.0"; - private static final String FIREFOX35_WINDOWS = "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8 (.NET CLR 3.5.30729) FirePHP/0.4"; - private static final String FIREFOX36_WINDOWS = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2) Gecko/20100115 Firefox/3.6 (.NET CLR 3.5.30729)"; - private static final String FIREFOX36B_MAC = "UAString mozilla/5.0 (macintosh; u; intel mac os x 10.6; en-us; rv:1.9.2) gecko/20100115 firefox/3.6"; - private static final String FIREFOX_30B5_MAC = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9b5) Gecko/2008032619 Firefox/3.0b5"; - private static final String FIREFOX_40B7_WIN = "Mozilla/5.0 (Windows NT 5.1; rv:2.0b7) Gecko/20100101 Firefox/4.0b7"; - private static final String FIREFOX_40B11_WIN = "Mozilla/5.0 (Windows NT 5.1; rv:2.0b11) Gecko/20100101 Firefox/4.0b11"; - private static final String KONQUEROR_LINUX = "Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Exabot-Thumbnails)"; - - private static final String IE6_WINDOWS = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)"; - private static final String IE7_WINDOWS = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"; - - private static final String IE8_WINDOWS = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; InfoPath.2)"; - private static final String IE8_IN_IE7_MODE_WINDOWS = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; InfoPath.2)"; - - private static final String IE9_IN_IE7_MODE_WINDOWS_7 = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)"; - private static final String IE9_BETA_IN_IE8_MODE_WINDOWS_7 = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)"; - private static final String IE9_BETA_WINDOWS_7 = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"; - - private static final String IE10_WINDOWS_8 = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)"; - private static final String IE11_WINDOWS_7 = "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; rv:11.0) like Gecko"; - private static final String IE11_WINDOWS_PHONE_8_1_UPDATE = "Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920) Like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537"; - - // "Version/" was added in 10.00 - private static final String OPERA964_WINDOWS = "Opera/9.64(Windows NT 5.1; U; en) Presto/2.1.1"; - private static final String OPERA1010_WINDOWS = "Opera/9.80 (Windows NT 5.1; U; en) Presto/2.2.15 Version/10.10"; - private static final String OPERA1050_WINDOWS = "Opera/9.80 (Windows NT 5.1; U; en) Presto/2.5.22 Version/10.50"; - - private static final String CHROME3_MAC = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/3.0.198 Safari/532.0"; - private static final String CHROME4_WINDOWS = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.89 Safari/532.5"; - - private static final String SAFARI3_WINDOWS = "Mozilla/5.0 (Windows; U; Windows NT 5.1; cs-CZ) AppleWebKit/525.28.3 (KHTML, like Gecko) Version/3.2.3 Safari/525.29"; - private static final String SAFARI4_MAC = "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_5_8; en-us) AppleWebKit/531.22.7 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7"; - - private static final String IPHONE_IOS_5_1 = "Mozilla/5.0 (iPhone; CPU iPhone OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B179 Safari/7534.48.3"; - private static final String IPHONE_IOS_4_0 = "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7"; - private static final String IPAD_IOS_4_3_1 = "Mozilla/5.0 (iPad; U; CPU OS 4_3_1 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8G4 Safari/6533.18.5"; - - // application on the home screen, without Safari in user agent - private static final String IPHONE_IOS_6_1_HOMESCREEN_SIMULATOR = "Mozilla/5.0 (iPhone; CPU iPhone OS 6_1 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Mobile/10B141"; - - private static final String ANDROID_HTC_2_1 = "Mozilla/5.0 (Linux; U; Android 2.1-update1; en-us; ADR6300 Build/ERE27) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17"; - private static final String ANDROID_GOOGLE_NEXUS_2_2 = "Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"; - private static final String ANDROID_MOTOROLA_3_0 = "Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13"; - private static final String ANDROID_GALAXY_NEXUS_4_0_4_CHROME = "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"; - - private static final String EDGE_WINDOWS_10 = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"; - - @Test - public void testSafari3() { - VBrowserDetails bd = new VBrowserDetails(SAFARI3_WINDOWS); - assertWebKit(bd); - assertSafari(bd); - assertBrowserMajorVersion(bd, 3); - assertBrowserMinorVersion(bd, 2); - assertEngineVersion(bd, 525.0f); - assertWindows(bd); - } - - @Test - public void testSafari4() { - VBrowserDetails bd = new VBrowserDetails(SAFARI4_MAC); - assertWebKit(bd); - assertSafari(bd); - assertBrowserMajorVersion(bd, 4); - assertBrowserMinorVersion(bd, 0); - assertEngineVersion(bd, 531f); - assertMacOSX(bd); - } - - @Test - public void testIPhoneIOS6Homescreen() { - VBrowserDetails bd = new VBrowserDetails( - IPHONE_IOS_6_1_HOMESCREEN_SIMULATOR); - assertWebKit(bd); - // not identified as Safari, no browser version available - // assertSafari(bd); - // assertBrowserMajorVersion(bd, 6); - // assertBrowserMinorVersion(bd, 1); - assertEngineVersion(bd, 536f); - assertIOS(bd, 6, 1); - assertIPhone(bd); - } - - @Test - public void testIPhoneIOS5() { - VBrowserDetails bd = new VBrowserDetails(IPHONE_IOS_5_1); - assertWebKit(bd); - assertSafari(bd); - assertBrowserMajorVersion(bd, 5); - assertBrowserMinorVersion(bd, 1); - assertEngineVersion(bd, 534f); - assertIOS(bd, 5, 1); - assertIPhone(bd); - } - - @Test - public void testIPhoneIOS4() { - VBrowserDetails bd = new VBrowserDetails(IPHONE_IOS_4_0); - assertWebKit(bd); - assertSafari(bd); - assertBrowserMajorVersion(bd, 4); - assertBrowserMinorVersion(bd, 0); - assertEngineVersion(bd, 532f); - assertIOS(bd, 4, 0); - assertIPhone(bd); - } - - @Test - public void testIPadIOS4() { - VBrowserDetails bd = new VBrowserDetails(IPAD_IOS_4_3_1); - assertWebKit(bd); - assertSafari(bd); - assertBrowserMajorVersion(bd, 5); - assertBrowserMinorVersion(bd, 0); - assertEngineVersion(bd, 533f); - assertIOS(bd, 4, 3); - assertIPad(bd); - } - - @Test - public void testAndroid21() { - VBrowserDetails bd = new VBrowserDetails(ANDROID_HTC_2_1); - assertWebKit(bd); - assertSafari(bd); - assertBrowserMajorVersion(bd, 4); - assertBrowserMinorVersion(bd, 0); - assertEngineVersion(bd, 530f); - assertAndroid(bd, 2, 1); - - } - - @Test - public void testAndroid22() { - VBrowserDetails bd = new VBrowserDetails(ANDROID_GOOGLE_NEXUS_2_2); - assertWebKit(bd); - assertSafari(bd); - assertBrowserMajorVersion(bd, 4); - assertBrowserMinorVersion(bd, 0); - assertEngineVersion(bd, 533f); - assertAndroid(bd, 2, 2); - } - - @Test - public void testAndroid30() { - VBrowserDetails bd = new VBrowserDetails(ANDROID_MOTOROLA_3_0); - assertWebKit(bd); - assertSafari(bd); - assertBrowserMajorVersion(bd, 4); - assertBrowserMinorVersion(bd, 0); - assertEngineVersion(bd, 534f); - assertAndroid(bd, 3, 0); - } - - @Test - public void testAndroid40Chrome() { - VBrowserDetails bd = new VBrowserDetails( - ANDROID_GALAXY_NEXUS_4_0_4_CHROME); - assertWebKit(bd); - assertChrome(bd); - assertBrowserMajorVersion(bd, 18); - assertBrowserMinorVersion(bd, 0); - assertEngineVersion(bd, 535f); - assertAndroid(bd, 4, 0); - } - - private void assertOSMajorVersion(VBrowserDetails bd, int i) { - assertEquals(i, bd.getOperatingSystemMajorVersion()); - } - - private void assertOSMinorVersion(VBrowserDetails bd, int i) { - assertEquals(i, bd.getOperatingSystemMinorVersion()); - } - - @Test - public void testChrome3() { - VBrowserDetails bd = new VBrowserDetails(CHROME3_MAC); - assertWebKit(bd); - assertChrome(bd); - assertBrowserMajorVersion(bd, 3); - assertBrowserMinorVersion(bd, 0); - assertEngineVersion(bd, 532.0f); - assertMacOSX(bd); - } - - @Test - public void testChrome4() { - VBrowserDetails bd = new VBrowserDetails(CHROME4_WINDOWS); - assertWebKit(bd); - assertChrome(bd); - assertBrowserMajorVersion(bd, 4); - assertBrowserMinorVersion(bd, 0); - assertEngineVersion(bd, 532f); - assertWindows(bd); - } - - @Test - public void testFirefox3() { - VBrowserDetails bd = new VBrowserDetails(FIREFOX30_WINDOWS); - assertGecko(bd); - assertFirefox(bd); - assertBrowserMajorVersion(bd, 3); - assertBrowserMinorVersion(bd, 0); - assertEngineVersion(bd, 1.9f); - assertWindows(bd); - - bd = new VBrowserDetails(FIREFOX30_LINUX); - assertGecko(bd); - assertFirefox(bd); - assertBrowserMajorVersion(bd, 3); - assertBrowserMinorVersion(bd, 0); - assertEngineVersion(bd, 1.9f); - assertLinux(bd); - } - - @Test - public void testFirefox33Android() { - VBrowserDetails bd = new VBrowserDetails(FIREFOX33_ANDROID); - assertGecko(bd); - assertFirefox(bd); - assertBrowserMajorVersion(bd, 33); - assertBrowserMinorVersion(bd, 0); - assertAndroid(bd, -1, -1); - } - - @Test - public void testFirefox35() { - VBrowserDetails bd = new VBrowserDetails(FIREFOX35_WINDOWS); - assertGecko(bd); - assertFirefox(bd); - assertBrowserMajorVersion(bd, 3); - assertBrowserMinorVersion(bd, 5); - assertEngineVersion(bd, 1.9f); - assertWindows(bd); - } - - @Test - public void testFirefox36() { - VBrowserDetails bd = new VBrowserDetails(FIREFOX36_WINDOWS); - assertGecko(bd); - assertFirefox(bd); - assertBrowserMajorVersion(bd, 3); - assertBrowserMinorVersion(bd, 6); - assertEngineVersion(bd, 1.9f); - assertWindows(bd); - } - - @Test - public void testFirefox30b5() { - VBrowserDetails bd = new VBrowserDetails(FIREFOX_30B5_MAC); - assertGecko(bd); - assertFirefox(bd); - assertBrowserMajorVersion(bd, 3); - assertBrowserMinorVersion(bd, 0); - assertEngineVersion(bd, 1.9f); - assertMacOSX(bd); - } - - @Test - public void testFirefox40b11() { - VBrowserDetails bd = new VBrowserDetails(FIREFOX_40B11_WIN); - assertGecko(bd); - assertFirefox(bd); - assertBrowserMajorVersion(bd, 4); - assertBrowserMinorVersion(bd, 0); - assertEngineVersion(bd, 2.0f); - assertWindows(bd); - } - - @Test - public void testFirefox40b7() { - VBrowserDetails bd = new VBrowserDetails(FIREFOX_40B7_WIN); - assertGecko(bd); - assertFirefox(bd); - assertBrowserMajorVersion(bd, 4); - assertBrowserMinorVersion(bd, 0); - assertEngineVersion(bd, 2.0f); - assertWindows(bd); - } - - @Test - public void testKonquerorLinux() { - // Just ensure detection does not crash - VBrowserDetails bd = new VBrowserDetails(KONQUEROR_LINUX); - assertLinux(bd); - } - - @Test - public void testFirefox36b() { - VBrowserDetails bd = new VBrowserDetails(FIREFOX36B_MAC); - assertGecko(bd); - assertFirefox(bd); - assertBrowserMajorVersion(bd, 3); - assertBrowserMinorVersion(bd, 6); - assertEngineVersion(bd, 1.9f); - assertMacOSX(bd); - } - - @Test - public void testOpera964() { - VBrowserDetails bd = new VBrowserDetails(OPERA964_WINDOWS); - assertPresto(bd); - assertOpera(bd); - assertBrowserMajorVersion(bd, 9); - assertBrowserMinorVersion(bd, 64); - assertWindows(bd); - } - - @Test - public void testOpera1010() { - VBrowserDetails bd = new VBrowserDetails(OPERA1010_WINDOWS); - assertPresto(bd); - assertOpera(bd); - assertBrowserMajorVersion(bd, 10); - assertBrowserMinorVersion(bd, 10); - assertWindows(bd); - } - - @Test - public void testOpera1050() { - VBrowserDetails bd = new VBrowserDetails(OPERA1050_WINDOWS); - assertPresto(bd); - assertOpera(bd); - assertBrowserMajorVersion(bd, 10); - assertBrowserMinorVersion(bd, 50); - assertWindows(bd); - } - - @Test - public void testIE6() { - VBrowserDetails bd = new VBrowserDetails(IE6_WINDOWS); - assertEngineVersion(bd, -1); - assertIE(bd); - assertBrowserMajorVersion(bd, 6); - assertBrowserMinorVersion(bd, 0); - assertWindows(bd); - } - - @Test - public void testIE7() { - VBrowserDetails bd = new VBrowserDetails(IE7_WINDOWS); - assertEngineVersion(bd, -1); - assertIE(bd); - assertBrowserMajorVersion(bd, 7); - assertBrowserMinorVersion(bd, 0); - assertWindows(bd); - } - - @Test - public void testIE8() { - VBrowserDetails bd = new VBrowserDetails(IE8_WINDOWS); - assertTrident(bd); - assertEngineVersion(bd, 4); - assertIE(bd); - assertBrowserMajorVersion(bd, 8); - assertBrowserMinorVersion(bd, 0); - assertWindows(bd); - } - - @Test - public void testIE8CompatibilityMode() { - VBrowserDetails bd = new VBrowserDetails(IE8_IN_IE7_MODE_WINDOWS); - bd.setIEMode(7); - - assertTrident(bd); - assertEngineVersion(bd, 4); - assertIE(bd); - assertBrowserMajorVersion(bd, 7); - assertBrowserMinorVersion(bd, 0); - - assertWindows(bd); - } - - @Test - public void testIE9() { - VBrowserDetails bd = new VBrowserDetails(IE9_BETA_WINDOWS_7); - assertTrident(bd); - assertEngineVersion(bd, 5); - assertIE(bd); - assertBrowserMajorVersion(bd, 9); - assertBrowserMinorVersion(bd, 0); - assertWindows(bd); - } - - @Test - public void testIE9InIE7CompatibilityMode() { - VBrowserDetails bd = new VBrowserDetails(IE9_IN_IE7_MODE_WINDOWS_7); - // bd.setIE8InCompatibilityMode(); - - assertTrident(bd); - assertEngineVersion(bd, 5); - assertIE(bd); - assertBrowserMajorVersion(bd, 7); - assertBrowserMinorVersion(bd, 0); - - assertWindows(bd); - } - - @Test - public void testIE9InIE8CompatibilityMode() { - VBrowserDetails bd = new VBrowserDetails(IE9_BETA_IN_IE8_MODE_WINDOWS_7); - // bd.setIE8InCompatibilityMode(); - - /* - * Trident/4.0 in example user agent string based on beta even though it - * should be Trident/5.0 in real (non-beta) user agent strings - */ - assertTrident(bd); - assertEngineVersion(bd, 4); - assertIE(bd); - assertBrowserMajorVersion(bd, 8); - assertBrowserMinorVersion(bd, 0); - - assertWindows(bd); - } - - @Test - public void testIE10() { - VBrowserDetails bd = new VBrowserDetails(IE10_WINDOWS_8); - assertTrident(bd); - assertEngineVersion(bd, 6); - assertIE(bd); - assertBrowserMajorVersion(bd, 10); - assertBrowserMinorVersion(bd, 0); - assertWindows(bd); - } - - @Test - public void testIE11() { - VBrowserDetails bd = new VBrowserDetails(IE11_WINDOWS_7); - assertTrident(bd); - assertEngineVersion(bd, 7); - assertIE(bd); - assertBrowserMajorVersion(bd, 11); - assertBrowserMinorVersion(bd, 0); - assertWindows(bd); - } - - @Test - public void testIE11WindowsPhone81Update() { - VBrowserDetails bd = new VBrowserDetails(IE11_WINDOWS_PHONE_8_1_UPDATE); - assertTrident(bd); - assertEngineVersion(bd, 7); - assertIE(bd); - assertBrowserMajorVersion(bd, 11); - assertBrowserMinorVersion(bd, 0); - assertWindows(bd, true); - } - - @Test - public void testEdgeWindows10() { - VBrowserDetails bd = new VBrowserDetails(EDGE_WINDOWS_10); - assertEdge(bd); - assertBrowserMajorVersion(bd, 12); - assertBrowserMinorVersion(bd, 10240); - assertWindows(bd, false); - } - - /* - * Helper methods below - */ - - private void assertEngineVersion(VBrowserDetails browserDetails, - float version) { - assertEquals(version, browserDetails.getBrowserEngineVersion(), 0.01d); - - } - - private void assertBrowserMajorVersion(VBrowserDetails browserDetails, - int version) { - assertEquals(version, browserDetails.getBrowserMajorVersion()); - - } - - private void assertBrowserMinorVersion(VBrowserDetails browserDetails, - int version) { - assertEquals(version, browserDetails.getBrowserMinorVersion()); - - } - - private void assertGecko(VBrowserDetails browserDetails) { - // Engine - assertTrue(browserDetails.isGecko()); - assertFalse(browserDetails.isWebKit()); - assertFalse(browserDetails.isPresto()); - assertFalse(browserDetails.isTrident()); - } - - private void assertPresto(VBrowserDetails browserDetails) { - // Engine - assertFalse(browserDetails.isGecko()); - assertFalse(browserDetails.isWebKit()); - assertTrue(browserDetails.isPresto()); - assertFalse(browserDetails.isTrident()); - } - - private void assertTrident(VBrowserDetails browserDetails) { - // Engine - assertFalse(browserDetails.isGecko()); - assertFalse(browserDetails.isWebKit()); - assertFalse(browserDetails.isPresto()); - assertTrue(browserDetails.isTrident()); - } - - private void assertWebKit(VBrowserDetails browserDetails) { - // Engine - assertFalse(browserDetails.isGecko()); - assertTrue(browserDetails.isWebKit()); - assertFalse(browserDetails.isPresto()); - assertFalse(browserDetails.isTrident()); - } - - private void assertFirefox(VBrowserDetails browserDetails) { - // Browser - assertTrue(browserDetails.isFirefox()); - assertFalse(browserDetails.isChrome()); - assertFalse(browserDetails.isIE()); - assertFalse(browserDetails.isOpera()); - assertFalse(browserDetails.isSafari()); - assertFalse(browserDetails.isEdge()); - } - - private void assertChrome(VBrowserDetails browserDetails) { - // Browser - assertFalse(browserDetails.isFirefox()); - assertTrue(browserDetails.isChrome()); - assertFalse(browserDetails.isIE()); - assertFalse(browserDetails.isOpera()); - assertFalse(browserDetails.isSafari()); - assertFalse(browserDetails.isEdge()); - } - - private void assertIE(VBrowserDetails browserDetails) { - // Browser - assertFalse(browserDetails.isFirefox()); - assertFalse(browserDetails.isChrome()); - assertTrue(browserDetails.isIE()); - assertFalse(browserDetails.isOpera()); - assertFalse(browserDetails.isSafari()); - assertFalse(browserDetails.isEdge()); - } - - private void assertOpera(VBrowserDetails browserDetails) { - // Browser - assertFalse(browserDetails.isFirefox()); - assertFalse(browserDetails.isChrome()); - assertFalse(browserDetails.isIE()); - assertTrue(browserDetails.isOpera()); - assertFalse(browserDetails.isSafari()); - assertFalse(browserDetails.isEdge()); - } - - private void assertSafari(VBrowserDetails browserDetails) { - // Browser - assertFalse(browserDetails.isFirefox()); - assertFalse(browserDetails.isChrome()); - assertFalse(browserDetails.isIE()); - assertFalse(browserDetails.isOpera()); - assertTrue(browserDetails.isSafari()); - assertFalse(browserDetails.isEdge()); - } - - private void assertEdge(VBrowserDetails browserDetails) { - // Browser - assertFalse(browserDetails.isFirefox()); - assertFalse(browserDetails.isChrome()); - assertFalse(browserDetails.isIE()); - assertFalse(browserDetails.isOpera()); - assertFalse(browserDetails.isSafari()); - assertTrue(browserDetails.isEdge()); - } - - private void assertMacOSX(VBrowserDetails browserDetails) { - assertFalse(browserDetails.isLinux()); - assertFalse(browserDetails.isWindows()); - assertTrue(browserDetails.isMacOSX()); - assertFalse(browserDetails.isAndroid()); - } - - private void assertAndroid(VBrowserDetails browserDetails, - int majorVersion, int minorVersion) { - assertFalse(browserDetails.isLinux()); - assertFalse(browserDetails.isWindows()); - assertFalse(browserDetails.isMacOSX()); - assertFalse(browserDetails.isIOS()); - assertTrue(browserDetails.isAndroid()); - - assertOSMajorVersion(browserDetails, majorVersion); - assertOSMinorVersion(browserDetails, minorVersion); - } - - private void assertIOS(VBrowserDetails browserDetails, int majorVersion, - int minorVersion) { - assertFalse(browserDetails.isLinux()); - assertFalse(browserDetails.isWindows()); - assertFalse(browserDetails.isMacOSX()); - assertTrue(browserDetails.isIOS()); - assertFalse(browserDetails.isAndroid()); - - assertOSMajorVersion(browserDetails, majorVersion); - assertOSMinorVersion(browserDetails, minorVersion); - } - - private void assertIPhone(VBrowserDetails browserDetails) { - assertTrue(browserDetails.isIPhone()); - assertFalse(browserDetails.isIPad()); - } - - private void assertIPad(VBrowserDetails browserDetails) { - assertFalse(browserDetails.isIPhone()); - assertTrue(browserDetails.isIPad()); - } - - private void assertWindows(VBrowserDetails browserDetails) { - assertWindows(browserDetails, false); - } - - private void assertWindows(VBrowserDetails browserDetails, - boolean isWindowsPhone) { - assertFalse(browserDetails.isLinux()); - assertTrue(browserDetails.isWindows()); - assertFalse(browserDetails.isMacOSX()); - assertFalse(browserDetails.isIOS()); - assertFalse(browserDetails.isAndroid()); - Assert.assertEquals(isWindowsPhone, browserDetails.isWindowsPhone()); - } - - private void assertLinux(VBrowserDetails browserDetails) { - assertTrue(browserDetails.isLinux()); - assertFalse(browserDetails.isWindows()); - assertFalse(browserDetails.isMacOSX()); - assertFalse(browserDetails.isIOS()); - assertFalse(browserDetails.isAndroid()); - } - -} diff --git a/client/tests/src/com/vaadin/client/communication/ServerMessageHandlerTest.java b/client/tests/src/com/vaadin/client/communication/ServerMessageHandlerTest.java deleted file mode 100644 index c2752f1953..0000000000 --- a/client/tests/src/com/vaadin/client/communication/ServerMessageHandlerTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.communication; - -import org.junit.Assert; -import org.junit.Test; - -/** - * - * @since - * @author Vaadin Ltd - */ -public class ServerMessageHandlerTest { - - @Test - public void unwrapValidJson() { - String payload = "{'foo': 'bar'}"; - Assert.assertEquals(payload, - MessageHandler.stripJSONWrapping("for(;;);[" + payload + "]")); - - } - - @Test - public void unwrapUnwrappedJson() { - String payload = "{'foo': 'bar'}"; - Assert.assertNull(MessageHandler.stripJSONWrapping(payload)); - - } - - @Test - public void unwrapNull() { - Assert.assertNull(MessageHandler.stripJSONWrapping(null)); - - } - - @Test - public void unwrapEmpty() { - Assert.assertNull(MessageHandler.stripJSONWrapping("")); - - } -} diff --git a/client/tests/src/com/vaadin/client/ui/grid/ListDataSourceTest.java b/client/tests/src/com/vaadin/client/ui/grid/ListDataSourceTest.java deleted file mode 100644 index 24ccd6c57e..0000000000 --- a/client/tests/src/com/vaadin/client/ui/grid/ListDataSourceTest.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.client.ui.grid; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; -import java.util.Comparator; - -import org.easymock.EasyMock; -import org.junit.Test; - -import com.vaadin.client.data.DataChangeHandler; -import com.vaadin.client.widget.grid.datasources.ListDataSource; - -public class ListDataSourceTest { - - @Test - public void testDataSourceConstruction() throws Exception { - - ListDataSource ds = new ListDataSource(0, 1, 2, 3); - - assertEquals(4, ds.size()); - assertEquals(0, (int) ds.getRow(0)); - assertEquals(1, (int) ds.getRow(1)); - assertEquals(2, (int) ds.getRow(2)); - assertEquals(3, (int) ds.getRow(3)); - - ds = new ListDataSource(Arrays.asList(0, 1, 2, 3)); - - assertEquals(4, ds.size()); - assertEquals(0, (int) ds.getRow(0)); - assertEquals(1, (int) ds.getRow(1)); - assertEquals(2, (int) ds.getRow(2)); - assertEquals(3, (int) ds.getRow(3)); - } - - @Test - public void testListAddOperation() throws Exception { - - ListDataSource ds = new ListDataSource(0, 1, 2, 3); - - DataChangeHandler handler = EasyMock - .createNiceMock(DataChangeHandler.class); - ds.setDataChangeHandler(handler); - - handler.dataAdded(4, 1); - EasyMock.expectLastCall(); - - EasyMock.replay(handler); - - ds.asList().add(4); - - assertEquals(5, ds.size()); - assertEquals(0, (int) ds.getRow(0)); - assertEquals(1, (int) ds.getRow(1)); - assertEquals(2, (int) ds.getRow(2)); - assertEquals(3, (int) ds.getRow(3)); - assertEquals(4, (int) ds.getRow(4)); - } - - @Test - public void testListAddAllOperation() throws Exception { - - ListDataSource ds = new ListDataSource(0, 1, 2, 3); - - DataChangeHandler handler = EasyMock - .createNiceMock(DataChangeHandler.class); - ds.setDataChangeHandler(handler); - - handler.dataAdded(4, 3); - EasyMock.expectLastCall(); - - EasyMock.replay(handler); - - ds.asList().addAll(Arrays.asList(4, 5, 6)); - - assertEquals(7, ds.size()); - assertEquals(0, (int) ds.getRow(0)); - assertEquals(1, (int) ds.getRow(1)); - assertEquals(2, (int) ds.getRow(2)); - assertEquals(3, (int) ds.getRow(3)); - assertEquals(4, (int) ds.getRow(4)); - assertEquals(5, (int) ds.getRow(5)); - assertEquals(6, (int) ds.getRow(6)); - } - - @Test - public void testListRemoveOperation() throws Exception { - - ListDataSource ds = new ListDataSource(0, 1, 2, 3); - - DataChangeHandler handler = EasyMock - .createNiceMock(DataChangeHandler.class); - ds.setDataChangeHandler(handler); - - handler.dataRemoved(3, 1); - EasyMock.expectLastCall(); - - EasyMock.replay(handler); - - ds.asList().remove(2); - - assertEquals(3, ds.size()); - assertEquals(0, (int) ds.getRow(0)); - assertEquals(1, (int) ds.getRow(1)); - assertEquals(3, (int) ds.getRow(2)); - } - - @Test - public void testListRemoveAllOperation() throws Exception { - - ListDataSource ds = new ListDataSource(0, 1, 2, 3); - - DataChangeHandler handler = EasyMock - .createNiceMock(DataChangeHandler.class); - ds.setDataChangeHandler(handler); - - handler.dataRemoved(0, 3); - EasyMock.expectLastCall(); - - EasyMock.replay(handler); - - ds.asList().removeAll(Arrays.asList(0, 2, 3)); - - assertEquals(1, ds.size()); - assertEquals(1, (int) ds.getRow(0)); - } - - @Test - public void testListClearOperation() throws Exception { - - ListDataSource ds = new ListDataSource(0, 1, 2, 3); - - DataChangeHandler handler = EasyMock - .createNiceMock(DataChangeHandler.class); - ds.setDataChangeHandler(handler); - - handler.dataRemoved(0, 4); - EasyMock.expectLastCall(); - - EasyMock.replay(handler); - - ds.asList().clear(); - - assertEquals(0, ds.size()); - } - - @Test(expected = IllegalStateException.class) - public void testFetchingNonExistantItem() { - ListDataSource ds = new ListDataSource(0, 1, 2, 3); - ds.ensureAvailability(5, 1); - } - - @Test(expected = UnsupportedOperationException.class) - public void testUnsupportedIteratorRemove() { - ListDataSource ds = new ListDataSource(0, 1, 2, 3); - ds.asList().iterator().remove(); - } - - @Test - public void sortColumn() { - ListDataSource ds = new ListDataSource(3, 4, 2, 3, 1); - - // TODO Should be simplified to sort(). No point in providing these - // trivial comparators. - ds.sort(new Comparator() { - @Override - public int compare(Integer o1, Integer o2) { - return o1.compareTo(o2); - } - }); - - assertTrue(Arrays.equals(ds.asList().toArray(), new Integer[] { 1, 2, - 3, 3, 4 })); - } - -} diff --git a/client/tests/src/com/vaadin/client/ui/grid/PartitioningTest.java b/client/tests/src/com/vaadin/client/ui/grid/PartitioningTest.java deleted file mode 100644 index e97bb339e4..0000000000 --- a/client/tests/src/com/vaadin/client/ui/grid/PartitioningTest.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2000-2013 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.client.ui.grid; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -import com.vaadin.shared.ui.grid.Range; - -@SuppressWarnings("static-method") -public class PartitioningTest { - - @Test - public void selfRangeTest() { - final Range range = Range.between(0, 10); - final Range[] partitioning = range.partitionWith(range); - - assertTrue("before is empty", partitioning[0].isEmpty()); - assertTrue("inside is self", partitioning[1].equals(range)); - assertTrue("after is empty", partitioning[2].isEmpty()); - } - - @Test - public void beforeRangeTest() { - final Range beforeRange = Range.between(0, 10); - final Range afterRange = Range.between(10, 20); - final Range[] partitioning = beforeRange.partitionWith(afterRange); - - assertTrue("before is self", partitioning[0].equals(beforeRange)); - assertTrue("inside is empty", partitioning[1].isEmpty()); - assertTrue("after is empty", partitioning[2].isEmpty()); - } - - @Test - public void afterRangeTest() { - final Range beforeRange = Range.between(0, 10); - final Range afterRange = Range.between(10, 20); - final Range[] partitioning = afterRange.partitionWith(beforeRange); - - assertTrue("before is empty", partitioning[0].isEmpty()); - assertTrue("inside is empty", partitioning[1].isEmpty()); - assertTrue("after is self", partitioning[2].equals(afterRange)); - } - - @Test - public void beforeAndInsideRangeTest() { - final Range beforeRange = Range.between(0, 10); - final Range afterRange = Range.between(5, 15); - final Range[] partitioning = beforeRange.partitionWith(afterRange); - - assertEquals("before", Range.between(0, 5), partitioning[0]); - assertEquals("inside", Range.between(5, 10), partitioning[1]); - assertTrue("after is empty", partitioning[2].isEmpty()); - } - - @Test - public void insideRangeTest() { - final Range fullRange = Range.between(0, 20); - final Range insideRange = Range.between(5, 15); - final Range[] partitioning = insideRange.partitionWith(fullRange); - - assertTrue("before is empty", partitioning[0].isEmpty()); - assertEquals("inside", Range.between(5, 15), partitioning[1]); - assertTrue("after is empty", partitioning[2].isEmpty()); - } - - @Test - public void insideAndBelowTest() { - final Range beforeRange = Range.between(0, 10); - final Range afterRange = Range.between(5, 15); - final Range[] partitioning = afterRange.partitionWith(beforeRange); - - assertTrue("before is empty", partitioning[0].isEmpty()); - assertEquals("inside", Range.between(5, 10), partitioning[1]); - assertEquals("after", Range.between(10, 15), partitioning[2]); - } - - @Test - public void aboveAndBelowTest() { - final Range fullRange = Range.between(0, 20); - final Range insideRange = Range.between(5, 15); - final Range[] partitioning = fullRange.partitionWith(insideRange); - - assertEquals("before", Range.between(0, 5), partitioning[0]); - assertEquals("inside", Range.between(5, 15), partitioning[1]); - assertEquals("after", Range.between(15, 20), partitioning[2]); - } -} diff --git a/ivysettings.xml b/ivysettings.xml index 0dae777b2c..8afc679af9 100644 --- a/ivysettings.xml +++ b/ivysettings.xml @@ -44,7 +44,7 @@ + resolver="local-maven" /> shared push server + client diff --git a/uitest/ivy.xml b/uitest/ivy.xml index 0f2141e00b..9c4d5025bd 100644 --- a/uitest/ivy.xml +++ b/uitest/ivy.xml @@ -41,7 +41,8 @@ + rev="${vaadin.version}" conf="build->default"> + + rev="${vaadin.version}" conf="build-provided,test->default">