Browse Source

[merge from 6.7] Make ClickEventHandler fire clicks only if mousedown and mouseup match (#4120)

svn changeset:23846/svn branch:6.8
tags/7.0.0.alpha3
Automerge 12 years ago
parent
commit
616387587d

+ 16
- 0
src/com/vaadin/terminal/gwt/client/Util.java View File

@@ -1156,6 +1156,22 @@ public class Util {
}
}

/**
* Find the element corresponding to the coordinates in the passed mouse
* event. Please note that this is not always the same as the target of the
* element e.g. if event capture is used.
*
* @param event
* the mouse event to get coordinates from
* @return the element at the coordinates of the event
*/
public static Element getElementUnderMouse(NativeEvent event) {
int pageX = getTouchOrMouseClientX(event);
int pageY = getTouchOrMouseClientY(event);

return getElementFromPoint(pageX, pageY);
}

/**
* A helper method to return the client position from an event. Returns
* position from either first changed touch (if touch event) or from the

+ 74
- 5
src/com/vaadin/terminal/gwt/client/ui/ClickEventHandler.java View File

@@ -6,33 +6,79 @@ package com.vaadin.terminal.gwt.client.ui;
import java.util.HashMap;
import java.util.Map;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.ContextMenuEvent;
import com.google.gwt.event.dom.client.ContextMenuHandler;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
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.shared.EventHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Element;
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.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.MouseEventDetails;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.Util;

public abstract class ClickEventHandler implements DoubleClickHandler,
ContextMenuHandler, MouseUpHandler {
ContextMenuHandler, MouseUpHandler, MouseDownHandler {

private HandlerRegistration doubleClickHandlerRegistration;
private HandlerRegistration mouseUpHandlerRegistration;
private HandlerRegistration mouseDownHandlerRegistration;
private HandlerRegistration contextMenuHandlerRegistration;

protected String clickEventIdentifier;
protected Paintable paintable;
private ApplicationConnection client;

/**
* The element where the last mouse down event was registered.
*/
private JavaScriptObject 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() {
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 = Util.getElementUnderMouse(event
.getNativeEvent());
if (lastMouseDownTarget != null
&& elementUnderMouse.cast() == lastMouseDownTarget) {
mouseUpPreviewMatched = true;
} else {
System.out.println("Ignoring mouseup from "
+ elementUnderMouse + " when mousedown was on "
+ lastMouseDownTarget);
}
}
}
};

