From 40cbef33e1c69f1c912d6a21968bcc8455476289 Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Fri, 29 Aug 2008 10:08:29 +0000 Subject: [PATCH] #2009, ItemClickEvents svn changeset:5298/svn branch:trunk --- .../itmill/toolkit/event/ItemClickEvent.java | 158 +++++++++++ .../gwt/client/MouseEventDetails.java | 92 +++++++ .../gwt/client/ui/IOrderedLayout.java | 6 +- .../terminal/gwt/client/ui/IPanel.java | 2 - .../terminal/gwt/client/ui/IScrollTable.java | 54 +++- .../toolkit/terminal/gwt/client/ui/ITree.java | 24 +- .../toolkit/tests/tickets/Ticket2009.java | 125 +++++++++ src/com/itmill/toolkit/ui/Table.java | 248 +++++++++++------- src/com/itmill/toolkit/ui/Tree.java | 42 ++- 9 files changed, 639 insertions(+), 112 deletions(-) create mode 100644 src/com/itmill/toolkit/event/ItemClickEvent.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/MouseEventDetails.java create mode 100644 src/com/itmill/toolkit/tests/tickets/Ticket2009.java diff --git a/src/com/itmill/toolkit/event/ItemClickEvent.java b/src/com/itmill/toolkit/event/ItemClickEvent.java new file mode 100644 index 0000000000..782aa990ca --- /dev/null +++ b/src/com/itmill/toolkit/event/ItemClickEvent.java @@ -0,0 +1,158 @@ +package com.itmill.toolkit.event; + +import java.lang.reflect.Method; + +import com.itmill.toolkit.data.Container; +import com.itmill.toolkit.data.Item; +import com.itmill.toolkit.data.Property; +import com.itmill.toolkit.terminal.gwt.client.MouseEventDetails; +import com.itmill.toolkit.ui.Component; +import com.itmill.toolkit.ui.Component.Event; + +/** + * + * Click event fired by a {@link Component} implementing + * {@link com.itmill.toolkit.data.Container} interface. ItemClickEvents happens + * on an {@link Item} rendered somehow on terminal. Event may also contain a + * specific {@link Property} on which the click event happened. + * + * ClickEvents are rather terminal dependent events. Correct values in event + * details cannot be guaranteed. + * + * EXPERIMENTAL FEATURE, user input is welcome + * + * @since 5.3 + * + * TODO extract generic super class/interfaces if we implement some other click + * events. + */ +public class ItemClickEvent extends Event { + public static final int BUTTON_LEFT = MouseEventDetails.BUTTON_LEFT; + public static final int BUTTON_MIDDLE = MouseEventDetails.BUTTON_MIDDLE; + public static final int BUTTON_RIGHT = MouseEventDetails.BUTTON_RIGHT; + + private MouseEventDetails details; + private Item item; + private Object itemId; + private Object propertyId; + + public ItemClickEvent(Component source, Item item, Object itemId, + Object propertyId, MouseEventDetails details) { + super(source); + this.details = details; + this.item = item; + this.itemId = itemId; + this.propertyId = propertyId; + } + + /** + * Gets the item on which the click event occurred. + * + * @return item which was clicked + */ + public Item getItem() { + return item; + } + + /** + * Gets a possible identifier in source for clicked Item + * + * @return + */ + public Object getItemId() { + return itemId; + } + + /** + * Returns property on which click event occurred. Returns null if source + * cannot be resolved at property leve. For example if clicked a cell in + * table, the "column id" is returned. + * + * @return a property id of clicked property or null if click didn't occur + * on any distinct property. + */ + public Object getPropertyId() { + return propertyId; + } + + public int getButton() { + return details.getButton(); + } + + public int getClientX() { + return details.getClientX(); + } + + public int getClientY() { + return details.getClientY(); + } + + public boolean isDoubleClick() { + return details.isDoubleClick(); + } + + public boolean isAltKey() { + return details.isAltKey(); + } + + public boolean isCtrlKey() { + return details.isCtrlKey(); + } + + public boolean isMetaKey() { + return details.isMetaKey(); + } + + public boolean isShiftKey() { + return details.isShiftKey(); + } + + /** + * Serial generated by eclipse + */ + private static final long serialVersionUID = 3576399524236787971L; + + public static final Method ITEM_CLICK_METHOD; + + static { + try { + ITEM_CLICK_METHOD = ItemClickListener.class.getDeclaredMethod( + "itemClick", new Class[] { ItemClickEvent.class }); + } catch (final java.lang.NoSuchMethodException e) { + // This should never happen + throw new java.lang.RuntimeException(); + } + } + + public interface ItemClickListener { + public void itemClick(ItemClickEvent event); + } + + /** + * Components implementing + * + * @link {@link Container} interface may support emitting + * {@link ItemClickEvent}s. + */ + public interface ItemClickSource { + /** + * Register listener to handle ItemClickEvents. + * + * Note! Click listeners are rather terminal dependent features. + * + * This feature is EXPERIMENTAL + * + * @param listener + * ItemClickListener to be registered + */ + public void addListener(ItemClickListener listener); + + /** + * Removes ItemClickListener. + * + * @param listener + */ + public void removeListener(ItemClickListener listener); + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/MouseEventDetails.java b/src/com/itmill/toolkit/terminal/gwt/client/MouseEventDetails.java new file mode 100644 index 0000000000..8d91863090 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/MouseEventDetails.java @@ -0,0 +1,92 @@ +package com.itmill.toolkit.terminal.gwt.client; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; + +/** + * Helper class to store and transfer mouse event details. + */ +public class MouseEventDetails { + public static final int BUTTON_LEFT = Event.BUTTON_LEFT; + public static final int BUTTON_MIDDLE = Event.BUTTON_MIDDLE; + public static final int BUTTON_RIGHT = Event.BUTTON_RIGHT; + + private static final char DELIM = ','; + + private int button; + private int clientX; + private int clientY; + private boolean altKey; + private boolean ctrlKey; + private boolean metaKey; + private boolean shiftKey; + private int type; + + public int getButton() { + return button; + } + + public int getClientX() { + return clientX; + } + + public int getClientY() { + return clientY; + } + + public boolean isAltKey() { + return altKey; + } + + public boolean isCtrlKey() { + return ctrlKey; + } + + public boolean isMetaKey() { + return metaKey; + } + + public boolean isShiftKey() { + return shiftKey; + } + + public MouseEventDetails(Event evt) { + button = DOM.eventGetButton(evt); + clientX = DOM.eventGetClientX(evt); + clientY = DOM.eventGetClientY(evt); + altKey = DOM.eventGetAltKey(evt); + ctrlKey = DOM.eventGetCtrlKey(evt); + metaKey = DOM.eventGetMetaKey(evt); + shiftKey = DOM.eventGetShiftKey(evt); + type = DOM.eventGetType(evt); + } + + private MouseEventDetails() { + } + + public String toString() { + return "" + button + DELIM + clientX + DELIM + clientY + DELIM + altKey + + DELIM + ctrlKey + DELIM + metaKey + DELIM + shiftKey + DELIM + + type; + } + + public static MouseEventDetails deSerialize(String serializedString) { + MouseEventDetails instance = new MouseEventDetails(); + String[] fields = serializedString.split(","); + + instance.button = Integer.parseInt(fields[0]); + instance.clientX = Integer.parseInt(fields[1]); + instance.clientY = Integer.parseInt(fields[2]); + instance.altKey = Boolean.valueOf(fields[3]).booleanValue(); + instance.ctrlKey = Boolean.valueOf(fields[4]).booleanValue(); + instance.metaKey = Boolean.valueOf(fields[5]).booleanValue(); + instance.shiftKey = Boolean.valueOf(fields[6]).booleanValue(); + instance.type = Integer.parseInt(fields[7]); + return instance; + } + + public boolean isDoubleClick() { + return type == Event.ONDBLCLICK; + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/IOrderedLayout.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/IOrderedLayout.java index 71258c192e..b1e9f5d919 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ui/IOrderedLayout.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/IOrderedLayout.java @@ -117,8 +117,8 @@ public class IOrderedLayout extends Panel implements Container, *