public ClickEventHandler(Paintable paintable, String clickEventIdentifier) {
this.paintable = paintable;
this.clickEventIdentifier = clickEventIdentifier;
@@ -46,6 +92,8 @@ public abstract class ClickEventHandler implements DoubleClickHandler,
if (mouseUpHandlerRegistration == null) {
mouseUpHandlerRegistration = registerHandler(this,
MouseUpEvent.getType());
mouseDownHandlerRegistration = registerHandler(this,
MouseDownEvent.getType());
contextMenuHandlerRegistration = registerHandler(this,
ContextMenuEvent.getType());
doubleClickHandlerRegistration = registerHandler(this,
@@ -56,10 +104,12 @@ public abstract class ClickEventHandler implements DoubleClickHandler,
// Remove existing handlers
doubleClickHandlerRegistration.removeHandler();
mouseUpHandlerRegistration.removeHandler();
mouseDownHandlerRegistration.removeHandler();
contextMenuHandlerRegistration.removeHandler();

contextMenuHandlerRegistration = null;
mouseUpHandlerRegistration = null;
mouseDownHandlerRegistration = null;
doubleClickHandlerRegistration = null;

}
@@ -101,14 +151,33 @@ public abstract class ClickEventHandler implements DoubleClickHandler,

}

public void onMouseDown(MouseDownEvent event) {
/*
* When getting a mousedown event, we must detect where the
* corresponding mouseup event if it's on a different part of the page.
*/
lastMouseDownTarget = event.getNativeEvent().getEventTarget();
mouseUpPreviewMatched = false;
mouseUpEventPreviewRegistration = Event
.addNativePreviewHandler(mouseUpPreviewHandler);
}

public void onMouseUp(MouseUpEvent event) {
// TODO For perfect accuracy we should check that a mousedown has
// occured on this element before this mouseup and that no mouseup
// has occured anywhere after that.
if (hasEventListener()) {
/*
* 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 even there as the event might get canceled
* before it gets here.
*/
if (hasEventListener()
&& mouseUpPreviewMatched
&& lastMouseDownTarget != null
&& Util.getElementUnderMouse(event.getNativeEvent()) == lastMouseDownTarget) {
// "Click" with left, right or middle button
fireClick(event.getNativeEvent());
}
mouseUpPreviewMatched = false;
lastMouseDownTarget = null;
}

public void onDoubleClick(DoubleClickEvent event) {

+ 14
- 15
tests/testbench/com/vaadin/tests/components/orderedlayout/LayoutClickListenerTest.html View File

@@ -19,74 +19,73 @@
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestscomponentsorderedlayoutLayoutClickListenerTest::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[0]/VTextField[0]</td>
<td></td>
<td>5,5</td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestscomponentsorderedlayoutLayoutClickListenerTest::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[1]/VLink[0]/domChild[0]/domChild[0]</td>
<td></td>
<td>5,5</td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestscomponentsorderedlayoutLayoutClickListenerTest::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[2]/VFilterSelect[0]/domChild[0]</td>
<td></td>
<td>5,5</td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestscomponentsorderedlayoutLayoutClickListenerTest::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[3]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]</td>
<td></td>
<td>5,5</td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestscomponentsorderedlayoutLayoutClickListenerTest::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[3]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]/ChildComponentContainer[0]/VLabel[0]</td>
<td></td>
<td>5,5</td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestscomponentsorderedlayoutLayoutClickListenerTest::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[3]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]/ChildComponentContainer[1]/VTextField[0]</td>
<td></td>
<td>5,5</td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestscomponentsorderedlayoutLayoutClickListenerTest::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[3]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]/ChildComponentContainer[2]/VLink[0]/domChild[0]/domChild[0]</td>
<td></td>
<td>5,5</td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestscomponentsorderedlayoutLayoutClickListenerTest::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[3]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]/domChild[0]/domChild[1]</td>
<td></td>
<td>vaadin=runcomvaadintestscomponentsorderedlayoutLayoutClickListenerTest::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[3]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]</td>
<td>396,87</td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestscomponentsorderedlayoutLayoutClickListenerTest::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[4]/VVerticalLayout[0]/ChildComponentContainer[0]/VLabel[0]</td>
<td></td>
<td>5,5</td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestscomponentsorderedlayoutLayoutClickListenerTest::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[4]/VVerticalLayout[0]/ChildComponentContainer[1]/VTextField[0]</td>
<td></td>
<td>5,5</td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestscomponentsorderedlayoutLayoutClickListenerTest::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[4]/VVerticalLayout[0]/ChildComponentContainer[2]/VLink[0]/domChild[0]/domChild[0]</td>
<td></td>
<td>5,5</td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestscomponentsorderedlayoutLayoutClickListenerTest::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[4]/VVerticalLayout[0]/ChildComponentContainer[3]/VHorizontalLayout[0]/ChildComponentContainer[0]/VLabel[0]</td>
<td></td>
<td>5,5</td>
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestscomponentsorderedlayoutLayoutClickListenerTest::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VGridLayout[0]/domChild[0]/domChild[0]</td>
<td></td>
<td>1135,146</td>
</tr>
<tr>
<td>screenCapture</td>
<td></td>
<td>everything_clicked</td>
</tr>

</tbody></table>
</body>
</html>

+ 3
- 3
tests/testbench/com/vaadin/tests/components/panel/PanelClickListenerRelativeCoordinates.html View File

@@ -28,10 +28,10 @@
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestscomponentspanelPanelClickListenerRelativeCoordinates::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VPanel[0]</td>
<td>287,25</td>
<td>vaadin=runcomvaadintestscomponentspanelPanelClickListenerRelativeCoordinates::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VPanel[0]/VVerticalLayout[0]</td>
<td>287,6</td>
</tr>
<!-- Should really be 287, 25 but due to v-view border-top it is not always... <tr>
<!-- Should really be 287, 25 but due to v-view border-top it is not always... Additional differences caused by targeting the click on the child element to make it behave like a real click (#4120)<tr>
<td>assertText</td>
<td>vaadin=runcomvaadintestscomponentspanelPanelClickListenerRelativeCoordinates::Root/VNotification[0]/HTML[0]/domChild[0]</td>
<td>287, 25</td>

+ 126
- 2
tests/testbench/com/vaadin/tests/layouts/TestLayoutClickListeners.html View File

@@ -59,8 +59,8 @@
</tr>
<tr>
<td>mouseClick</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[0]/VVerticalLayout[0]/ChildComponentContainer[1]/VGridLayout[0]/domChild[0]/domChild[0]/domChild[1]</td>
<td>87,42</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[0]/VVerticalLayout[0]/ChildComponentContainer[1]/VGridLayout[0]/domChild[0]/domChild[0]</td>
<td>491,79</td>
</tr>
<tr>
<td>waitForVaadin</td>
@@ -185,6 +185,130 @@
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[0]/VVerticalLayout[0]/ChildComponentContainer[0]/VLabel[0]</td>
<td>exact:Button A button with its own click listener was clicked</td>
</tr>
<!--Drag in GridLayout-->
<tr>
<td>drag</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[0]/VVerticalLayout[0]/ChildComponentContainer[1]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[0]/VLabel[0]</td>
<td>40,8</td>
</tr>
<tr>
<td>drop</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[0]/VVerticalLayout[0]/ChildComponentContainer[1]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[0]/VLabel[0]</td>
<td>40,8</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::PID_SLog_row_0</td>
<td>exact:GridLayout: left click on This is label 1</td>
</tr>
<tr>
<td>drag</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[0]/VVerticalLayout[0]/ChildComponentContainer[1]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[0]/VLabel[0]</td>
<td>24,7</td>
</tr>
<tr>
<td>drop</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[0]/VVerticalLayout[0]/ChildComponentContainer[1]/VGridLayout[0]/AbsolutePanel[0]/ChildComponentContainer[4]/VTextField[0]</td>
<td>46,13</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::PID_SLog_row_0</td>
<td>exact:GridLayout: left click on This is label 1</td>
</tr>
<!--Drag in VerticalLayout-->
<tr>
<td>drag</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTextField[0]</td>
<td>25,9</td>
</tr>
<tr>
<td>drop</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTextField[0]</td>
<td>25,9</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::PID_SLog_row_0</td>
<td>exact:VerticalLayout: left click on This is tf5</td>
</tr>
<tr>
<td>drag</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/domChild[0]/domChild[8]/domChild[0]/domChild[0]</td>
<td>28,11</td>
</tr>
<tr>
<td>drop</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[8]/VTextField[0]</td>
<td>39,7</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::PID_SLog_row_0</td>
<td>exact:VerticalLayout: left click on This is tf5</td>
</tr>
<!--Drag in AbsoluteLayout-->
<tr>
<td>drag</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[2]/VAbsoluteLayout[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]</td>
<td>21,9</td>
</tr>
<tr>
<td>drop</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[2]/VAbsoluteLayout[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]</td>
<td>21,9</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::PID_SLog_row_0</td>
<td>exact:AbsoluteLayout: left click on This is its caption</td>
</tr>
<tr>
<td>drag</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[2]/VAbsoluteLayout[0]/VAbsoluteLayout$AbsoluteWrapper[0]/VTextField[0]</td>
<td>54,7</td>
</tr>
<tr>
<td>drop</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[2]/VAbsoluteLayout[0]/VAbsoluteLayout$AbsoluteWrapper[1]/VTextField[0]</td>
<td>52,10</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::PID_SLog_row_0</td>
<td>exact:AbsoluteLayout: left click on This is its caption</td>
</tr>
<!--Drag in CSSLayout-->
<tr>
<td>drag</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[3]/VCssLayout[0]/VCssLayout$FlowPane[0]/VTextField[0]</td>
<td>51,7</td>
</tr>
<tr>
<td>drop</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[3]/VCssLayout[0]/VCssLayout$FlowPane[0]/VTextField[0]</td>
<td>51,7</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::PID_SLog_row_0</td>
<td>exact:CSSLayout: left click on This is its caption</td>
</tr>
<tr>
<td>drag</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[3]/VCssLayout[0]/domChild[0]/domChild[0]/domChild[2]/domChild[0]</td>
<td>31,7</td>
</tr>
<tr>
<td>drop</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[3]/VCssLayout[0]/VCssLayout$FlowPane[0]/VTextField[1]</td>
<td>33,7</td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runcomvaadintestslayoutsTestLayoutClickListeners::PID_SLog_row_0</td>
<td>exact:CSSLayout: left click on This is its caption</td>
</tr>
</tbody></table>
</body>
</html>

Loading…
Cancel
Save