* There are two modes - vertical and horizontal. *

@@ -574,7 +574,7 @@ public class IOrderedLayout extends Panel implements Container, * without letting root element to affect the calculation. * * @param offset - * offsetWidth or offsetHeight + * offsetWidth or offsetHeight */ private int rootOffsetMeasure(String offset) { // TODO This method must be optimized! diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/IPanel.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/IPanel.java index 37fb6feca8..3493617722 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ui/IPanel.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/IPanel.java @@ -302,8 +302,6 @@ public class IPanel extends SimplePanel implements Paintable, // Restore content to flow if (hasChildren) { - ApplicationConnection.getConsole().log( - "positioning:" + origPositioning); DOM.setStyleAttribute(contentEl, "position", origPositioning); } // restore scroll position diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/IScrollTable.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/IScrollTable.java index 25209a1431..6dab67ccd6 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ui/IScrollTable.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/IScrollTable.java @@ -27,6 +27,7 @@ import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.Widget; import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener; +import com.itmill.toolkit.terminal.gwt.client.MouseEventDetails; import com.itmill.toolkit.terminal.gwt.client.Paintable; import com.itmill.toolkit.terminal.gwt.client.UIDL; import com.itmill.toolkit.terminal.gwt.client.Util; @@ -128,6 +129,7 @@ public class IScrollTable extends Composite implements Table, ScrollListener, * for container element. Then this value is used as a fallback. */ private int oldAvailPixels; + private boolean emitClickEvents; public IScrollTable() { @@ -154,6 +156,7 @@ public class IScrollTable extends Composite implements Table, ScrollListener, this.client = client; paintableId = uidl.getStringAttribute("id"); immediate = uidl.getBooleanAttribute("immediate"); + emitClickEvents = uidl.getBooleanAttribute("listenClicks"); final int newTotalRows = uidl.getIntAttribute("totalrows"); if (newTotalRows != totalRows) { if (tBody != null) { @@ -1942,7 +1945,7 @@ public class IScrollTable extends Composite implements Table, ScrollListener, private IScrollTableRow(int rowKey) { this.rowKey = rowKey; setElement(DOM.createElement("tr")); - DOM.sinkEvents(getElement(), Event.ONCLICK); + DOM.sinkEvents(getElement(), Event.ONCLICK | Event.ONDBLCLICK); attachContextMenuEvent(getElement()); } @@ -2084,26 +2087,55 @@ public class IScrollTable extends Composite implements Table, ScrollListener, return false; } + private void handleClickEvent(Event event) { + if (emitClickEvents) { + boolean dbl = DOM.eventGetType(event) == Event.ONDBLCLICK; + final Element tdOrTr = DOM.getParent(DOM + .eventGetTarget(event)); + client.updateVariable(paintableId, "clickedKey", "" + + rowKey, false); + if (DOM.compare(getElement(), DOM.getParent(tdOrTr))) { + int childIndex = DOM + .getChildIndex(getElement(), tdOrTr); + String colKey = null; + colKey = tHead.getHeaderCell(childIndex).getColKey(); + client.updateVariable(paintableId, "clickedColKey", + colKey, false); + } + MouseEventDetails details = new MouseEventDetails(event); + client + .updateVariable( + paintableId, + "clickEvent", + details.toString(), + !(!dbl + && selectMode > Table.SELECT_MODE_NONE && immediate)); + } + } + /* * React on click that occur on content cells only */ public void onBrowserEvent(Event event) { - switch (DOM.eventGetType(event)) { - case Event.ONCLICK: - final Element tdOrTr = DOM.getParent(DOM - .eventGetTarget(event)); - if (DOM.compare(getElement(), tdOrTr) - || DOM.compare(getElement(), DOM.getParent(tdOrTr))) { + final Element tdOrTr = DOM.getParent(DOM.eventGetTarget(event)); + if (DOM.compare(getElement(), tdOrTr) + || DOM.compare(getElement(), DOM.getParent(tdOrTr))) { + switch (DOM.eventGetType(event)) { + case Event.ONCLICK: + handleClickEvent(event); if (selectMode > Table.SELECT_MODE_NONE) { toggleSelection(); client.updateVariable(paintableId, "selected", selectedRowKeys.toArray(), immediate); } - } - break; + break; + case Event.ONDBLCLICK: + handleClickEvent(event); + break; - default: - break; + default: + break; + } } super.onBrowserEvent(event); } diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ITree.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ITree.java index 4615d3990f..08f3930799 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ui/ITree.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ITree.java @@ -16,6 +16,7 @@ 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.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.MouseEventDetails; import com.itmill.toolkit.terminal.gwt.client.Paintable; import com.itmill.toolkit.terminal.gwt.client.UIDL; import com.itmill.toolkit.terminal.gwt.client.Util; @@ -49,6 +50,8 @@ public class ITree extends FlowPanel implements Paintable { private boolean readonly; + private boolean emitClickEvents; + public ITree() { super(); setStyleName(CLASSNAME); @@ -97,6 +100,7 @@ public class ITree extends FlowPanel implements Paintable { disabled = uidl.getBooleanAttribute("disabled"); readonly = uidl.getBooleanAttribute("readonly"); + emitClickEvents = uidl.getBooleanAttribute("listenClicks"); isNullSelectionAllowed = uidl.getBooleanAttribute("nullselect"); @@ -189,7 +193,7 @@ public class ITree extends FlowPanel implements Paintable { public TreeNode() { constructDom(); - sinkEvents(Event.ONCLICK); + sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.ONMOUSEUP); } public void onBrowserEvent(Event event) { @@ -197,14 +201,19 @@ public class ITree extends FlowPanel implements Paintable { if (disabled) { return; } - if (DOM.eventGetType(event) == Event.ONCLICK) { - final Element target = DOM.eventGetTarget(event); + final int type = DOM.eventGetType(event); + final Element target = DOM.eventGetTarget(event); + if (emitClickEvents && DOM.compare(target, nodeCaptionSpan) + && (type == Event.ONDBLCLICK || type == Event.ONMOUSEUP)) { + fireClick(event); + } + if (type == Event.ONCLICK) { if (DOM.compare(getElement(), target) || DOM.compare(ie6compatnode, target)) { // state change toggleState(); } else if (!readonly && DOM.compare(target, nodeCaptionSpan)) { - // caption click = selection change + // caption click = selection change && possible click event toggleSelection(); } DOM.eventCancelBubble(event, true); @@ -215,6 +224,13 @@ public class ITree extends FlowPanel implements Paintable { } + private void fireClick(Event evt) { + MouseEventDetails details = new MouseEventDetails(evt); + client.updateVariable(paintableId, "clickedKey", key, false); + client.updateVariable(paintableId, "clickEvent", + details.toString(), !(selectable && immediate)); + } + private void toggleSelection() { if (selectable) { ITree.this.setSelected(this, !isSelected()); diff --git a/src/com/itmill/toolkit/tests/tickets/Ticket2009.java b/src/com/itmill/toolkit/tests/tickets/Ticket2009.java new file mode 100644 index 0000000000..f9eebb02b7 --- /dev/null +++ b/src/com/itmill/toolkit/tests/tickets/Ticket2009.java @@ -0,0 +1,125 @@ +package com.itmill.toolkit.tests.tickets; + +import com.itmill.toolkit.data.Container; +import com.itmill.toolkit.event.ItemClickEvent; +import com.itmill.toolkit.tests.TestForTablesInitialColumnWidthLogicRendering; +import com.itmill.toolkit.ui.Button; +import com.itmill.toolkit.ui.Label; +import com.itmill.toolkit.ui.OrderedLayout; +import com.itmill.toolkit.ui.Panel; +import com.itmill.toolkit.ui.Table; +import com.itmill.toolkit.ui.TextField; +import com.itmill.toolkit.ui.Tree; +import com.itmill.toolkit.ui.Window; +import com.itmill.toolkit.ui.Button.ClickEvent; + +public class Ticket2009 extends com.itmill.toolkit.Application { + + TextField f = new TextField(); + + public void init() { + final Window main = new Window(getClass().getName().substring( + getClass().getName().lastIndexOf(".") + 1)); + setMainWindow(main); + + OrderedLayout ol = new OrderedLayout( + OrderedLayout.ORIENTATION_HORIZONTAL); + main.setLayout(ol); + ol.setSizeFull(); + + Panel p = new Panel("Tree test"); + p.setSizeFull(); + + Tree t = new Tree(); + + t.addItem("Foo"); + t.addItem("Bar"); + + final OrderedLayout events = new OrderedLayout(); + + t.addListener(new ItemClickEvent.ItemClickListener() { + public void itemClick(ItemClickEvent event) { + events.addComponent(new Label(new Label("Click:" + + (event.isDoubleClick() ? "double" : "single") + + " button:" + event.getButton() + " propertyId:" + + event.getPropertyId() + " itemID:" + + event.getItemId() + " item:" + event.getItem()))); + + } + }); + + main.addComponent(p); + p.addComponent(t); + p.addComponent(events); + + Panel p2 = new Panel("Table test (try dbl click also)"); + p2.setSizeFull(); + + final OrderedLayout events2 = new OrderedLayout(); + Table table = TestForTablesInitialColumnWidthLogicRendering + .getTestTable(5, 100); + table.setRowHeaderMode(Table.ROW_HEADER_MODE_ID); + table.addListener(new ItemClickEvent.ItemClickListener() { + public void itemClick(ItemClickEvent event) { + events2.addComponent(new Label("Click:" + + (event.isDoubleClick() ? "double" : "single") + + " button:" + event.getButton() + " propertyId:" + + event.getPropertyId() + " itemID:" + + event.getItemId() + " item:" + event.getItem())); + if (event.isDoubleClick()) { + new PropertyEditor(event); + } + + } + }); + p2.addComponent(table); + p2.addComponent(events2); + + main.addComponent(p2); + + } + + class PropertyEditor extends Window { + + private static final int W = 300; + private static final int H = 150; + + private Container c; + private Object itemid; + private Object propertyid; + + TextField editor = new TextField(); + Button done = new Button("Done"); + + PropertyEditor(ItemClickEvent event) { + c = (Container) event.getSource(); + + propertyid = event.getPropertyId(); + itemid = event.getItemId(); + + setCaption("Editing " + itemid + " : " + propertyid); + + editor.setPropertyDataSource(c.getContainerProperty(itemid, + propertyid)); + addComponent(editor); + addComponent(done); + + setWidth(W); + setHeight(H); + + setPositionX(event.getClientX() - W / 2); + setPositionY(event.getClientY() - H / 2); + + getMainWindow().addWindow(this); + + done.addListener(new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + getMainWindow().removeWindow(PropertyEditor.this); + } + }); + + } + + } + +} \ No newline at end of file diff --git a/src/com/itmill/toolkit/ui/Table.java b/src/com/itmill/toolkit/ui/Table.java index a8b660554f..1a5fb10a67 100644 --- a/src/com/itmill/toolkit/ui/Table.java +++ b/src/com/itmill/toolkit/ui/Table.java @@ -21,10 +21,14 @@ import com.itmill.toolkit.data.Property; import com.itmill.toolkit.data.util.ContainerOrderedWrapper; import com.itmill.toolkit.data.util.IndexedContainer; import com.itmill.toolkit.event.Action; +import com.itmill.toolkit.event.ItemClickEvent; +import com.itmill.toolkit.event.ItemClickEvent.ItemClickListener; +import com.itmill.toolkit.event.ItemClickEvent.ItemClickSource; import com.itmill.toolkit.terminal.KeyMapper; import com.itmill.toolkit.terminal.PaintException; import com.itmill.toolkit.terminal.PaintTarget; import com.itmill.toolkit.terminal.Resource; +import com.itmill.toolkit.terminal.gwt.client.MouseEventDetails; /** *

@@ -44,7 +48,7 @@ import com.itmill.toolkit.terminal.Resource; * @since 3.0 */ public class Table extends AbstractSelect implements Action.Container, - Container.Ordered, Container.Sortable { + Container.Ordered, Container.Sortable, ItemClickSource { private static final int CELL_KEY = 0; @@ -306,6 +310,8 @@ public class Table extends AbstractSelect implements Action.Container, */ private CellStyleGenerator cellStyleGenerator = null; + private int clickListenerCount; + /* Table constructors */ /** @@ -364,7 +370,7 @@ public class Table extends AbstractSelect implements Action.Container, *

* * @param visibleColumns - * the Array of shown property id:s. + * the Array of shown property id:s. */ public void setVisibleColumns(Object[] visibleColumns) { @@ -461,8 +467,8 @@ public class Table extends AbstractSelect implements Action.Container, *

* * @param columnHeaders - * the Array of column headers that match the - * getVisibleColumns method. + * the Array of column headers that match the + * getVisibleColumns method. */ public void setColumnHeaders(String[] columnHeaders) { @@ -521,8 +527,8 @@ public class Table extends AbstractSelect implements Action.Container, *

* * @param columnIcons - * the Array of icons that match the - * getVisibleColumns. + * the Array of icons that match the + * getVisibleColumns. */ public void setColumnIcons(Resource[] columnIcons) { @@ -548,8 +554,8 @@ public class Table extends AbstractSelect implements Action.Container, * *

* The items in the array must match the properties identified by - * getVisibleColumns(). The possible values for the alignments - * include: + * getVisibleColumns(). The possible values for the + * alignments include: *