From 7e926576b03e6078c2f38b500a961307c11c47d8 Mon Sep 17 00:00:00 2001 From: Matti Tahvonen Date: Tue, 5 Aug 2008 07:58:31 +0000 Subject: [PATCH] svn changeset:5135/svn branch:trunk --- .../terminal/gwt/client/ui/ui/Action.java | 55 + .../gwt/client/ui/ui/ActionOwner.java | 16 + .../gwt/client/ui/ui/AlignmentInfo.java | 76 + .../gwt/client/ui/ui/CalendarEntry.java | 126 + .../gwt/client/ui/ui/CalendarPanel.java | 491 ++++ .../gwt/client/ui/ui/ContextMenu.java | 117 + .../terminal/gwt/client/ui/ui/Field.java | 13 + .../terminal/gwt/client/ui/ui/IAccordion.java | 267 ++ .../terminal/gwt/client/ui/ui/IButton.java | 117 + .../terminal/gwt/client/ui/ui/ICheckBox.java | 118 + .../gwt/client/ui/ui/ICustomComponent.java | 64 + .../gwt/client/ui/ui/ICustomLayout.java | 463 ++++ .../terminal/gwt/client/ui/ui/IDateField.java | 231 ++ .../gwt/client/ui/ui/IDateFieldCalendar.java | 25 + .../terminal/gwt/client/ui/ui/IEmbedded.java | 127 + .../gwt/client/ui/ui/IExpandLayout.java | 726 ++++++ .../gwt/client/ui/ui/IFilterSelect.java | 788 ++++++ .../terminal/gwt/client/ui/ui/IForm.java | 142 ++ .../gwt/client/ui/ui/IFormLayout.java | 299 +++ .../gwt/client/ui/ui/IGridLayout.java | 296 +++ .../client/ui/ui/IHorizontalExpandLayout.java | 13 + .../terminal/gwt/client/ui/ui/ILabel.java | 49 + .../terminal/gwt/client/ui/ui/ILink.java | 193 ++ .../gwt/client/ui/ui/IListSelect.java | 126 + .../terminal/gwt/client/ui/ui/IMenuBar.java | 234 ++ .../gwt/client/ui/ui/INativeSelect.java | 77 + .../gwt/client/ui/ui/IOptionGroup.java | 78 + .../gwt/client/ui/ui/IOptionGroupBase.java | 223 ++ .../gwt/client/ui/ui/IOrderedLayout.java | 1150 +++++++++ .../terminal/gwt/client/ui/ui/IPanel.java | 388 +++ .../gwt/client/ui/ui/IPasswordField.java | 21 + .../gwt/client/ui/ui/IPopupCalendar.java | 126 + .../gwt/client/ui/ui/IProgressIndicator.java | 96 + .../gwt/client/ui/ui/IScrollTable.java | 2246 +++++++++++++++++ .../terminal/gwt/client/ui/ui/ISlider.java | 415 +++ .../gwt/client/ui/ui/ISplitPanel.java | 410 +++ .../client/ui/ui/ISplitPanelHorizontal.java | 12 + .../gwt/client/ui/ui/ISplitPanelVertical.java | 12 + .../gwt/client/ui/ui/ITablePaging.java | 438 ++++ .../terminal/gwt/client/ui/ui/ITabsheet.java | 383 +++ .../gwt/client/ui/ui/ITabsheetBase.java | 157 ++ .../gwt/client/ui/ui/ITabsheetPanel.java | 112 + .../terminal/gwt/client/ui/ui/ITextArea.java | 50 + .../terminal/gwt/client/ui/ui/ITextField.java | 204 ++ .../gwt/client/ui/ui/ITextualDate.java | 279 ++ .../terminal/gwt/client/ui/ui/ITree.java | 409 +++ .../gwt/client/ui/ui/ITwinColSelect.java | 220 ++ .../gwt/client/ui/ui/IUnknownComponent.java | 40 + .../terminal/gwt/client/ui/ui/IUpload.java | 151 ++ .../terminal/gwt/client/ui/ui/IView.java | 295 +++ .../terminal/gwt/client/ui/ui/IWindow.java | 678 +++++ .../terminal/gwt/client/ui/ui/Icon.java | 36 + .../terminal/gwt/client/ui/ui/MarginInfo.java | 68 + .../terminal/gwt/client/ui/ui/MenuBar.java | 513 ++++ .../terminal/gwt/client/ui/ui/MenuItem.java | 188 ++ .../gwt/client/ui/ui/Notification.java | 289 +++ .../client/ui/ui/ShortcutActionHandler.java | 177 ++ .../terminal/gwt/client/ui/ui/Table.java | 15 + .../terminal/gwt/client/ui/ui/Time.java | 317 +++ .../gwt/client/ui/ui/ToolkitOverlay.java | 147 ++ .../terminal/gwt/client/ui/ui/TreeAction.java | 55 + .../terminal/gwt/client/ui/ui/TreeImages.java | 27 + .../ui/ui/absolutegrid/AbsoluteGrid.java | 305 +++ .../ui/absolutegrid/ISizeableGridLayout.java | 166 ++ .../ui/ui/richtextarea/IRichTextArea.java | 111 + .../RichTextToolbar$Strings.properties | 35 + .../ui/ui/richtextarea/RichTextToolbar.java | 509 ++++ .../client/ui/ui/richtextarea/backColors.gif | Bin 0 -> 104 bytes .../gwt/client/ui/ui/richtextarea/bold.gif | Bin 0 -> 864 bytes .../client/ui/ui/richtextarea/createLink.gif | Bin 0 -> 118 bytes .../client/ui/ui/richtextarea/fontSizes.gif | Bin 0 -> 96 bytes .../gwt/client/ui/ui/richtextarea/fonts.gif | Bin 0 -> 147 bytes .../client/ui/ui/richtextarea/foreColors.gif | Bin 0 -> 173 bytes .../gwt/client/ui/ui/richtextarea/gwtLogo.png | Bin 0 -> 11454 bytes .../gwt/client/ui/ui/richtextarea/hr.gif | Bin 0 -> 67 bytes .../gwt/client/ui/ui/richtextarea/indent.gif | Bin 0 -> 82 bytes .../client/ui/ui/richtextarea/insertImage.gif | Bin 0 -> 290 bytes .../gwt/client/ui/ui/richtextarea/italic.gif | Bin 0 -> 79 bytes .../ui/ui/richtextarea/justifyCenter.gif | Bin 0 -> 70 bytes .../client/ui/ui/richtextarea/justifyLeft.gif | Bin 0 -> 71 bytes .../ui/ui/richtextarea/justifyRight.gif | Bin 0 -> 855 bytes .../gwt/client/ui/ui/richtextarea/ol.gif | Bin 0 -> 76 bytes .../gwt/client/ui/ui/richtextarea/outdent.gif | Bin 0 -> 82 bytes .../ui/ui/richtextarea/removeFormat.gif | Bin 0 -> 360 bytes .../client/ui/ui/richtextarea/removeLink.gif | Bin 0 -> 895 bytes .../ui/ui/richtextarea/strikeThrough.gif | Bin 0 -> 80 bytes .../client/ui/ui/richtextarea/subscript.gif | Bin 0 -> 80 bytes .../client/ui/ui/richtextarea/superscript.gif | Bin 0 -> 80 bytes .../gwt/client/ui/ui/richtextarea/ul.gif | Bin 0 -> 863 bytes .../client/ui/ui/richtextarea/underline.gif | Bin 0 -> 88 bytes 90 files changed, 16800 insertions(+) create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Action.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ActionOwner.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/AlignmentInfo.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/CalendarEntry.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/CalendarPanel.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ContextMenu.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Field.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IAccordion.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IButton.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ICheckBox.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ICustomComponent.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ICustomLayout.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IDateField.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IDateFieldCalendar.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IEmbedded.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IExpandLayout.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IFilterSelect.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IForm.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IFormLayout.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IGridLayout.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IHorizontalExpandLayout.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ILabel.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ILink.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IListSelect.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IMenuBar.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/INativeSelect.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IOptionGroup.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IOptionGroupBase.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IOrderedLayout.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IPanel.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IPasswordField.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IPopupCalendar.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IProgressIndicator.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IScrollTable.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISlider.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISplitPanel.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISplitPanelHorizontal.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISplitPanelVertical.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITablePaging.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITabsheet.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITabsheetBase.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITabsheetPanel.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITextArea.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITextField.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITextualDate.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITree.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITwinColSelect.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IUnknownComponent.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IUpload.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IView.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IWindow.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Icon.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/MarginInfo.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/MenuBar.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/MenuItem.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Notification.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ShortcutActionHandler.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Table.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Time.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ToolkitOverlay.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/TreeAction.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/TreeImages.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/absolutegrid/AbsoluteGrid.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/absolutegrid/ISizeableGridLayout.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/IRichTextArea.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/RichTextToolbar$Strings.properties create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/RichTextToolbar.java create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/backColors.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/bold.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/createLink.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/fontSizes.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/fonts.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/foreColors.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/gwtLogo.png create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/hr.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/indent.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/insertImage.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/italic.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/justifyCenter.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/justifyLeft.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/justifyRight.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/ol.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/outdent.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/removeFormat.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/removeLink.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/strikeThrough.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/subscript.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/superscript.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/ul.gif create mode 100644 src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/underline.gif diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Action.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Action.java new file mode 100644 index 0000000000..0f059870e3 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Action.java @@ -0,0 +1,55 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.Command; + +/** + * + */ +public abstract class Action implements Command { + + protected ActionOwner owner; + + protected String iconUrl = null; + + protected String caption = ""; + + public Action(ActionOwner owner) { + this.owner = owner; + } + + /** + * Executed when action fired + */ + public abstract void execute(); + + public String getHTML() { + final StringBuffer sb = new StringBuffer(); + sb.append("
"); + if (getIconUrl() != null) { + sb.append("\"icon\""); + } + sb.append(getCaption()); + sb.append("
"); + return sb.toString(); + } + + public String getCaption() { + return caption; + } + + public void setCaption(String caption) { + this.caption = caption; + } + + public String getIconUrl() { + return iconUrl; + } + + public void setIconUrl(String url) { + iconUrl = url; + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ActionOwner.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ActionOwner.java new file mode 100644 index 0000000000..647a1cfe89 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ActionOwner.java @@ -0,0 +1,16 @@ +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; + +public interface ActionOwner { + + /** + * @return Array of IActions + */ + public Action[] getActions(); + + public ApplicationConnection getClient(); + + public String getPaintableId(); + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/AlignmentInfo.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/AlignmentInfo.java new file mode 100644 index 0000000000..68024d849a --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/AlignmentInfo.java @@ -0,0 +1,76 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +public class AlignmentInfo { + + public static final int ALIGNMENT_LEFT = 1; + public static final int ALIGNMENT_RIGHT = 2; + public static final int ALIGNMENT_TOP = 4; + public static final int ALIGNMENT_BOTTOM = 8; + public static final int ALIGNMENT_HORIZONTAL_CENTER = 16; + public static final int ALIGNMENT_VERTICAL_CENTER = 32; + + private int bitMask; + + public AlignmentInfo(int bitMask) { + this.bitMask = bitMask; + } + + public AlignmentInfo(int horizontal, int vertical) { + setAlignment(horizontal, vertical); + } + + public void setAlignment(int horiz, int vert) { + bitMask = horiz + vert; + } + + public int getBitMask() { + return bitMask; + } + + public boolean isTop() { + return (bitMask & ALIGNMENT_TOP) == ALIGNMENT_TOP; + } + + public boolean isBottom() { + return (bitMask & ALIGNMENT_BOTTOM) == ALIGNMENT_BOTTOM; + } + + public boolean isLeft() { + return (bitMask & ALIGNMENT_LEFT) == ALIGNMENT_LEFT; + } + + public boolean isRight() { + return (bitMask & ALIGNMENT_RIGHT) == ALIGNMENT_RIGHT; + } + + public boolean isVerticalCenter() { + return (bitMask & ALIGNMENT_VERTICAL_CENTER) == ALIGNMENT_VERTICAL_CENTER; + } + + public boolean isHorizontalCenter() { + return (bitMask & ALIGNMENT_HORIZONTAL_CENTER) == ALIGNMENT_HORIZONTAL_CENTER; + } + + public String getVerticalAlignment() { + if (isBottom()) { + return "bottom"; + } else if (isVerticalCenter()) { + return "middle"; + } + return "top"; + } + + public String getHorizontalAlignment() { + if (isRight()) { + return "right"; + } else if (isHorizontalCenter()) { + return "center"; + } + return "left"; + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/CalendarEntry.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/CalendarEntry.java new file mode 100644 index 0000000000..551c458ed6 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/CalendarEntry.java @@ -0,0 +1,126 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.Date; + +import com.itmill.toolkit.terminal.gwt.client.DateTimeService; + +public class CalendarEntry { + private final String styleName; + private Date start; + private Date end; + private String title; + private String description; + private boolean notime; + + public CalendarEntry(String styleName, Date start, Date end, String title, + String description, boolean notime) { + this.styleName = styleName; + if (notime) { + Date d = new Date(start.getTime()); + d.setSeconds(0); + d.setMinutes(0); + this.start = d; + if (end != null) { + d = new Date(end.getTime()); + d.setSeconds(0); + d.setMinutes(0); + this.end = d; + } else { + end = start; + } + } else { + this.start = start; + this.end = end; + } + this.title = title; + this.description = description; + this.notime = notime; + } + + public CalendarEntry(String styleName, Date start, Date end, String title, + String description) { + this(styleName, start, end, title, description, false); + } + + public String getStyleName() { + return styleName; + } + + public Date getStart() { + return start; + } + + public void setStart(Date start) { + this.start = start; + } + + public Date getEnd() { + return end; + } + + public void setEnd(Date end) { + this.end = end; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public boolean isNotime() { + return notime; + } + + public void setNotime(boolean notime) { + this.notime = notime; + } + + public String getStringForDate(Date d) { + // TODO format from DateTimeService + String s = ""; + if (!notime) { + if (!DateTimeService.isSameDay(d, start)) { + s += (start.getYear() + 1900) + "." + (start.getMonth() + 1) + + "." + start.getDate() + " "; + } + int i = start.getHours(); + s += (i < 10 ? "0" : "") + i; + s += ":"; + i = start.getMinutes(); + s += (i < 10 ? "0" : "") + i; + if (!start.equals(end)) { + s += " - "; + if (!DateTimeService.isSameDay(start, end)) { + s += (end.getYear() + 1900) + "." + (end.getMonth() + 1) + + "." + end.getDate() + " "; + } + i = end.getHours(); + s += (i < 10 ? "0" : "") + i; + s += ":"; + i = end.getMinutes(); + s += (i < 10 ? "0" : "") + i; + } + s += " "; + } + if (title != null) { + s += title; + } + return s; + } + +} \ No newline at end of file diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/CalendarPanel.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/CalendarPanel.java new file mode 100644 index 0000000000..f5ecf338d0 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/CalendarPanel.java @@ -0,0 +1,491 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +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.ClickListener; +import com.google.gwt.user.client.ui.FlexTable; +import com.google.gwt.user.client.ui.MouseListener; +import com.google.gwt.user.client.ui.MouseListenerCollection; +import com.google.gwt.user.client.ui.SourcesMouseEvents; +import com.google.gwt.user.client.ui.SourcesTableEvents; +import com.google.gwt.user.client.ui.TableListener; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.DateTimeService; +import com.itmill.toolkit.terminal.gwt.client.LocaleService; + +public class CalendarPanel extends FlexTable implements MouseListener, + ClickListener { + + private final IDateField datefield; + + private IEventButton prevYear; + + private IEventButton nextYear; + + private IEventButton prevMonth; + + private IEventButton nextMonth; + + private Time time; + + private Date minDate = null; + + private Date maxDate = null; + + private CalendarEntrySource entrySource; + + /* Needed to identify resolution changes */ + private int resolution = IDateField.RESOLUTION_YEAR; + + /* Needed to identify locale changes */ + private String locale = LocaleService.getDefaultLocale(); + + public CalendarPanel(IDateField parent) { + datefield = parent; + setStyleName(IDateField.CLASSNAME + "-calendarpanel"); + // buildCalendar(true); + addTableListener(new DateClickListener(this)); + } + + public CalendarPanel(IDateField parent, Date min, Date max) { + datefield = parent; + setStyleName(IDateField.CLASSNAME + "-calendarpanel"); + // buildCalendar(true); + addTableListener(new DateClickListener(this)); + + } + + private void buildCalendar(boolean forceRedraw) { + final boolean needsMonth = datefield.getCurrentResolution() > IDateField.RESOLUTION_YEAR; + boolean needsBody = datefield.getCurrentResolution() >= IDateField.RESOLUTION_DAY; + final boolean needsTime = datefield.getCurrentResolution() >= IDateField.RESOLUTION_HOUR; + buildCalendarHeader(forceRedraw, needsMonth); + clearCalendarBody(!needsBody); + if (needsBody) { + buildCalendarBody(); + } + if (needsTime) { + buildTime(forceRedraw); + } else if (time != null) { + remove(time); + time = null; + } + } + + private void clearCalendarBody(boolean remove) { + if (!remove) { + for (int row = 2; row < 8; row++) { + for (int col = 0; col < 7; col++) { + setHTML(row, col, " "); + } + } + } else if (getRowCount() > 2) { + while (getRowCount() > 2) { + removeRow(2); + } + } + } + + private void buildCalendarHeader(boolean forceRedraw, boolean needsMonth) { + if (forceRedraw) { + if (prevMonth == null) { // Only do once + prevYear = new IEventButton(); + prevYear.setHTML("«"); + prevYear.setStyleName("i-button-prevyear"); + nextYear = new IEventButton(); + nextYear.setHTML("»"); + nextYear.setStyleName("i-button-nextyear"); + prevYear.addMouseListener(this); + nextYear.addMouseListener(this); + prevYear.addClickListener(this); + nextYear.addClickListener(this); + setWidget(0, 0, prevYear); + setWidget(0, 4, nextYear); + + if (needsMonth) { + prevMonth = new IEventButton(); + prevMonth.setHTML("‹"); + prevMonth.setStyleName("i-button-prevmonth"); + nextMonth = new IEventButton(); + nextMonth.setHTML("›"); + nextMonth.setStyleName("i-button-nextmonth"); + prevMonth.addMouseListener(this); + nextMonth.addMouseListener(this); + prevMonth.addClickListener(this); + nextMonth.addClickListener(this); + setWidget(0, 3, nextMonth); + setWidget(0, 1, prevMonth); + } + + getFlexCellFormatter().setColSpan(0, 2, 3); + getRowFormatter().addStyleName(0, + IDateField.CLASSNAME + "-calendarpanel-header"); + } else if (!needsMonth) { + // Remove month traverse buttons + prevMonth.removeClickListener(this); + prevMonth.removeMouseListener(this); + nextMonth.removeClickListener(this); + nextMonth.removeMouseListener(this); + remove(prevMonth); + remove(nextMonth); + prevMonth = null; + nextMonth = null; + } + + // Print weekday names + final int firstDay = datefield.getDateTimeService() + .getFirstDayOfWeek(); + for (int i = 0; i < 7; i++) { + int day = i + firstDay; + if (day > 6) { + day = 0; + } + if (datefield.getCurrentResolution() > IDateField.RESOLUTION_MONTH) { + setHTML(1, i, "" + + datefield.getDateTimeService().getShortDay(day) + + ""); + } else { + setHTML(1, i, ""); + } + } + } + + final String monthName = needsMonth ? datefield.getDateTimeService() + .getMonth(datefield.getShowingDate().getMonth()) : ""; + final int year = datefield.getShowingDate().getYear() + 1900; + setHTML(0, 2, "" + monthName + " " + year + + ""); + } + + private void buildCalendarBody() { + // date actually selected? + Date currentDate = datefield.getCurrentDate(); + Date showing = datefield.getShowingDate(); + boolean selected = (currentDate != null + && currentDate.getMonth() == showing.getMonth() && currentDate + .getYear() == showing.getYear()); + + final int startWeekDay = datefield.getDateTimeService() + .getStartWeekDay(datefield.getShowingDate()); + final int numDays = DateTimeService.getNumberOfDaysInMonth(datefield + .getShowingDate()); + int dayCount = 0; + final Date today = new Date(); + final Date curr = new Date(datefield.getShowingDate().getTime()); + for (int row = 2; row < 8; row++) { + for (int col = 0; col < 7; col++) { + if (!(row == 2 && col < startWeekDay)) { + if (dayCount < numDays) { + final int selectedDate = ++dayCount; + String title = ""; + if (entrySource != null) { + curr.setDate(dayCount); + final List entries = entrySource.getEntries(curr, + IDateField.RESOLUTION_DAY); + if (entries != null) { + for (final Iterator it = entries.iterator(); it + .hasNext();) { + final CalendarEntry entry = (CalendarEntry) it + .next(); + title += (title.length() > 0 ? ", " : "") + + entry.getStringForDate(curr); + } + } + } + final String baseclass = IDateField.CLASSNAME + + "-calendarpanel-day"; + String cssClass = baseclass; + if (!isEnabledDate(curr)) { + cssClass += " " + baseclass + "-disabled"; + } + if (selected + && datefield.getShowingDate().getDate() == dayCount) { + cssClass += " " + baseclass + "-selected"; + } + if (today.getDate() == dayCount + && today.getMonth() == datefield + .getShowingDate().getMonth() + && today.getYear() == datefield + .getShowingDate().getYear()) { + cssClass += " " + baseclass + "-today"; + } + if (title.length() > 0) { + cssClass += " " + baseclass + "-entry"; + } + setHTML(row, col, "" + + selectedDate + ""); + } else { + break; + } + + } + } + } + } + + private void buildTime(boolean forceRedraw) { + if (time == null) { + time = new Time(datefield); + setText(8, 0, ""); // Add new row + getFlexCellFormatter().setColSpan(8, 0, 7); + setWidget(8, 0, time); + } + time.updateTime(forceRedraw); + } + + /** + * + * @param forceRedraw + * Build all from scratch, in case of e.g. locale changes + */ + public void updateCalendar() { + // Locale and resolution changes force a complete redraw + buildCalendar(locale != datefield.getCurrentLocale() + || resolution != datefield.getCurrentResolution()); + if (datefield instanceof ITextualDate) { + ((ITextualDate) datefield).buildDate(); + } + locale = datefield.getCurrentLocale(); + resolution = datefield.getCurrentResolution(); + } + + public void onClick(Widget sender) { + // processClickEvent(sender, true); + } + + private boolean isEnabledDate(Date date) { + if ((minDate != null && date.before(minDate)) + || (maxDate != null && date.after(maxDate))) { + return false; + } + return true; + } + + private void processClickEvent(Widget sender, boolean updateVariable) { + if (!datefield.isEnabled() || datefield.isReadonly()) { + return; + } + Date showingDate = datefield.getShowingDate(); + if (!updateVariable) { + if (sender == prevYear) { + showingDate.setYear(showingDate.getYear() - 1); + updateCalendar(); + } else if (sender == nextYear) { + showingDate.setYear(showingDate.getYear() + 1); + updateCalendar(); + } else if (sender == prevMonth) { + showingDate.setMonth(showingDate.getMonth() - 1); + updateCalendar(); + } else if (sender == nextMonth) { + showingDate.setMonth(showingDate.getMonth() + 1); + updateCalendar(); + } + } else { + if (datefield.getCurrentResolution() == IDateField.RESOLUTION_YEAR + || datefield.getCurrentResolution() == IDateField.RESOLUTION_MONTH) { + // Due to current UI, update variable if res=year/month + datefield.setCurrentDate(new Date(showingDate.getTime())); + if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MONTH) { + datefield.getClient().updateVariable(datefield.getId(), + "month", datefield.getCurrentDate().getMonth() + 1, + false); + } + datefield.getClient().updateVariable(datefield.getId(), "year", + datefield.getCurrentDate().getYear() + 1900, + datefield.isImmediate()); + } + } + } + + private Timer timer; + + public void onMouseDown(final Widget sender, int x, int y) { + if (sender instanceof IEventButton) { + processClickEvent(sender, false); + timer = new Timer() { + public void run() { + processClickEvent(sender, false); + } + }; + timer.scheduleRepeating(100); + } + } + + public void onMouseEnter(Widget sender) { + } + + public void onMouseLeave(Widget sender) { + if (timer != null) { + timer.cancel(); + } + } + + public void onMouseMove(Widget sender, int x, int y) { + } + + public void onMouseUp(Widget sender, int x, int y) { + if (timer != null) { + timer.cancel(); + } + processClickEvent(sender, true); + } + + private class IEventButton extends IButton implements SourcesMouseEvents { + + private MouseListenerCollection mouseListeners; + + public IEventButton() { + super(); + sinkEvents(Event.FOCUSEVENTS | Event.KEYEVENTS | Event.ONCLICK + | Event.MOUSEEVENTS); + } + + public void addMouseListener(MouseListener listener) { + if (mouseListeners == null) { + mouseListeners = new MouseListenerCollection(); + } + mouseListeners.add(listener); + } + + public void removeMouseListener(MouseListener listener) { + if (mouseListeners != null) { + mouseListeners.remove(listener); + } + } + + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + switch (DOM.eventGetType(event)) { + case Event.ONMOUSEDOWN: + case Event.ONMOUSEUP: + case Event.ONMOUSEMOVE: + case Event.ONMOUSEOVER: + case Event.ONMOUSEOUT: + if (mouseListeners != null) { + mouseListeners.fireMouseEvent(this, event); + } + break; + } + } + } + + private class DateClickListener implements TableListener { + + private final CalendarPanel cal; + + public DateClickListener(CalendarPanel panel) { + cal = panel; + } + + public void onCellClicked(SourcesTableEvents sender, int row, int col) { + if (sender != cal || row < 2 || row > 7 + || !cal.datefield.isEnabled() || cal.datefield.isReadonly()) { + return; + } + + final String text = cal.getText(row, col); + if (text.equals(" ")) { + return; + } + + try { + final Integer day = new Integer(text); + final Date newDate = cal.datefield.getShowingDate(); + newDate.setDate(day.intValue()); + if (!isEnabledDate(newDate)) { + return; + } + if (cal.datefield.getCurrentDate() == null) { + cal.datefield.setCurrentDate(new Date(newDate.getTime())); + + // Init variables with current time + datefield.getClient().updateVariable(cal.datefield.getId(), + "hour", newDate.getHours(), false); + datefield.getClient().updateVariable(cal.datefield.getId(), + "min", newDate.getMinutes(), false); + datefield.getClient().updateVariable(cal.datefield.getId(), + "sec", newDate.getSeconds(), false); + datefield.getClient().updateVariable(cal.datefield.getId(), + "msec", datefield.getMilliseconds(), false); + } + + cal.datefield.getCurrentDate().setTime(newDate.getTime()); + cal.datefield.getClient().updateVariable(cal.datefield.getId(), + "day", cal.datefield.getCurrentDate().getDate(), false); + cal.datefield.getClient().updateVariable(cal.datefield.getId(), + "month", cal.datefield.getCurrentDate().getMonth() + 1, + false); + cal.datefield.getClient().updateVariable(cal.datefield.getId(), + "year", + cal.datefield.getCurrentDate().getYear() + 1900, + cal.datefield.isImmediate()); + + if (datefield instanceof ITextualDate + && resolution < IDateField.RESOLUTION_HOUR) { + ((ToolkitOverlay) getParent()).hide(); + } else { + updateCalendar(); + } + + } catch (final NumberFormatException e) { + // Not a number, ignore and stop here + return; + } + } + + } + + public void setLimits(Date min, Date max) { + if (min != null) { + final Date d = new Date(min.getTime()); + d.setHours(0); + d.setMinutes(0); + d.setSeconds(1); + minDate = d; + } else { + minDate = null; + } + if (max != null) { + final Date d = new Date(max.getTime()); + d.setHours(24); + d.setMinutes(59); + d.setSeconds(59); + maxDate = d; + } else { + maxDate = null; + } + } + + public void setCalendarEntrySource(CalendarEntrySource entrySource) { + this.entrySource = entrySource; + } + + public CalendarEntrySource getCalendarEntrySource() { + return entrySource; + } + + public interface CalendarEntrySource { + public List getEntries(Date date, int resolution); + } + + /** + * Sets focus to Calendar panel. + * + * @param focus + */ + public void setFocus(boolean focus) { + nextYear.setFocus(focus); + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ContextMenu.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ContextMenu.java new file mode 100644 index 0000000000..b97dafee5b --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ContextMenu.java @@ -0,0 +1,117 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.MenuBar; +import com.google.gwt.user.client.ui.MenuItem; +import com.google.gwt.user.client.ui.PopupPanel; + +public class ContextMenu extends ToolkitOverlay { + + private ActionOwner actionOwner; + + private final CMenuBar menu = new CMenuBar(); + + private int left; + + private int top; + + /** + * This method should be used only by Client object as only one per client + * should exists. Request an instance via client.getContextMenu(); + * + * @param cli + * to be set as an owner of menu + */ + public ContextMenu() { + super(true, false, true); + setWidget(menu); + setStyleName("i-contextmenu"); + } + + /** + * Sets the element from which to build menu + * + * @param ao + */ + public void setActionOwner(ActionOwner ao) { + actionOwner = ao; + } + + /** + * Shows context menu at given location. + * + * @param left + * @param top + */ + public void showAt(int left, int top) { + this.left = left; + this.top = top; + menu.clearItems(); + final Action[] actions = actionOwner.getActions(); + for (int i = 0; i < actions.length; i++) { + final Action a = actions[i]; + menu.addItem(new MenuItem(a.getHTML(), true, a)); + } + + setPopupPositionAndShow(new PositionCallback() { + public void setPosition(int offsetWidth, int offsetHeight) { + // mac FF gets bad width due GWT popups overflow hacks, + // re-determine width + offsetWidth = menu.getOffsetWidth(); + int left = ContextMenu.this.left; + int top = ContextMenu.this.top; + if (offsetWidth + left > Window.getClientWidth()) { + left = left - offsetWidth; + if (left < 0) { + left = 0; + } + } + if (offsetHeight + top > Window.getClientHeight()) { + top = top - offsetHeight; + if (top < 0) { + top = 0; + } + } + setPopupPosition(left, top); + } + }); + } + + public void showAt(ActionOwner ao, int left, int top) { + setActionOwner(ao); + showAt(left, top); + } + + /** + * Extend standard Gwt MenuBar to set proper settings and to override + * onPopupClosed method so that PopupPanel gets closed. + */ + class CMenuBar extends MenuBar { + public CMenuBar() { + super(true); + } + + public void onPopupClosed(PopupPanel sender, boolean autoClosed) { + super.onPopupClosed(sender, autoClosed); + ContextMenu.this.hide(); + } + + /*public void onBrowserEvent(Event event) { + // Remove current selection when mouse leaves + if (DOM.eventGetType(event) == Event.ONMOUSEOUT) { + Element to = DOM.eventGetToElement(event); + if (!DOM.isOrHasChild(getElement(), to)) { + DOM.setElementProperty( + super.getSelectedItem().getElement(), "className", + super.getSelectedItem().getStylePrimaryName()); + } + } + + super.onBrowserEvent(event); + }*/ + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Field.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Field.java new file mode 100644 index 0000000000..d0e1d0b666 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Field.java @@ -0,0 +1,13 @@ +/** + * + */ +package com.itmill.toolkit.terminal.gwt.client.ui; + +/** + * This interface indicates that the component is a Field (serverside), and + * wants (for instance) to automatically get the i-modified classname. + * + */ +public interface Field { + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IAccordion.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IAccordion.java new file mode 100644 index 0000000000..bd591bd229 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IAccordion.java @@ -0,0 +1,267 @@ +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.DeferredCommand; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.ClickListener; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Caption; +import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +public class IAccordion extends ITabsheetBase implements + ContainerResizedListener { + + public static final String CLASSNAME = "i-accordion"; + + private ArrayList stack = new ArrayList(); + + private Set paintables = new HashSet(); + + private String height; + + public IAccordion() { + super(CLASSNAME); + // IE6 needs this to calculate offsetHeight correctly + if (Util.isIE6()) { + DOM.setStyleAttribute(getElement(), "zoom", "1"); + } + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + super.updateFromUIDL(uidl, client); + iLayout(); + } + + private StackItem getSelectedStack() { + if (stack.size() == 0) { + return null; + } + return (StackItem) stack.get(activeTabIndex); + } + + protected void renderTab(UIDL tabUidl, int index, boolean selected) { + // TODO check indexes, now new tabs get placed last (changing tab order + // is not supported from server-side) + + StackItem item = new StackItem(tabUidl); + + if (stack.size() == 0) { + item.addStyleDependentName("first"); + } + + stack.add(item); + add(item, getElement()); + + if (selected) { + item.open(); + item.setContent(tabUidl.getChildUIDL(0)); + } else if (tabUidl.getChildCount() > 0) { + // updating a drawn child on hidden tab + Paintable paintable = client.getPaintable(tabUidl.getChildUIDL(0)); + paintable.updateFromUIDL(tabUidl.getChildUIDL(0), client); + } + } + + protected void selectTab(final int index, final UIDL contentUidl) { + StackItem item = (StackItem) stack.get(index); + if (index != activeTabIndex) { + activeTabIndex = index; + item.open(); + iLayout(); + } + item.setContent(contentUidl); + } + + public void onSelectTab(StackItem item) { + final int index = stack.indexOf(item); + if (index != activeTabIndex && !disabled && !readonly + && !disabledTabKeys.contains(tabKeys.get(index))) { + if (getSelectedStack() != null) { + getSelectedStack().close(); + } + addStyleDependentName("loading"); + // run updating variables in deferred command to bypass some FF + // optimization issues + DeferredCommand.addCommand(new Command() { + public void execute() { + client.updateVariable(id, "selected", "" + + tabKeys.get(index), true); + } + }); + } + } + + public void setWidth(String width) { + if (width.equals("100%")) { + super.setWidth(""); + } else { + super.setWidth(width); + } + } + + public void setHeight(String height) { + this.height = height; + } + + public void iLayout() { + StackItem item = getSelectedStack(); + if (item == null) { + return; + } + + if (height != null && height != "") { + // Detach visible widget from document flow for a while to calculate + // used height correctly + Widget w = item.getPaintable(); + String originalPositioning = ""; + if (w != null) { + originalPositioning = DOM.getStyleAttribute(w.getElement(), + "position"); + DOM.setStyleAttribute(w.getElement(), "visibility", "hidden"); + DOM.setStyleAttribute(w.getElement(), "position", "absolute"); + } + DOM.setStyleAttribute(item.getContainerElement(), "height", "0"); + + // Calculate target height + super.setHeight(height); + int targetHeight = DOM.getElementPropertyInt(DOM + .getParent(getElement()), "offsetHeight"); + super.setHeight(""); + + // Calculate used height + int usedHeight = getOffsetHeight(); + + int h = targetHeight - usedHeight; + if (h < 0) { + h = 0; + } + DOM.setStyleAttribute(item.getContainerElement(), "height", h + + "px"); + + // Put widget back into normal flow + if (w != null) { + DOM.setStyleAttribute(w.getElement(), "position", + originalPositioning); + DOM.setStyleAttribute(w.getElement(), "visibility", ""); + } + } else { + DOM.setStyleAttribute(item.getContainerElement(), "height", ""); + } + + Util.runDescendentsLayout(this); + } + + /** + * TODO Caption widget not properly attached + */ + protected class StackItem extends ComplexPanel implements ClickListener { + + private Caption caption; + private boolean open = false; + private Element content; + private Element captionNode; + private Paintable paintable; + + public StackItem(UIDL tabUidl) { + setElement(DOM.createDiv()); + caption = new Caption(null, client); + caption.addClickListener(this); + content = DOM.createDiv(); + captionNode = DOM.createDiv(); + super.add(caption, captionNode); + DOM.appendChild(captionNode, caption.getElement()); + DOM.appendChild(getElement(), captionNode); + DOM.appendChild(getElement(), content); + setStylePrimaryName(CLASSNAME + "-item"); + DOM.setElementProperty(content, "className", CLASSNAME + + "-item-content"); + DOM.setElementProperty(captionNode, "className", CLASSNAME + + "-item-caption"); + DOM.setStyleAttribute(content, "overflow", "auto"); + DOM.setStyleAttribute(content, "display", "none"); + // Force 'hasLayout' in IE6 (prevents layout problems) + if (Util.isIE6()) { + DOM.setStyleAttribute(content, "zoom", "1"); + } + + caption.updateCaption(tabUidl); + } + + public Element getContainerElement() { + return content; + } + + public Widget getPaintable() { + if (getWidgetCount() > 1) { + return getWidget(1); + } else { + return null; + } + } + + public void open() { + open = true; + DOM.setStyleAttribute(content, "display", ""); + addStyleDependentName("open"); + if (getPaintable() != null) { + add(getPaintable(), content); + } + } + + public void close() { + open = false; + if (getPaintable() != null) { + remove(getPaintable()); + } + DOM.setStyleAttribute(content, "display", "none"); + removeStyleDependentName("open"); + } + + public boolean isOpen() { + return open; + } + + public void setContent(UIDL contentUidl) { + final Paintable newPntbl = client.getPaintable(contentUidl); + // due hack #1 in ITabsheetBase + ((Widget) newPntbl).setVisible(true); + if (getPaintable() == null) { + add((Widget) newPntbl, content); + paintables.add(newPntbl); + } else if (getPaintable() != newPntbl) { + client.unregisterPaintable((Paintable) getWidget(1)); + paintables.remove(getWidget(1)); + remove(1); + add((Widget) newPntbl, content); + paintables.add(newPntbl); + } + paintable = newPntbl; + paintable.updateFromUIDL(contentUidl, client); + } + + public void onClick(Widget sender) { + onSelectTab(this); + } + } + + protected void clearPaintables() { + stack.clear(); + clear(); + } + + protected Iterator getPaintableIterator() { + return paintables.iterator(); + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IButton.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IButton.java new file mode 100644 index 0000000000..68d83f1a13 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IButton.java @@ -0,0 +1,117 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.ClickListener; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.BrowserInfo; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.Tooltip; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class IButton extends Button implements Paintable { + + public static final String CLASSNAME = "i-button"; + + String id; + + ApplicationConnection client; + + private Element errorIndicatorElement; + + private final Element captionElement = DOM.createSpan(); + + private Icon icon; + + public IButton() { + setStyleName(CLASSNAME); + + DOM.appendChild(getElement(), captionElement); + + addClickListener(new ClickListener() { + public void onClick(Widget sender) { + if (id == null || client == null) { + return; + } + /* + * TODO isolata workaround. Safari don't always seem to fire + * onblur previously focused component before button is clicked. + */ + IButton.this.setFocus(true); + client.updateVariable(id, "state", true, true); + } + }); + sinkEvents(Tooltip.TOOLTIP_EVENTS); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + // Ensure correct implementation, + // but don't let container manage caption etc. + if (client.updateComponent(this, uidl, false)) { + return; + } + + // Save details + this.client = client; + id = uidl.getId(); + + // Set text + setText(uidl.getStringAttribute("caption")); + + // handle error + if (uidl.hasAttribute("error")) { + if (errorIndicatorElement == null) { + errorIndicatorElement = DOM.createDiv(); + DOM.setElementProperty(errorIndicatorElement, "className", + "i-errorindicator"); + } + DOM.insertChild(getElement(), errorIndicatorElement, 0); + + // Fix for IE6, IE7 + if (BrowserInfo.get().isIE()) { + DOM.setInnerText(errorIndicatorElement, " "); + } + + } else if (errorIndicatorElement != null) { + DOM.removeChild(getElement(), errorIndicatorElement); + errorIndicatorElement = null; + } + + if (uidl.hasAttribute("readonly")) { + setEnabled(false); + } + + if (uidl.hasAttribute("icon")) { + if (icon == null) { + icon = new Icon(client); + DOM.insertChild(getElement(), icon.getElement(), 0); + } + icon.setUri(uidl.getStringAttribute("icon")); + } else { + if (icon != null) { + DOM.removeChild(getElement(), icon.getElement()); + icon = null; + } + } + } + + public void setText(String text) { + DOM.setInnerText(captionElement, text); + } + + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (client != null) { + client.handleTooltipEvent(event, this); + } + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ICheckBox.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ICheckBox.java new file mode 100644 index 0000000000..191db6023a --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ICheckBox.java @@ -0,0 +1,118 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.ClickListener; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.Tooltip; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class ICheckBox extends com.google.gwt.user.client.ui.CheckBox implements + Paintable, Field { + + public static final String CLASSNAME = "i-checkbox"; + + String id; + + boolean immediate; + + ApplicationConnection client; + + private Element errorIndicatorElement; + + private Icon icon; + + private boolean isBlockMode = false; + + public ICheckBox() { + setStyleName(CLASSNAME); + addClickListener(new ClickListener() { + + public void onClick(Widget sender) { + if (id == null || client == null) { + return; + } + client.updateVariable(id, "state", isChecked(), immediate); + } + + }); + sinkEvents(Tooltip.TOOLTIP_EVENTS); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Save details + this.client = client; + id = uidl.getId(); + + // Ensure correct implementation + if (client.updateComponent(this, uidl, false)) { + return; + } + + if (uidl.hasAttribute("error")) { + if (errorIndicatorElement == null) { + errorIndicatorElement = DOM.createDiv(); + DOM.setElementProperty(errorIndicatorElement, "className", + "i-errorindicator"); + DOM.appendChild(getElement(), errorIndicatorElement); + } + } else if (errorIndicatorElement != null) { + DOM.setStyleAttribute(errorIndicatorElement, "display", "none"); + } + + if (uidl.hasAttribute("readonly")) { + setEnabled(false); + } + + if (uidl.hasAttribute("icon")) { + if (icon == null) { + icon = new Icon(client); + DOM.insertChild(getElement(), icon.getElement(), 1); + } + icon.setUri(uidl.getStringAttribute("icon")); + } else if (icon != null) { + // detach icon + DOM.removeChild(getElement(), icon.getElement()); + icon = null; + } + + // Set text + setText(uidl.getStringAttribute("caption")); + setChecked(uidl.getBooleanVariable("state")); + immediate = uidl.getBooleanAttribute("immediate"); + } + + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (client != null) { + client.handleTooltipEvent(event, this); + } + } + + public void setWidth(String width) { + setBlockMode(); + super.setWidth(width); + } + + public void setHeight(String height) { + setBlockMode(); + super.setHeight(height); + } + + /** + * makes container element (span) to be block element to enable sizing. + */ + private void setBlockMode() { + if (!isBlockMode) { + DOM.setStyleAttribute(getElement(), "display", "block"); + isBlockMode = true; + } + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ICustomComponent.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ICustomComponent.java new file mode 100644 index 0000000000..fcd4d4796b --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ICustomComponent.java @@ -0,0 +1,64 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Container; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class ICustomComponent extends SimplePanel implements Container { + + private static final String CLASSNAME = "i-customcomponent"; + + public ICustomComponent() { + super(); + setStyleName(CLASSNAME); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, false)) { + return; + } + + final UIDL child = uidl.getChildUIDL(0); + if (child != null) { + final Paintable p = client.getPaintable(child); + if (p != getWidget()) { + if (getWidget() != null) { + client.unregisterPaintable((Paintable) getWidget()); + clear(); + } + setWidget((Widget) p); + } + p.updateFromUIDL(child, client); + } + + } + + public boolean hasChildComponent(Widget component) { + if (getWidget() == component) { + return true; + } else { + return false; + } + } + + public void replaceChildComponent(Widget oldComponent, Widget newComponent) { + if (hasChildComponent(oldComponent)) { + clear(); + setWidget(newComponent); + } else { + throw new IllegalStateException(); + } + } + + public void updateCaption(Paintable component, UIDL uidl) { + // TODO custom component could handle its composition roots caption + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ICustomLayout.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ICustomLayout.java new file mode 100644 index 0000000000..366257fcaa --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ICustomLayout.java @@ -0,0 +1,463 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.HashMap; +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.Element; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Caption; +import com.itmill.toolkit.terminal.gwt.client.CaptionWrapper; +import com.itmill.toolkit.terminal.gwt.client.Container; +import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +/** + * Custom Layout implements complex layout defined with HTML template. + * + * @author IT Mill + * + */ +public class ICustomLayout extends ComplexPanel implements Paintable, + Container, ContainerResizedListener { + + public static final String CLASSNAME = "i-customlayout"; + + /** Location-name to containing element in DOM map */ + private final HashMap locationToElement = new HashMap(); + + /** Location-name to contained widget map */ + private final HashMap locationToWidget = new HashMap(); + + /** Widget to captionwrapper map */ + private final HashMap widgetToCaptionWrapper = new HashMap(); + + /** Currently rendered style */ + String currentTemplate; + + /** Unexecuted scripts loaded from the template */ + private String scripts = ""; + + /** Paintable ID of this paintable */ + private String pid; + + private ApplicationConnection client; + + public ICustomLayout() { + setElement(DOM.createDiv()); + // Clear any unwanted styling + DOM.setStyleAttribute(getElement(), "border", "none"); + DOM.setStyleAttribute(getElement(), "margin", "0"); + DOM.setStyleAttribute(getElement(), "padding", "0"); + setStyleName(CLASSNAME); + } + + /** + * Sets widget to given location. + * + * If location already contains a widget it will be removed. + * + * @param widget + * Widget to be set into location. + * @param location + * location name where widget will be added + * + * @throws IllegalArgumentException + * if no such location is found in the layout. + */ + public void setWidget(Widget widget, String location) { + + if (widget == null) { + return; + } + + // If no given location is found in the layout, and exception is throws + Element elem = (Element) locationToElement.get(location); + if (elem == null && hasTemplate()) { + throw new IllegalArgumentException("No location " + location + + " found"); + } + + // Get previous widget + final Widget previous = (Widget) locationToWidget.get(location); + // NOP if given widget already exists in this location + if (previous == widget) { + return; + } + remove(previous); + + // if template is missing add element in order + if (!hasTemplate()) { + elem = getElement(); + } + + // Add widget to location + super.add(widget, elem); + locationToWidget.put(location, widget); + } + + /** Update the layout from UIDL */ + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + this.client = client; + // Client manages general cases + if (client.updateComponent(this, uidl, false)) { + return; + } + + // Update PID + pid = uidl.getId(); + if (!hasTemplate()) { + // Update HTML template only once + initializeHTML(uidl, client); + } + + // Set size + if (uidl.hasAttribute("width")) { + setWidth(uidl.getStringAttribute("width")); + } else { + setWidth("100%"); + } + if (uidl.hasAttribute("height")) { + setHeight(uidl.getStringAttribute("height")); + } else { + setHeight("100%"); + } + + // Evaluate scripts + eval(scripts); + scripts = null; + + iLayout(); + + Set oldWidgets = new HashSet(); + oldWidgets.addAll(locationToWidget.values()); + + // For all contained widgets + for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { + final UIDL uidlForChild = (UIDL) i.next(); + if (uidlForChild.getTag().equals("location")) { + final String location = uidlForChild.getStringAttribute("name"); + final Paintable child = client.getPaintable(uidlForChild + .getChildUIDL(0)); + try { + setWidget((Widget) child, location); + child.updateFromUIDL(uidlForChild.getChildUIDL(0), client); + } catch (final IllegalArgumentException e) { + // If no location is found, this component is not visible + } + oldWidgets.remove(child); + } + } + for (Iterator iterator = oldWidgets.iterator(); iterator.hasNext();) { + Widget oldWidget = (Widget) iterator.next(); + if (oldWidget.isAttached()) { + // slot of this widget is emptied, remove it + remove(oldWidget); + } + } + + iLayout(); + } + + /** Initialize HTML-layout. */ + private void initializeHTML(UIDL uidl, ApplicationConnection client) { + + final String newTemplate = uidl.getStringAttribute("template"); + + // Get the HTML-template from client + String template = client + .getResource("layouts/" + newTemplate + ".html"); + if (template == null) { + template = "Layout file layouts/" + + newTemplate + + ".html is missing. Components will be drawn for debug purposes."; + } else { + currentTemplate = newTemplate; + } + + // Connect body of the template to DOM + template = extractBodyAndScriptsFromTemplate(template); + DOM.setInnerHTML(getElement(), template); + + // Remap locations to elements + locationToElement.clear(); + scanForLocations(getElement()); + + String themeUri = client.getThemeUri(); + prefixImgSrcs(getElement(), themeUri + "/layouts/"); + + publishResizedFunction(DOM.getFirstChild(getElement())); + + } + + private native boolean uriEndsWithSlash() + /*-{ + var path = $wnd.location.pathname; + if(path.charAt(path.length - 1) == "/") + return true; + return false; + }-*/; + + private boolean hasTemplate() { + if (currentTemplate == null) { + return false; + } else { + return true; + } + } + + /** Collect locations from template */ + private void scanForLocations(Element elem) { + + final String location = getLocation(elem); + if (location != null) { + locationToElement.put(location, elem); + DOM.setInnerHTML(elem, ""); + } else { + final int len = DOM.getChildCount(elem); + for (int i = 0; i < len; i++) { + scanForLocations(DOM.getChild(elem, i)); + } + } + } + + /** Get the location attribute for given element */ + private static native String getLocation(Element elem) + /*-{ + return elem.getAttribute("location"); + }-*/; + + /** Evaluate given script in browser document */ + private static native void eval(String script) + /*-{ + try { + if (script != null) + eval("{ var document = $doc; var window = $wnd; "+ script + "}"); + } catch (e) { + } + }-*/; + + /** Prefix all img tag srcs with given prefix. */ + private static native void prefixImgSrcs(Element e, String srcPrefix) + /*-{ + try { + var divs = e.getElementsByTagName("img"); + var base = "" + $doc.location; + var l = base.length-1; + while (l >= 0 && base.charAt(l) != "/") l--; + base = base.substring(0,l+1); + for (var i = 0; i < divs.length; i++) { + var div = divs[i]; + var src = div.getAttribute("src"); + if (src.indexOf("/")==0 || src.match(/\w+:\/\//)) { + continue; + } + div.setAttribute("src",srcPrefix + src); + } + } catch (e) { alert(e + " " + srcPrefix);} + }-*/; + + /** + * Extract body part and script tags from raw html-template. + * + * Saves contents of all script-tags to private property: scripts. Returns + * contents of the body part for the html without script-tags. Also replaces + * all _UID_ tags with an unique id-string. + * + * @param html + * Original HTML-template received from server + * @return html that is used to create the HTMLPanel. + */ + private String extractBodyAndScriptsFromTemplate(String html) { + + // Replace UID:s + html = html.replaceAll("_UID_", pid + "__"); + + // Exctract script-tags + scripts = ""; + int endOfPrevScript = 0; + int nextPosToCheck = 0; + String lc = html.toLowerCase(); + String res = ""; + int scriptStart = lc.indexOf(" 0) { + res += html.substring(endOfPrevScript, scriptStart); + scriptStart = lc.indexOf(">", scriptStart); + final int j = lc.indexOf("", 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; + } + + /** Replace child components */ + public void replaceChildComponent(Widget from, Widget to) { + final String location = getLocation(from); + if (location == null) { + throw new IllegalArgumentException(); + } + setWidget(to, location); + } + + /** Does this layout contain given child */ + public boolean hasChildComponent(Widget component) { + return locationToWidget.containsValue(component); + } + + /** Update caption for given widget */ + public void updateCaption(Paintable component, UIDL uidl) { + CaptionWrapper wrapper = (CaptionWrapper) widgetToCaptionWrapper + .get(component); + if (Caption.isNeeded(uidl)) { + if (wrapper == null) { + final String loc = getLocation((Widget) component); + super.remove((Widget) component); + wrapper = new CaptionWrapper(component, client); + super.add(wrapper, (Element) locationToElement.get(loc)); + widgetToCaptionWrapper.put(component, wrapper); + } + wrapper.updateCaption(uidl); + } else { + if (wrapper != null) { + final String loc = getLocation((Widget) component); + super.remove(wrapper); + super.add((Widget) wrapper.getPaintable(), + (Element) locationToElement.get(loc)); + widgetToCaptionWrapper.remove(component); + } + } + } + + /** Get the location of an widget */ + public String getLocation(Widget w) { + for (final Iterator i = locationToWidget.keySet().iterator(); i + .hasNext();) { + final String location = (String) i.next(); + if (locationToWidget.get(location) == w) { + return location; + } + } + return null; + } + + /** Removes given widget from the layout */ + public boolean remove(Widget w) { + client.unregisterPaintable((Paintable) w); + final String location = getLocation(w); + if (location != null) { + locationToWidget.remove(location); + } + final CaptionWrapper cw = (CaptionWrapper) widgetToCaptionWrapper + .get(w); + if (cw != null) { + widgetToCaptionWrapper.remove(w); + return super.remove(cw); + } else if (w != null) { + return super.remove(w); + } + return false; + } + + /** Adding widget without specifying location is not supported */ + public void add(Widget w) { + throw new UnsupportedOperationException(); + } + + /** Clear all widgets from the layout */ + public void clear() { + super.clear(); + locationToWidget.clear(); + widgetToCaptionWrapper.clear(); + } + + public void iLayout() { + if (!iLayoutJS(DOM.getFirstChild(getElement()))) { + Util.runDescendentsLayout(this); + } + } + + /** + * 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() { + Util.runDescendentsLayout(this); + } + + public void onDetach() { + detachResizedFunction(DOM.getFirstChild(getElement())); + } + + private native void detachResizedFunction(Element element) + /*-{ + element.notifyChildrenOfSizeChange = null; + }-*/; + + private native void publishResizedFunction(Element element) + /*-{ + var self = this; + element.notifyChildrenOfSizeChange = function() { + self.@com.itmill.toolkit.terminal.gwt.client.ui.ICustomLayout::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. + * + * @param el + * @return true if layout function exists and was run successfully, else + * false. + */ + private native boolean iLayoutJS(Element el) + /*-{ + if(el && el.iLayoutJS) { + try { + el.iLayoutJS(); + return true; + } catch (e) { + return false; + } + } else { + return false; + } + }-*/; +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IDateField.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IDateField.java new file mode 100644 index 0000000000..123ded4515 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IDateField.java @@ -0,0 +1,231 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.Date; + +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.FlowPanel; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.DateTimeService; +import com.itmill.toolkit.terminal.gwt.client.LocaleNotLoadedException; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.Tooltip; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class IDateField extends FlowPanel implements Paintable, Field { + + public static final String CLASSNAME = "i-datefield"; + + protected String id; + + protected ApplicationConnection client; + + protected boolean immediate; + + public static final int RESOLUTION_YEAR = 0; + public static final int RESOLUTION_MONTH = 1; + public static final int RESOLUTION_DAY = 2; + public static final int RESOLUTION_HOUR = 3; + public static final int RESOLUTION_MIN = 4; + public static final int RESOLUTION_SEC = 5; + public static final int RESOLUTION_MSEC = 6; + + protected int currentResolution = RESOLUTION_YEAR; + + protected String currentLocale; + + protected boolean readonly; + + protected boolean enabled; + + protected Date date = null; + // e.g when paging a calendar, before actually selecting + protected Date showingDate = new Date(); + + protected DateTimeService dts; + + public IDateField() { + setStyleName(CLASSNAME); + dts = new DateTimeService(); + sinkEvents(Tooltip.TOOLTIP_EVENTS); + } + + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (client != null) { + client.handleTooltipEvent(event, this); + } + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Ensure correct implementation and let layout manage caption + if (client.updateComponent(this, uidl, true)) { + return; + } + + // Save details + this.client = client; + id = uidl.getId(); + immediate = uidl.getBooleanAttribute("immediate"); + + readonly = uidl.getBooleanAttribute("readonly"); + enabled = !uidl.getBooleanAttribute("disabled"); + + if (uidl.hasAttribute("locale")) { + final String locale = uidl.getStringAttribute("locale"); + try { + dts.setLocale(locale); + currentLocale = locale; + } catch (final LocaleNotLoadedException e) { + currentLocale = dts.getLocale(); + // TODO redirect this to console + System.out.println("Tried to use an unloaded locale \"" + + locale + "\". Using default locale (" + currentLocale + + ")."); + } + } + + int newResolution; + if (uidl.hasVariable("msec")) { + newResolution = RESOLUTION_MSEC; + } else if (uidl.hasVariable("sec")) { + newResolution = RESOLUTION_SEC; + } else if (uidl.hasVariable("min")) { + newResolution = RESOLUTION_MIN; + } else if (uidl.hasVariable("hour")) { + newResolution = RESOLUTION_HOUR; + } else if (uidl.hasVariable("day")) { + newResolution = RESOLUTION_DAY; + } else if (uidl.hasVariable("month")) { + newResolution = RESOLUTION_MONTH; + } else { + newResolution = RESOLUTION_YEAR; + } + + currentResolution = newResolution; + + final int year = uidl.getIntVariable("year"); + final int month = (currentResolution >= RESOLUTION_MONTH) ? uidl + .getIntVariable("month") : -1; + final int day = (currentResolution >= RESOLUTION_DAY) ? uidl + .getIntVariable("day") : -1; + final int hour = (currentResolution >= RESOLUTION_HOUR) ? uidl + .getIntVariable("hour") : 0; + final int min = (currentResolution >= RESOLUTION_MIN) ? uidl + .getIntVariable("min") : 0; + final int sec = (currentResolution >= RESOLUTION_SEC) ? uidl + .getIntVariable("sec") : 0; + final int msec = (currentResolution >= RESOLUTION_MSEC) ? uidl + .getIntVariable("msec") : 0; + + // Construct new date for this datefield (only if not null) + if (year > -1) { + date = new Date((long) getTime(year, month, day, hour, min, sec, + msec)); + showingDate.setTime(date.getTime()); + } else { + date = null; + showingDate = new Date(); + } + + } + + /* + * We need this redundant native function because Java's Date object doesn't + * have a setMilliseconds method. + */ + private 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 (int) (date.getTime() - date.getTime() / 1000 * 1000); + } + + public void setMilliseconds(int ms) { + date.setTime(date.getTime() / 1000 * 1000 + ms); + } + + public int getShowingMilliseconds() { + return (int) (showingDate.getTime() - showingDate.getTime() / 1000 * 1000); + } + + public void setShowingMilliseconds(int ms) { + showingDate.setTime(showingDate.getTime() / 1000 * 1000 + ms); + } + + public int getCurrentResolution() { + return currentResolution; + } + + public void setCurrentResolution(int 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 Date getShowingDate() { + return showingDate; + } + + public void setShowingDate(Date date) { + showingDate = date; + } + + public boolean isImmediate() { + return immediate; + } + + public boolean isReadonly() { + return readonly; + } + + public boolean isEnabled() { + return enabled; + } + + public DateTimeService getDateTimeService() { + return dts; + } + + public String getId() { + return id; + } + + public ApplicationConnection getClient() { + return client; + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IDateFieldCalendar.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IDateFieldCalendar.java new file mode 100644 index 0000000000..895b2c7858 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IDateFieldCalendar.java @@ -0,0 +1,25 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class IDateFieldCalendar extends IDateField { + + private final CalendarPanel date; + + public IDateFieldCalendar() { + super(); + date = new CalendarPanel(this); + add(date); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + super.updateFromUIDL(uidl, client); + date.updateCalendar(); + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IEmbedded.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IEmbedded.java new file mode 100644 index 0000000000..d1df5601e8 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IEmbedded.java @@ -0,0 +1,127 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.HTML; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class IEmbedded extends HTML implements Paintable { + private static String CLASSNAME = "i-embedded"; + + private String heigth; + private String width; + private Element browserElement; + + public IEmbedded() { + setStyleName(CLASSNAME); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, true)) { + return; + } + + boolean clearBrowserElement = true; + + if (uidl.hasAttribute("type")) { + final String type = uidl.getStringAttribute("type"); + if (type.equals("image")) { + String w = uidl.getStringAttribute("width"); + if (w != null) { + w = " width=\"" + w + "\" "; + } else { + w = ""; + } + String h = uidl.getStringAttribute("height"); + if (h != null) { + h = " height=\"" + h + "\" "; + } else { + h = ""; + } + setHTML(""); + client.addPngFix(DOM.getFirstChild(getElement())); + + } else if (type.equals("browser")) { + if (browserElement == null) { + setHTML(""); + browserElement = DOM.getFirstChild(getElement()); + } else { + DOM.setElementAttribute(browserElement, "src", getSrc(uidl, + client)); + } + clearBrowserElement = false; + } else { + ApplicationConnection.getConsole().log( + "Unknown Embedded type '" + type + "'"); + } + } else if (uidl.hasAttribute("mimetype")) { + final String mime = uidl.getStringAttribute("mimetype"); + if (mime.equals("application/x-shockwave-flash")) { + setHTML(""); + } else { + ApplicationConnection.getConsole().log( + "Unknown Embedded mimetype '" + mime + "'"); + } + } else { + ApplicationConnection.getConsole().log( + "Unknown Embedded; no type or mimetype attribute"); + } + + if (clearBrowserElement) { + browserElement = null; + } + + } + + /** + * Helper to return translated src-attribute from embedded's UIDL + * + * @param uidl + * @param client + * @return + */ + private String getSrc(UIDL uidl, ApplicationConnection client) { + String url = client.translateToolkitUri(uidl.getStringAttribute("src")); + if (url == null) { + return ""; + } + return url; + } + + public void setWidth(String width) { + if (width == null || width.equals("")) { + width = "100%"; + } + this.width = width; + super.setHeight(width); + } + + public void setHeight(String height) { + if (height == null || height.equals("")) { + height = "100%"; + } + heigth = height; + super.setHeight(height); + } + + protected void onDetach() { + // Force browser to fire unload event when component is detached from + // the view (IE doesn't do this automatically) + if (browserElement != null) { + DOM.setElementAttribute(browserElement, "src", "javascript:false"); + } + super.onDetach(); + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IExpandLayout.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IExpandLayout.java new file mode 100644 index 0000000000..e4f86ec1cb --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IExpandLayout.java @@ -0,0 +1,726 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.DeferredCommand; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.ComplexPanel; +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.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.BrowserInfo; +import com.itmill.toolkit.terminal.gwt.client.Caption; +import com.itmill.toolkit.terminal.gwt.client.Container; +import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.StyleConstants; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +/** + * @author IT Mill Ltd + */ +public class IExpandLayout extends ComplexPanel implements + ContainerResizedListener, Container { + + public static final String CLASSNAME = "i-expandlayout"; + public static final int ORIENTATION_HORIZONTAL = 1; + + public static final int ORIENTATION_VERTICAL = 0; + + /** + * Minimum pixels reserved for expanded element to avoid "odd" situations + * where expanded element is 0 size. Default is 5 pixels to show user a hint + * that there is a component. Then user can often use splitpanel or resize + * window to show component properly. This value may be insane in some + * applications. Override this to specify a proper for your case. + */ + protected static final int EXPANDED_ELEMENTS_MIN_WIDTH = 5; + + /** + * Contains reference to Element where Paintables are wrapped. + */ + protected Element childContainer; + + protected ApplicationConnection client; + + protected HashMap componentToCaption = new HashMap(); + + /* + * Elements that provides the Layout interface implementation. + */ + protected Element element; + private Widget expandedWidget; + + private UIDL expandedWidgetUidl; + + int orientationMode = ORIENTATION_VERTICAL; + + protected int topMargin = -1; + private String width; + private String height; + private Element marginElement; + private Element breakElement; + private int bottomMargin = -1; + private boolean hasComponentSpacing; + private int spacingSize = -1; + + public IExpandLayout() { + this(IExpandLayout.ORIENTATION_VERTICAL); + } + + public IExpandLayout(int orientation) { + orientationMode = orientation; + constructDOM(); + setStyleName(CLASSNAME); + } + + public void add(Widget w) { + final WidgetWrapper wrapper = createWidgetWrappper(); + DOM.appendChild(childContainer, wrapper.getElement()); + super.add(w, wrapper.getContainerElement()); + } + + protected void constructDOM() { + element = DOM.createDiv(); + // DOM.setStyleAttribute(element, "overflow", "hidden"); + + if (orientationMode == ORIENTATION_HORIZONTAL) { + marginElement = DOM.createDiv(); + if (BrowserInfo.get().isIE()) { + DOM.setStyleAttribute(marginElement, "zoom", "1"); + DOM.setStyleAttribute(marginElement, "overflow", "hidden"); + } + childContainer = DOM.createDiv(); + if (BrowserInfo.get().isIE()) { + DOM.setStyleAttribute(childContainer, "zoom", "1"); + DOM.setStyleAttribute(childContainer, "overflow", "hidden"); + } + DOM.setStyleAttribute(childContainer, "height", "100%"); + breakElement = DOM.createDiv(); + DOM.setStyleAttribute(breakElement, "overflow", "hidden"); + DOM.setStyleAttribute(breakElement, "height", "0px"); + DOM.setStyleAttribute(breakElement, "clear", "both"); + DOM.appendChild(marginElement, childContainer); + DOM.appendChild(marginElement, breakElement); + DOM.appendChild(element, marginElement); + } else { + childContainer = DOM.createDiv(); + DOM.appendChild(element, childContainer); + marginElement = childContainer; + } + setElement(element); + } + + protected WidgetWrapper createWidgetWrappper() { + if (orientationMode == ORIENTATION_HORIZONTAL) { + return new HorizontalWidgetWrapper(); + } else { + return new VerticalWidgetWrapper(); + } + } + + /** + * Returns given widgets WidgetWrapper + * + * @param child + * @return + */ + public WidgetWrapper getWidgetWrapperFor(Widget child) { + final Element containerElement = DOM.getParent(child.getElement()); + if (orientationMode == ORIENTATION_HORIZONTAL) { + return new HorizontalWidgetWrapper(containerElement); + } else { + return new VerticalWidgetWrapper(containerElement); + } + } + + abstract class WidgetWrapper extends UIObject { + /** + * @return element that contains Widget + */ + public Element getContainerElement() { + return getElement(); + } + + abstract void setExpandedSize(int pixels); + + abstract void setAlignment(String verticalAlignment, + String horizontalAlignment); + + abstract void setSpacingEnabled(boolean b); + } + + class VerticalWidgetWrapper extends WidgetWrapper { + + public VerticalWidgetWrapper(Element div) { + setElement(div); + } + + public VerticalWidgetWrapper() { + setElement(DOM.createDiv()); + // Set to 'hidden' at first (prevent IE6 content overflows), and set + // to 'auto' later. + DOM.setStyleAttribute(getContainerElement(), "overflow", "hidden"); + } + + void setExpandedSize(int pixels) { + final int spaceForMarginsAndSpacings = getOffsetHeight() + - DOM.getElementPropertyInt(getElement(), "clientHeight"); + int fixedInnerSize = pixels - spaceForMarginsAndSpacings; + if (fixedInnerSize < 0) { + fixedInnerSize = 0; + } + setHeight(fixedInnerSize + "px"); + DOM.setStyleAttribute(getContainerElement(), "overflow", "auto"); + } + + void setAlignment(String verticalAlignment, String horizontalAlignment) { + DOM.setStyleAttribute(getElement(), "textAlign", + horizontalAlignment); + // ignoring vertical alignment + } + + void setSpacingEnabled(boolean b) { + setStyleName(getElement(), CLASSNAME + "-" + + StyleConstants.VERTICAL_SPACING, b); + } + } + + class HorizontalWidgetWrapper extends WidgetWrapper { + + private Element td; + private String valign = "top"; + private String align = "left"; + + public HorizontalWidgetWrapper(Element element) { + if (DOM.getElementProperty(element, "nodeName").equals("TD")) { + td = element; + setElement(DOM.getParent(DOM.getParent(DOM.getParent(DOM + .getParent(td))))); + } else { + setElement(element); + } + } + + public HorizontalWidgetWrapper() { + setElement(DOM.createDiv()); + DOM.setStyleAttribute(getElement(), "cssFloat", "left"); + if (BrowserInfo.get().isIE()) { + DOM.setStyleAttribute(getElement(), "styleFloat", "left"); + } + DOM.setStyleAttribute(getElement(), "height", "100%"); + } + + void setExpandedSize(int pixels) { + setWidth(pixels + "px"); + DOM.setStyleAttribute(getElement(), "overflow", "hidden"); + } + + void setAlignment(String verticalAlignment, String horizontalAlignment) { + DOM.setStyleAttribute(getElement(), "verticalAlign", + verticalAlignment); + if (!valign.equals(verticalAlignment)) { + if (verticalAlignment.equals("top")) { + // remove table, move content to div + + } else { + if (td == null) { + // build one cell table + final Element table = DOM.createTable(); + final Element tBody = DOM.createTBody(); + final Element tr = DOM.createTR(); + td = DOM.createTD(); + DOM.appendChild(table, tBody); + DOM.appendChild(tBody, tr); + DOM.appendChild(tr, td); + DOM.setElementProperty(table, "className", CLASSNAME + + "-valign"); + DOM.setElementProperty(tr, "className", CLASSNAME + + "-valign"); + DOM.setElementProperty(td, "className", CLASSNAME + + "-valign"); + // move possible content to cell + final Element content = DOM.getFirstChild(getElement()); + if (content != null) { + DOM.removeChild(getElement(), content); + DOM.appendChild(td, content); + } + DOM.appendChild(getElement(), table); + } + // set alignment + DOM.setStyleAttribute(td, "verticalAlign", + verticalAlignment); + } + valign = verticalAlignment; + } + if (!align.equals(horizontalAlignment)) { + DOM.setStyleAttribute(getContainerElement(), "textAlign", + horizontalAlignment); + align = horizontalAlignment; + } + } + + public Element getContainerElement() { + if (td == null) { + return super.getContainerElement(); + } else { + return td; + } + } + + void setSpacingEnabled(boolean b) { + setStyleName(getElement(), CLASSNAME + "-" + + StyleConstants.HORIZONTAL_SPACING, b); + } + } + + protected ArrayList getPaintables() { + final ArrayList al = new ArrayList(); + final Iterator it = iterator(); + while (it.hasNext()) { + final Widget w = (Widget) it.next(); + if (w instanceof Paintable) { + al.add(w); + } + } + return al; + } + + public Widget getWidget(int index) { + return getChildren().get(index); + } + + public int getWidgetCount() { + return getChildren().size(); + } + + public int getWidgetIndex(Widget child) { + return getChildren().indexOf(child); + } + + protected void handleAlignments(UIDL uidl) { + // Component alignments as a comma separated list. + // See com.itmill.toolkit.terminal.gwt.client.ui.AlignmentInfo.java for + // possible values. + final int[] alignments = uidl.getIntArrayAttribute("alignments"); + int alignmentIndex = 0; + // Set alignment attributes + final Iterator it = getPaintables().iterator(); + boolean first = true; + while (it.hasNext()) { + // Calculate alignment info + final AlignmentInfo ai = new AlignmentInfo( + alignments[alignmentIndex++]); + final WidgetWrapper wr = getWidgetWrapperFor((Widget) it.next()); + wr.setAlignment(ai.getVerticalAlignment(), ai + .getHorizontalAlignment()); + if (first) { + wr.setSpacingEnabled(false); + first = false; + } else { + wr.setSpacingEnabled(hasComponentSpacing); + } + + } + } + + protected void handleMargins(UIDL uidl) { + if (uidl.hasAttribute("margins")) { + final MarginInfo margins = new MarginInfo(uidl + .getIntAttribute("margins")); + setStyleName(marginElement, CLASSNAME + "-" + + StyleConstants.MARGIN_TOP, margins.hasTop()); + setStyleName(marginElement, CLASSNAME + "-" + + StyleConstants.MARGIN_RIGHT, margins.hasRight()); + setStyleName(marginElement, CLASSNAME + "-" + + StyleConstants.MARGIN_BOTTOM, margins.hasBottom()); + setStyleName(marginElement, CLASSNAME + "-" + + StyleConstants.MARGIN_LEFT, margins.hasLeft()); + } + } + + public boolean hasChildComponent(Widget component) { + return getWidgetIndex(component) >= 0; + } + + public void iLayout() { + if (orientationMode == ORIENTATION_HORIZONTAL) { + int pixels; + if ("".equals(height)) { + // try to find minimum height by looping all widgets + int maxHeight = 0; + Iterator iterator = getPaintables().iterator(); + while (iterator.hasNext()) { + Widget w = (Widget) iterator.next(); + int h = w.getOffsetHeight(); + if (h > maxHeight) { + maxHeight = h; + } + } + pixels = maxHeight; + } else { + pixels = getOffsetHeight() - getTopMargin() - getBottomMargin(); + if (pixels < 0) { + pixels = 0; + } + } + DOM.setStyleAttribute(marginElement, "height", pixels + "px"); + DOM.setStyleAttribute(marginElement, "overflow", "hidden"); + } + + if (expandedWidget == null) { + return; + } + + final int availableSpace = getAvailableSpace(); + + final int usedSpace = getUsedSpace(); + + int spaceForExpandedWidget = availableSpace - usedSpace; + + if (spaceForExpandedWidget < EXPANDED_ELEMENTS_MIN_WIDTH) { + // TODO fire warning for developer + spaceForExpandedWidget = EXPANDED_ELEMENTS_MIN_WIDTH; + } + + final WidgetWrapper wr = getWidgetWrapperFor(expandedWidget); + wr.setExpandedSize(spaceForExpandedWidget); + + // TODO save previous size and only propagate if really changed + Util.runDescendentsLayout(this); + } + + private int getTopMargin() { + if (topMargin < 0) { + topMargin = DOM.getElementPropertyInt(childContainer, "offsetTop") + - DOM.getElementPropertyInt(getElement(), "offsetTop"); + } + if (topMargin < 0) { + // FIXME shouldn't happen + return 0; + } else { + return topMargin; + } + } + + private int getBottomMargin() { + if (bottomMargin < 0) { + bottomMargin = DOM + .getElementPropertyInt(marginElement, "offsetTop") + + DOM.getElementPropertyInt(marginElement, "offsetHeight") + - DOM.getElementPropertyInt(breakElement, "offsetTop"); + if (bottomMargin < 0) { + // FIXME shouldn't happen + return 0; + } + } + return bottomMargin; + } + + private int getUsedSpace() { + int total = 0; + final int widgetCount = getWidgetCount(); + final Iterator it = iterator(); + while (it.hasNext()) { + final Widget w = (Widget) it.next(); + if (w != expandedWidget) { + final WidgetWrapper wr = getWidgetWrapperFor(w); + if (orientationMode == ORIENTATION_VERTICAL) { + total += wr.getOffsetHeight(); + } else { + total += wr.getOffsetWidth(); + } + } + } + total += getSpacingSize() * (widgetCount - 1); + return total; + } + + private int getSpacingSize() { + if (hasComponentSpacing) { + if (spacingSize < 0) { + final Element temp = DOM.createDiv(); + final WidgetWrapper wr = createWidgetWrappper(); + wr.setSpacingEnabled(true); + DOM.appendChild(temp, wr.getElement()); + DOM.setStyleAttribute(temp, "position", "absolute"); + DOM.setStyleAttribute(temp, "top", "0"); + DOM.setStyleAttribute(temp, "visibility", "hidden"); + DOM.appendChild(RootPanel.getBodyElement(), temp); + if (orientationMode == ORIENTATION_HORIZONTAL) { + spacingSize = DOM.getElementPropertyInt(wr.getElement(), + "offsetLeft"); + } else { + spacingSize = DOM.getElementPropertyInt(wr.getElement(), + "offsetTop"); + } + DOM.removeChild(RootPanel.getBodyElement(), temp); + } + return spacingSize; + } else { + return 0; + } + } + + private int getAvailableSpace() { + int size; + if (orientationMode == ORIENTATION_VERTICAL) { + if (BrowserInfo.get().isIE6()) { + DOM.setStyleAttribute(getElement(), "overflow", "hidden"); + } + size = getOffsetHeight(); + if (BrowserInfo.get().isIE6()) { + DOM.setStyleAttribute(getElement(), "overflow", "visible"); + } + + final int marginTop = DOM.getElementPropertyInt(DOM + .getFirstChild(marginElement), "offsetTop") + - DOM.getElementPropertyInt(element, "offsetTop"); + + final Element lastElement = DOM.getChild(marginElement, (DOM + .getChildCount(marginElement) - 1)); + final int marginBottom = DOM.getElementPropertyInt(marginElement, + "offsetHeight") + + DOM.getElementPropertyInt(marginElement, "offsetTop") + - (DOM.getElementPropertyInt(lastElement, "offsetTop") + DOM + .getElementPropertyInt(lastElement, "offsetHeight")); + size -= (marginTop + marginBottom); + } else { + // horizontal mode + size = DOM.getElementPropertyInt(childContainer, "offsetWidth"); + } + return size; + } + + protected void insert(Widget w, int beforeIndex) { + if (w instanceof Caption) { + final Caption c = (Caption) w; + // captions go into same container element as their + // owners + final Element container = DOM.getParent(((UIObject) c.getOwner()) + .getElement()); + final Element captionContainer = DOM.createDiv(); + DOM.insertChild(container, captionContainer, 0); + insert(w, captionContainer, beforeIndex, false); + } else { + final WidgetWrapper wrapper = createWidgetWrappper(); + DOM.insertChild(childContainer, wrapper.getElement(), beforeIndex); + insert(w, wrapper.getContainerElement(), beforeIndex, false); + } + } + + public boolean remove(int index) { + return remove(getWidget(index)); + } + + public boolean remove(Widget w) { + final WidgetWrapper ww = getWidgetWrapperFor(w); + final boolean removed = super.remove(w); + if (removed) { + if (!(w instanceof Caption)) { + DOM.removeChild(childContainer, ww.getElement()); + } + return true; + } + return false; + } + + public void removeCaption(Widget w) { + final Caption c = (Caption) componentToCaption.get(w); + if (c != null) { + this.remove(c); + componentToCaption.remove(w); + } + } + + public boolean removePaintable(Paintable p) { + final Caption c = (Caption) componentToCaption.get(p); + if (c != null) { + componentToCaption.remove(c); + remove(c); + } + client.unregisterPaintable(p); + if (expandedWidget == p) { + expandedWidget = null; + } + return remove((Widget) p); + } + + public void replaceChildComponent(Widget from, Widget to) { + client.unregisterPaintable((Paintable) from); + final Caption c = (Caption) componentToCaption.get(from); + if (c != null) { + remove(c); + componentToCaption.remove(c); + } + final int index = getWidgetIndex(from); + if (index >= 0) { + remove(index); + insert(to, index); + } + } + + public void updateCaption(Paintable component, UIDL uidl) { + + Caption c = (Caption) componentToCaption.get(component); + + if (Caption.isNeeded(uidl)) { + if (c == null) { + final int index = getWidgetIndex((Widget) component); + c = new Caption(component, client); + insert(c, index); + componentToCaption.put(component, c); + } + c.updateCaption(uidl); + } else { + if (c != null) { + remove(c); + componentToCaption.remove(component); + } + } + } + + public void setWidth(String newWidth) { + if (newWidth.equals(width)) { + return; + } + width = newWidth; + super.setWidth(width); + } + + public void setHeight(String newHeight) { + if (newHeight.equals(height)) { + return; + } + height = newHeight; + super.setHeight(height); + if (orientationMode == ORIENTATION_HORIZONTAL) { + iLayout(); + } + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + this.client = client; + + // Modify layout margins + handleMargins(uidl); + + // Ensure correct implementation + if (client.updateComponent(this, uidl, false)) { + return; + } + + hasComponentSpacing = uidl.getBooleanAttribute("spacing"); + + final ArrayList uidlWidgets = new ArrayList(); + for (final Iterator it = uidl.getChildIterator(); it.hasNext();) { + final UIDL cellUidl = (UIDL) it.next(); + final Paintable child = client.getPaintable(cellUidl + .getChildUIDL(0)); + uidlWidgets.add(child); + if (cellUidl.hasAttribute("expanded")) { + expandedWidget = (Widget) child; + expandedWidgetUidl = cellUidl.getChildUIDL(0); + } + } + + final ArrayList oldWidgets = getPaintables(); + + final Iterator oldIt = oldWidgets.iterator(); + final Iterator newIt = uidlWidgets.iterator(); + final Iterator newUidl = uidl.getChildIterator(); + + Widget oldChild = null; + while (newIt.hasNext()) { + final Widget child = (Widget) newIt.next(); + final UIDL childUidl = ((UIDL) newUidl.next()).getChildUIDL(0); + if (oldChild == null && oldIt.hasNext()) { + // search for next old Paintable which still exists in layout + // and delete others + while (oldIt.hasNext()) { + oldChild = (Widget) oldIt.next(); + // now oldChild is an instance of Paintable + if (uidlWidgets.contains(oldChild)) { + break; + } else { + removePaintable((Paintable) oldChild); + oldChild = null; + } + } + } + if (oldChild == null) { + // we are adding components to layout + add(child); + } else if (child == oldChild) { + // child already attached and updated + oldChild = null; + } else if (hasChildComponent(child)) { + // current child has been moved, re-insert before current + // oldChild + // TODO this might be optimized by moving only container element + // to correct position + removeCaption(child); + int index = getWidgetIndex(oldChild); + if (componentToCaption.containsKey(oldChild)) { + index--; + } + remove(child); + insert(child, index); + } else { + // insert new child before old one + final int index = getWidgetIndex(oldChild); + insert(child, index); + } + if (child != expandedWidget) { + ((Paintable) child).updateFromUIDL(childUidl, client); + } + } + // remove possibly remaining old Paintable object which were not updated + while (oldIt.hasNext()) { + oldChild = (Widget) oldIt.next(); + final Paintable p = (Paintable) oldChild; + if (!uidlWidgets.contains(p)) { + removePaintable(p); + } + } + + if (uidlWidgets.size() == 0) { + return; + } + + // Set component alignments + handleAlignments(uidl); + + iLayout(); + + /* + * Expanded widget is updated after layout function so it has its + * container fixed at the moment of updateFromUIDL. + */ + if (expandedWidget != null) { + ((Paintable) expandedWidget).updateFromUIDL(expandedWidgetUidl, + client); + } + + // workaround for safari bug #1870 + float wkv = BrowserInfo.get().getWebkitVersion(); + if (wkv > 0 && wkv < 526.9) { + DeferredCommand.addCommand(new Command() { + public void execute() { + iLayout(); + } + }); + } + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IFilterSelect.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IFilterSelect.java new file mode 100644 index 0000000000..57c11c3f67 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IFilterSelect.java @@ -0,0 +1,788 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; + +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.ClickListener; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusListener; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.KeyboardListener; +import com.google.gwt.user.client.ui.PopupListener; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; +import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Focusable; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.Tooltip; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +/** + * + * TODO needs major refactoring (to be extensible etc) + */ +public class IFilterSelect extends Composite implements Paintable, Field, + KeyboardListener, ClickListener, FocusListener, Focusable { + + public class FilterSelectSuggestion implements Suggestion, Command { + + private final String key; + private final String caption; + private String iconUri; + + public FilterSelectSuggestion(UIDL uidl) { + key = uidl.getStringAttribute("key"); + caption = uidl.getStringAttribute("caption"); + if (uidl.hasAttribute("icon")) { + iconUri = client.translateToolkitUri(uidl + .getStringAttribute("icon")); + } + } + + public String getDisplayString() { + final StringBuffer sb = new StringBuffer(); + if (iconUri != null) { + sb.append("\"icon\""); + } + sb.append(Util.escapeHTML(caption)); + return sb.toString(); + } + + public String getReplacementString() { + return caption; + } + + public int getOptionKey() { + return Integer.parseInt(key); + } + + public String getIconUri() { + return iconUri; + } + + public void execute() { + onSuggestionSelected(this); + } + } + + /** + * @author mattitahvonen + * + */ + public class SuggestionPopup extends ToolkitOverlay implements + PositionCallback, PopupListener { + private static final int EXTRASPACE = 8; + + private static final String Z_INDEX = "30000"; + + private 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; + + SuggestionPopup() { + super(true, false, true); + menu = new SuggestionMenu(); + setWidget(menu); + setStyleName(CLASSNAME + "-suggestpopup"); + DOM.setStyleAttribute(getElement(), "zIndex", Z_INDEX); + + final Element root = getContainerElement(); + + DOM.setInnerHTML(up, "Prev"); + DOM.sinkEvents(up, Event.ONCLICK); + DOM.setInnerHTML(down, "Next"); + DOM.sinkEvents(down, Event.ONCLICK); + DOM.insertChild(root, up, 0); + DOM.appendChild(root, down); + DOM.appendChild(root, status); + DOM.setElementProperty(status, "className", CLASSNAME + "-status"); + + addPopupListener(this); + } + + public void showSuggestions(Collection currentSuggestions, + int currentPage, int totalSuggestions) { + + if (ApplicationConnection.isTestingMode()) { + // Add TT anchor point + DOM.setElementProperty(getElement(), "id", paintableId + + "_OPTIONLIST"); + } + + menu.setSuggestions(currentSuggestions); + final int x = IFilterSelect.this.getAbsoluteLeft(); + int y = tb.getAbsoluteTop(); + y += tb.getOffsetHeight(); + setPopupPosition(x, y); + final int first = currentPage * PAGELENTH + + (nullSelectionAllowed && currentPage > 0 ? 0 : 1); + final int last = first + currentSuggestions.size() - 1; + final int matches = totalSuggestions + - (nullSelectionAllowed ? 1 : 0); + if (last > 0) { + // nullsel not counted, as requested by user + DOM.setInnerText(status, (matches == 0 ? 0 : first) + + "-" + + ("".equals(lastFilter) && nullSelectionAllowed + && currentPage == 0 ? last - 1 : last) + "/" + + matches); + } else { + DOM.setInnerText(status, ""); + } + // We don't need to show arrows or statusbar if there is only one + // page + if (matches <= PAGELENTH) { + setPagingEnabled(false); + } else { + setPagingEnabled(true); + } + setPrevButtonActive(first > 1); + setNextButtonActive(last < matches); + + // clear previously fixed width + menu.setWidth(""); + DOM.setStyleAttribute(DOM.getFirstChild(menu.getElement()), + "width", ""); + + setPopupPositionAndShow(this); + } + + private void setNextButtonActive(boolean b) { + if (b) { + DOM.sinkEvents(down, Event.ONCLICK); + DOM.setElementProperty(down, "className", CLASSNAME + + "-nextpage"); + } else { + DOM.sinkEvents(down, 0); + DOM.setElementProperty(down, "className", CLASSNAME + + "-nextpage-off"); + } + } + + private void setPrevButtonActive(boolean b) { + if (b) { + DOM.sinkEvents(up, Event.ONCLICK); + DOM + .setElementProperty(up, "className", CLASSNAME + + "-prevpage"); + } else { + DOM.sinkEvents(up, 0); + DOM.setElementProperty(up, "className", CLASSNAME + + "-prevpage-off"); + } + + } + + public void selectNextItem() { + final MenuItem cur = menu.getSelectedItem(); + final int index = 1 + menu.getItems().indexOf(cur); + if (menu.getItems().size() > index) { + final MenuItem newSelectedItem = (MenuItem) menu.getItems() + .get(index); + menu.selectItem(newSelectedItem); + tb.setText(newSelectedItem.getText()); + tb.setSelectionRange(lastFilter.length(), newSelectedItem + .getText().length() + - lastFilter.length()); + + } else if (hasNextPage()) { + filterOptions(currentPage + 1, lastFilter); + } + } + + public void selectPrevItem() { + final MenuItem cur = menu.getSelectedItem(); + final int index = -1 + menu.getItems().indexOf(cur); + if (index > -1) { + final MenuItem newSelectedItem = (MenuItem) menu.getItems() + .get(index); + menu.selectItem(newSelectedItem); + tb.setText(newSelectedItem.getText()); + tb.setSelectionRange(lastFilter.length(), newSelectedItem + .getText().length() + - lastFilter.length()); + } else if (index == -1) { + if (currentPage > 0) { + filterOptions(currentPage - 1, lastFilter); + } + } else { + final MenuItem newSelectedItem = (MenuItem) menu.getItems() + .get(menu.getItems().size() - 1); + menu.selectItem(newSelectedItem); + tb.setText(newSelectedItem.getText()); + tb.setSelectionRange(lastFilter.length(), newSelectedItem + .getText().length() + - lastFilter.length()); + } + } + + public void onBrowserEvent(Event event) { + final Element target = DOM.eventGetTarget(event); + if (DOM.compare(target, up) + || DOM.compare(target, DOM.getChild(up, 0))) { + filterOptions(currentPage - 1, lastFilter); + } else if (DOM.compare(target, down) + || DOM.compare(target, DOM.getChild(down, 0))) { + filterOptions(currentPage + 1, lastFilter); + } + tb.setFocus(true); + } + + public void setPagingEnabled(boolean paging) { + if (isPagingEnabled == paging) { + return; + } + if (paging) { + DOM.setStyleAttribute(down, "display", ""); + DOM.setStyleAttribute(up, "display", ""); + DOM.setStyleAttribute(status, "display", ""); + } else { + DOM.setStyleAttribute(down, "display", "none"); + DOM.setStyleAttribute(up, "display", "none"); + DOM.setStyleAttribute(status, "display", "none"); + } + isPagingEnabled = paging; + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition(int, + * int) + */ + public void setPosition(int offsetWidth, int offsetHeight) { + + int top = -1; + int left = -1; + + // reset menu size and retrieve its "natural" size + menu.setHeight(""); + if (currentPage > 0) { + // fix height to avoid height change when getting to last page + menu.fixHeightTo(PAGELENTH); + } + offsetHeight = getOffsetHeight(); + + final int desiredWidth = IFilterSelect.this.getOffsetWidth(); + int naturalMenuWidth = DOM.getElementPropertyInt(DOM + .getFirstChild(menu.getElement()), "offsetWidth"); + if (naturalMenuWidth < desiredWidth) { + menu.setWidth(desiredWidth + "px"); + DOM.setStyleAttribute(DOM.getFirstChild(menu.getElement()), + "width", "100%"); + naturalMenuWidth = desiredWidth; + } + if (Util.isIE()) { + DOM.setStyleAttribute(getElement(), "width", naturalMenuWidth + + "px"); + } + + if (offsetHeight + getPopupTop() > Window.getClientHeight() + + Window.getScrollTop()) { + // popup on top of input instead + top = getPopupTop() - offsetHeight + - IFilterSelect.this.getOffsetHeight(); + if (top < 0) { + top = 0; + } + } else { + top = getPopupTop(); + } + + // fetch real width (mac FF bugs here due GWT popups overflow:auto ) + offsetWidth = DOM.getElementPropertyInt(DOM.getFirstChild(menu + .getElement()), "offsetWidth"); + if (offsetWidth + getPopupLeft() > Window.getClientWidth() + + Window.getScrollLeft()) { + left = IFilterSelect.this.getAbsoluteLeft() + + IFilterSelect.this.getOffsetWidth() + + Window.getScrollLeft() - offsetWidth; + if (left < 0) { + left = 0; + } + } else { + left = getPopupLeft(); + } + setPopupPosition(left, top); + + } + + /** + * @return true if popup was just closed + */ + public boolean isJustClosed() { + final long now = (new Date()).getTime(); + return (lastAutoClosed > 0 && (now - lastAutoClosed) < 200); + } + + public void onPopupClosed(PopupPanel sender, boolean autoClosed) { + if (autoClosed) { + lastAutoClosed = (new Date()).getTime(); + } + } + + } + + public class SuggestionMenu extends MenuBar { + + SuggestionMenu() { + super(true); + setStyleName(CLASSNAME + "-suggestmenu"); + } + + /** + * 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 pagelenth) { + if (currentSuggestions.size() > 0) { + final int pixels = pagelenth * (getOffsetHeight() - 2) + / currentSuggestions.size(); + setHeight((pixels + 2) + "px"); + } + } + + public void setSuggestions(Collection suggestions) { + clearItems(); + final Iterator it = suggestions.iterator(); + while (it.hasNext()) { + final FilterSelectSuggestion s = (FilterSelectSuggestion) it + .next(); + final MenuItem mi = new MenuItem(s.getDisplayString(), true, s); + this.addItem(mi); + if (s == currentSuggestion) { + selectItem(mi); + } + } + } + + public void doSelectedItemAction() { + final MenuItem item = getSelectedItem(); + final String enteredItemValue = tb.getText(); + // check for exact match in menu + int p = getItems().size(); + if (p > 0) { + for (int i = 0; i < p; i++) { + final MenuItem potentialExactMatch = (MenuItem) getItems() + .get(i); + if (potentialExactMatch.getText().equals(enteredItemValue)) { + selectItem(potentialExactMatch); + doItemAction(potentialExactMatch, true); + suggestionPopup.hide(); + return; + } + } + } + if (allowNewItem) { + + if (!enteredItemValue.equals(emptyText)) { + client.updateVariable(paintableId, "newitem", + enteredItemValue, immediate); + } + } else if (item != null + && !"".equals(lastFilter) + && item.getText().toLowerCase().startsWith( + lastFilter.toLowerCase())) { + doItemAction(item, true); + } else { + if (currentSuggestion != null) { + String text = currentSuggestion.getReplacementString(); + tb.setText((text.equals("") ? emptyText : text)); + // TODO add/remove class CLASSNAME_EMPTY + selectedOptionKey = currentSuggestion.key; + } else { + tb.setText(emptyText); + // TODO add class CLASSNAME_EMPTY + selectedOptionKey = null; + } + } + suggestionPopup.hide(); + } + } + + public static final int FILTERINGMODE_OFF = 0; + public static final int FILTERINGMODE_STARTSWITH = 1; + public static final int FILTERINGMODE_CONTAINS = 2; + + private static final String CLASSNAME = "i-filterselect"; + + public static final int PAGELENTH = 10; + + private final FlowPanel panel = new FlowPanel(); + + private final TextBox tb = new TextBox() { + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (client != null) { + client.handleTooltipEvent(event, IFilterSelect.this); + } + } + }; + + private final SuggestionPopup suggestionPopup = new SuggestionPopup(); + + private final HTML popupOpener = new HTML(""); + + private final Image selectedItemIcon = new Image(); + + private ApplicationConnection client; + + private String paintableId; + + private int currentPage; + + private final Collection currentSuggestions = new ArrayList(); + + private boolean immediate; + + private String selectedOptionKey; + + private boolean filtering = false; + + private String lastFilter = ""; + + private FilterSelectSuggestion currentSuggestion; + + private int totalMatches; + private boolean allowNewItem; + private boolean nullSelectionAllowed; + private boolean enabled; + + // shown in unfocused empty field, disappears on focus (e.g "Search here") + private String emptyText = ""; + private static final String CLASSNAME_EMPTY = "empty"; + private static final String ATTR_EMPTYTEXT = "emptytext"; + + public IFilterSelect() { + selectedItemIcon.setVisible(false); + panel.add(selectedItemIcon); + tb.sinkEvents(Tooltip.TOOLTIP_EVENTS); + panel.add(tb); + panel.add(popupOpener); + initWidget(panel); + setStyleName(CLASSNAME); + tb.addKeyboardListener(this); + tb.setStyleName(CLASSNAME + "-input"); + tb.addFocusListener(this); + popupOpener.setStyleName(CLASSNAME + "-button"); + popupOpener.addClickListener(this); + } + + public boolean hasNextPage() { + if (totalMatches > (currentPage + 1) * PAGELENTH) { + return true; + } else { + return false; + } + } + + public void filterOptions(int page) { + filterOptions(page, tb.getText()); + } + + public void filterOptions(int page, String filter) { + if (filter.equals(lastFilter) && currentPage == page) { + if (!suggestionPopup.isAttached()) { + suggestionPopup.showSuggestions(currentSuggestions, + currentPage, totalMatches); + } + return; + } + if (!filter.equals(lastFilter)) { + // we are on subsequent page and text has changed -> reset page + if ("".equals(filter)) { + // let server decide + page = -1; + } else { + page = 0; + } + } + + filtering = true; + client.updateVariable(paintableId, "filter", filter, false); + client.updateVariable(paintableId, "page", page, true); + lastFilter = filter; + currentPage = page; + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + paintableId = uidl.getId(); + this.client = client; + + boolean readonly = uidl.hasAttribute("readonly"); + boolean disabled = uidl.hasAttribute("disabled"); + + if (disabled || readonly) { + tb.setEnabled(false); + enabled = false; + } else { + tb.setEnabled(true); + enabled = true; + } + + if (client.updateComponent(this, uidl, true)) { + return; + } + + immediate = uidl.hasAttribute("immediate"); + + nullSelectionAllowed = uidl.hasAttribute("nullselect"); + + currentPage = uidl.getIntVariable("page"); + + if (uidl.hasAttribute(ATTR_EMPTYTEXT)) { + // "emptytext" changed from server + emptyText = uidl.getStringAttribute(ATTR_EMPTYTEXT); + } + + suggestionPopup.setPagingEnabled(true); + + allowNewItem = uidl.hasAttribute("allownewitem"); + + currentSuggestions.clear(); + final UIDL options = uidl.getChildUIDL(0); + totalMatches = uidl.getIntAttribute("totalMatches"); + + String captions = emptyText; + + for (final Iterator i = options.getChildIterator(); i.hasNext();) { + final UIDL optionUidl = (UIDL) i.next(); + final FilterSelectSuggestion suggestion = new FilterSelectSuggestion( + optionUidl); + currentSuggestions.add(suggestion); + if (optionUidl.hasAttribute("selected")) { + if (!filtering) { + tb.setText(suggestion.getReplacementString()); + } + currentSuggestion = suggestion; + } + + // Collect captions so we can calculate minimum width for textarea + if (captions.length() > 0) { + captions += "|"; + } + captions += suggestion.getReplacementString(); + } + + if (!filtering && uidl.hasVariable("selected") + && uidl.getStringArrayVariable("selected").length == 0) { + // select nulled + tb.setText(emptyText); + selectedOptionKey = null; + // TODO add class CLASSNAME_EMPTY + } + + if (filtering + && lastFilter.toLowerCase().equals( + uidl.getStringVariable("filter"))) { + suggestionPopup.showSuggestions(currentSuggestions, currentPage, + totalMatches); + filtering = false; + } + + // Calculate minumum textarea width + final int minw = minWidth(captions); + final Element spacer = DOM.createDiv(); + DOM.setStyleAttribute(spacer, "width", minw + "px"); + DOM.setStyleAttribute(spacer, "height", "0"); + DOM.setStyleAttribute(spacer, "overflow", "hidden"); + DOM.appendChild(panel.getElement(), spacer); + + } + + public void onSuggestionSelected(FilterSelectSuggestion suggestion) { + currentSuggestion = suggestion; + String newKey; + if (suggestion.key.equals("")) { + // "nullselection" + newKey = ""; + } else { + // normal selection + newKey = String.valueOf(suggestion.getOptionKey()); + } + String text = suggestion.getReplacementString(); + tb.setText(text.equals("") ? emptyText : text); + // TODO add/remove class CLASSNAME_EMPTY + setSelectedItemIcon(suggestion.getIconUri()); + if (!newKey.equals(selectedOptionKey)) { + selectedOptionKey = newKey; + client.updateVariable(paintableId, "selected", + new String[] { selectedOptionKey }, immediate); + // currentPage = -1; // forget the page + } + suggestionPopup.hide(); + } + + private void setSelectedItemIcon(String iconUri) { + if (iconUri == null) { + selectedItemIcon.setVisible(false); + } else { + selectedItemIcon.setUrl(iconUri); + selectedItemIcon.setVisible(true); + } + } + + public void onKeyDown(Widget sender, char keyCode, int modifiers) { + if (enabled && suggestionPopup.isAttached()) { + switch (keyCode) { + case KeyboardListener.KEY_DOWN: + suggestionPopup.selectNextItem(); + DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); + break; + case KeyboardListener.KEY_UP: + suggestionPopup.selectPrevItem(); + DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); + break; + case KeyboardListener.KEY_PAGEDOWN: + if (hasNextPage()) { + filterOptions(currentPage + 1, lastFilter); + } + break; + case KeyboardListener.KEY_PAGEUP: + if (currentPage > 0) { + filterOptions(currentPage - 1, lastFilter); + } + break; + case KeyboardListener.KEY_ENTER: + case KeyboardListener.KEY_TAB: + suggestionPopup.menu.doSelectedItemAction(); + break; + } + } + } + + public void onKeyPress(Widget sender, char keyCode, int modifiers) { + + } + + public void onKeyUp(Widget sender, char keyCode, int modifiers) { + if (enabled) { + switch (keyCode) { + case KeyboardListener.KEY_ENTER: + case KeyboardListener.KEY_TAB: + ; // NOP + break; + case KeyboardListener.KEY_DOWN: + case KeyboardListener.KEY_UP: + case KeyboardListener.KEY_PAGEDOWN: + case KeyboardListener.KEY_PAGEUP: + if (suggestionPopup.isAttached()) { + break; + } else { + // open popup as from gadget + filterOptions(-1, ""); + lastFilter = ""; + tb.selectAll(); + break; + } + case KeyboardListener.KEY_ESCAPE: + if (currentSuggestion != null) { + String text = currentSuggestion.getReplacementString(); + tb.setText((text.equals("") ? emptyText : text)); + // TODO add/remove class CLASSNAME_EMPTY + selectedOptionKey = currentSuggestion.key; + } else { + tb.setText(emptyText); + // TODO add class CLASSNAME_EMPTY + selectedOptionKey = null; + } + lastFilter = ""; + suggestionPopup.hide(); + break; + default: + filterOptions(currentPage); + break; + } + } + } + + /** + * Listener for popupopener + */ + public void onClick(Widget sender) { + if (enabled) { + // ask suggestionPopup if it was just closed, we are using GWT + // Popup's + // auto close feature + if (!suggestionPopup.isJustClosed()) { + filterOptions(-1, ""); + lastFilter = ""; + } + DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); + tb.setFocus(true); + tb.selectAll(); + + } + } + + /* + * Calculate minumum width for FilterSelect textarea + */ + private 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; + }-*/; + + public void onFocus(Widget sender) { + if (emptyText.equals(tb.getText())) { + tb.setText(""); + // TODO remove class CLASSNAME_EMPTY + } + } + + public void onLostFocus(Widget sender) { + if (suggestionPopup.isJustClosed()) { + suggestionPopup.menu.doSelectedItemAction(); + } + if ("".equals(tb.getText())) { + tb.setText(emptyText); + // TODO add CLASSNAME_EMPTY + } + } + + public void focus() { + tb.setFocus(true); + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IForm.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IForm.java new file mode 100644 index 0000000000..6722e1e421 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IForm.java @@ -0,0 +1,142 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Container; +import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener; +import com.itmill.toolkit.terminal.gwt.client.ErrorMessage; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +public class IForm extends ComplexPanel implements Paintable, + ContainerResizedListener { + + public static final String CLASSNAME = "i-form"; + + private Container lo; + private Element legend = DOM.createLegend(); + private Element caption = DOM.createSpan(); + private Element errorIndicatorElement = DOM.createDiv(); + private Element desc = DOM.createDiv(); + private Icon icon; + private ErrorMessage errorMessage = new ErrorMessage(); + + private Element fieldContainer = DOM.createDiv(); + + private Element footerContainer = DOM.createDiv(); + + private Container footer; + + public IForm() { + setElement(DOM.createFieldSet()); + setStyleName(CLASSNAME); + DOM.appendChild(getElement(), legend); + DOM.appendChild(legend, caption); + DOM.setElementProperty(errorIndicatorElement, "className", + "i-errorindicator"); + DOM.setStyleAttribute(errorIndicatorElement, "display", "none"); + DOM.setInnerText(errorIndicatorElement, " "); // needed for IE + DOM.setElementProperty(desc, "className", "i-form-description"); + DOM.appendChild(getElement(), desc); + DOM.appendChild(getElement(), fieldContainer); + errorMessage.setVisible(false); + errorMessage.setStyleName(CLASSNAME + "-errormessage"); + DOM.appendChild(getElement(), errorMessage.getElement()); + DOM.appendChild(getElement(), footerContainer); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, false)) { + return; + } + + boolean legendEmpty = true; + if (uidl.hasAttribute("caption")) { + DOM.setInnerText(caption, uidl.getStringAttribute("caption")); + legendEmpty = false; + } else { + DOM.setInnerText(caption, ""); + } + if (uidl.hasAttribute("icon")) { + if (icon == null) { + icon = new Icon(client); + DOM.insertChild(legend, icon.getElement(), 0); + } + icon.setUri(uidl.getStringAttribute("icon")); + legendEmpty = false; + } else { + if (icon != null) { + DOM.removeChild(legend, icon.getElement()); + } + } + if (legendEmpty) { + DOM.setStyleAttribute(legend, "display", "none"); + } else { + DOM.setStyleAttribute(legend, "display", ""); + } + + if (uidl.hasAttribute("error")) { + final UIDL errorUidl = uidl.getErrors(); + errorMessage.updateFromUIDL(errorUidl); + errorMessage.setVisible(true); + + } else { + errorMessage.setVisible(false); + } + + if (uidl.hasAttribute("description")) { + DOM.setInnerHTML(desc, uidl.getStringAttribute("description")); + } else { + DOM.setInnerHTML(desc, ""); + } + + iLayout(); + + final UIDL layoutUidl = uidl.getChildUIDL(0); + Container newLo = (Container) client.getPaintable(layoutUidl); + if (lo == null) { + lo = newLo; + add((Widget) lo, fieldContainer); + } else if (lo != newLo) { + client.unregisterPaintable(lo); + remove((Widget) lo); + lo = newLo; + add((Widget) lo, fieldContainer); + } + lo.updateFromUIDL(layoutUidl, client); + + if (uidl.getChildCount() > 1) { + // render footer + Container newFooter = (Container) client.getPaintable(uidl + .getChildUIDL(1)); + if (footer == null) { + add((Widget) newFooter, footerContainer); + footer = newFooter; + } else if (newFooter != footer) { + remove((Widget) footer); + client.unregisterPaintable(footer); + add((Widget) newFooter, footerContainer); + } + footer = newFooter; + footer.updateFromUIDL(uidl.getChildUIDL(1), client); + } else { + if (footer != null) { + remove((Widget) footer); + client.unregisterPaintable(footer); + } + } + } + + public void iLayout() { + Util.runDescendentsLayout(this); + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IFormLayout.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IFormLayout.java new file mode 100644 index 0000000000..a476e263d9 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IFormLayout.java @@ -0,0 +1,299 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.HashMap; +import java.util.Iterator; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.FlexTable; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Container; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.StyleConstants; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +/** + * Two col Layout that places caption on left col and field on right col + */ +public class IFormLayout extends FlexTable implements Container { + + private final static String CLASSNAME = "i-formlayout"; + + HashMap componentToCaption = new HashMap(); + private ApplicationConnection client; + private HashMap componentToError = new HashMap(); + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + this.client = client; + + if (client.updateComponent(this, uidl, false)) { + return; + } + + final MarginInfo margins = new MarginInfo(uidl + .getIntAttribute("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()); + + setStyleName(margin, CLASSNAME + "-" + "spacing", uidl + .hasAttribute("spacing")); + + int i = 0; + for (final Iterator it = uidl.getChildIterator(); it.hasNext(); i++) { + prepareCell(i, 1); + final UIDL childUidl = (UIDL) it.next(); + final Paintable p = client.getPaintable(childUidl); + Caption caption = (Caption) componentToCaption.get(p); + if (caption == null) { + caption = new Caption(p, client); + componentToCaption.put(p, caption); + } + ErrorFlag error = (ErrorFlag) componentToError.get(p); + if (error == null) { + error = new ErrorFlag(); + componentToError.put(p, error); + } + prepareCell(i, 2); + final Paintable oldComponent = (Paintable) getWidget(i, 2); + if (oldComponent == null) { + setWidget(i, 2, (Widget) p); + } else if (oldComponent != p) { + client.unregisterPaintable(oldComponent); + setWidget(i, 2, (Widget) p); + } + getCellFormatter().setStyleName(i, 2, CLASSNAME + "-contentcell"); + getCellFormatter().setStyleName(i, 0, CLASSNAME + "-captioncell"); + setWidget(i, 0, caption); + + getCellFormatter().setStyleName(i, 1, CLASSNAME + "-errorcell"); + setWidget(i, 1, error); + + p.updateFromUIDL(childUidl, client); + + String rowstyles = CLASSNAME + "-row"; + if (i == 0) { + rowstyles += " " + CLASSNAME + "-firstrow"; + } + if (!it.hasNext()) { + rowstyles += " " + CLASSNAME + "-lastrow"; + } + + getRowFormatter().setStyleName(i, rowstyles); + + } + + while (getRowCount() > i) { + final Paintable p = (Paintable) getWidget(i, 2); + client.unregisterPaintable(p); + componentToCaption.remove(p); + removeRow(i); + } + } + + public boolean hasChildComponent(Widget component) { + return componentToCaption.containsKey(component); + } + + public void replaceChildComponent(Widget oldComponent, Widget newComponent) { + int i; + for (i = 0; i < getRowCount(); i++) { + if (oldComponent == getWidget(i, 1)) { + final Caption newCap = new Caption((Paintable) newComponent, + client); + setWidget(i, 0, newCap); + setWidget(i, 1, newComponent); + client.unregisterPaintable((Paintable) oldComponent); + break; + } + } + } + + public void updateCaption(Paintable component, UIDL uidl) { + final Caption c = (Caption) componentToCaption.get(component); + if (c != null) { + c.updateCaption(uidl); + } + final ErrorFlag e = (ErrorFlag) componentToError.get(component); + if (e != null) { + e.updateFromUIDL(uidl, component); + } + } + + public class Caption extends HTML { + + public static final String CLASSNAME = "i-caption"; + + private final Paintable owner; + + private Element requiredFieldIndicator; + + private Icon icon; + + private Element captionText; + + private final ApplicationConnection client; + + /** + * + * @param component + * optional owner of caption. If not set, getOwner will + * return null + * @param client + */ + public Caption(Paintable component, ApplicationConnection client) { + super(); + this.client = client; + owner = component; + setStyleName(CLASSNAME); + } + + public void updateCaption(UIDL uidl) { + setVisible(!uidl.getBooleanAttribute("invisible")); + + setStyleName(getElement(), "i-disabled", uidl + .hasAttribute("disabled")); + + boolean isEmpty = true; + + if (uidl.hasAttribute("icon")) { + if (icon == null) { + icon = new Icon(client); + + DOM.insertChild(getElement(), icon.getElement(), 0); + } + icon.setUri(uidl.getStringAttribute("icon")); + isEmpty = false; + } else { + if (icon != null) { + DOM.removeChild(getElement(), icon.getElement()); + icon = null; + } + + } + + if (uidl.hasAttribute("caption")) { + if (captionText == null) { + captionText = DOM.createSpan(); + DOM.insertChild(getElement(), captionText, icon == null ? 0 + : 1); + } + String c = uidl.getStringAttribute("caption"); + if (c == null) { + c = ""; + } else { + isEmpty = false; + } + DOM.setInnerText(captionText, c); + } else { + // TODO should span also be removed + } + + if (uidl.hasAttribute("description")) { + if (captionText != null) { + addStyleDependentName("hasdescription"); + } else { + removeStyleDependentName("hasdescription"); + } + } + + if (uidl.getBooleanAttribute("required")) { + if (requiredFieldIndicator == null) { + requiredFieldIndicator = DOM.createSpan(); + DOM.setInnerText(requiredFieldIndicator, "*"); + DOM.setElementProperty(requiredFieldIndicator, "className", + "i-required-field-indicator"); + DOM.appendChild(getElement(), requiredFieldIndicator); + } + } else { + if (requiredFieldIndicator != null) { + DOM.removeChild(getElement(), requiredFieldIndicator); + requiredFieldIndicator = null; + } + } + + // Workaround for IE weirdness, sometimes returns bad height in some + // circumstances when Caption is empty. See #1444 + // IE7 bugs more often. I wonder what happens when IE8 arrives... + if (Util.isIE()) { + if (isEmpty) { + setHeight("0px"); + DOM.setStyleAttribute(getElement(), "overflow", "hidden"); + } else { + setHeight(""); + DOM.setStyleAttribute(getElement(), "overflow", ""); + } + + } + + } + + /** + * Returns Paintable for which this Caption belongs to. + * + * @return owner Widget + */ + public Paintable getOwner() { + return owner; + } + + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (client != null) { + client.handleTooltipEvent(event, owner); + } + } + } + + private class ErrorFlag extends HTML { + private static final String CLASSNAME = ".i-form-layout-error-indicator"; + Element errorIndicatorElement; + private Paintable owner; + + public ErrorFlag() { + setStyleName(CLASSNAME); + } + + public void updateFromUIDL(UIDL uidl, Paintable component) { + owner = component; + if (uidl.hasAttribute("error")) { + if (errorIndicatorElement == null) { + errorIndicatorElement = DOM.createDiv(); + if (Util.isIE()) { + DOM.setInnerHTML(errorIndicatorElement, " "); + } + DOM.setElementProperty(errorIndicatorElement, "className", + "i-errorindicator"); + DOM.appendChild(getElement(), errorIndicatorElement); + } + + } else if (errorIndicatorElement != null) { + DOM.removeChild(getElement(), errorIndicatorElement); + errorIndicatorElement = null; + } + } + + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (owner != null) { + client.handleTooltipEvent(event, owner); + } + } + + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IGridLayout.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IGridLayout.java new file mode 100644 index 0000000000..68bb5396c1 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IGridLayout.java @@ -0,0 +1,296 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.DeferredCommand; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.FlexTable; +import com.google.gwt.user.client.ui.HasHorizontalAlignment; +import com.google.gwt.user.client.ui.HasVerticalAlignment; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant; +import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.BrowserInfo; +import com.itmill.toolkit.terminal.gwt.client.CaptionWrapper; +import com.itmill.toolkit.terminal.gwt.client.Container; +import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.StyleConstants; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +public class IGridLayout extends SimplePanel implements Paintable, Container, + ContainerResizedListener { + + public static final String CLASSNAME = "i-gridlayout"; + + private Grid grid = new Grid(); + + private boolean needsLayout = false; + + private boolean needsFF2Hack = BrowserInfo.get().isFF2(); + + private Element margin = DOM.createDiv(); + + private Element meterElement; + + private String width; + + public IGridLayout() { + super(); + DOM.appendChild(getElement(), margin); + DOM.setStyleAttribute(getElement(), "overflow", "hidden"); + setStyleName(CLASSNAME); + setWidget(grid); + } + + protected Element getContainerElement() { + return margin; + } + + public void setWidth(String width) { + this.width = width; + if (width != null && !width.equals("")) { + needsLayout = true; + } else { + needsLayout = false; + grid.setWidth(""); + } + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + if (client.updateComponent(this, uidl, false)) { + return; + } + final MarginInfo margins = new MarginInfo(uidl + .getIntAttribute("margins")); + + 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()); + + setStyleName(margin, CLASSNAME + "-" + "spacing", uidl + .hasAttribute("spacing")); + iLayout(); + grid.updateFromUIDL(uidl, client); + } + + public boolean hasChildComponent(Widget component) { + return grid.hasChildComponent(component); + } + + public void replaceChildComponent(Widget oldComponent, Widget newComponent) { + grid.replaceChildComponent(oldComponent, newComponent); + } + + public void updateCaption(Paintable component, UIDL uidl) { + grid.updateCaption(component, uidl); + } + + public class Grid extends FlexTable implements Paintable, Container { + + /** Widget to captionwrapper map */ + private final HashMap widgetToCaptionWrapper = new HashMap(); + + public Grid() { + super(); + setStyleName(CLASSNAME + "-grid"); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + int row = 0, column = 0; + + final ArrayList oldWidgetWrappers = new ArrayList(); + for (final Iterator iterator = iterator(); iterator.hasNext();) { + oldWidgetWrappers.add(iterator.next()); + } + clear(); + + final int[] alignments = uidl.getIntArrayAttribute("alignments"); + int alignmentIndex = 0; + + for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { + final UIDL r = (UIDL) i.next(); + if ("gr".equals(r.getTag())) { + column = 0; + for (final Iterator j = r.getChildIterator(); j.hasNext();) { + final UIDL c = (UIDL) j.next(); + if ("gc".equals(c.getTag())) { + prepareCell(row, column); + + // Set cell width + int w; + if (c.hasAttribute("w")) { + w = c.getIntAttribute("w"); + } else { + w = 1; + } + + FlexCellFormatter formatter = (FlexCellFormatter) getCellFormatter(); + + // set col span + formatter.setColSpan(row, column, w); + + String styleNames = CLASSNAME + "-cell"; + if (column == 0) { + styleNames += " " + CLASSNAME + "-firstcol"; + } + if (row == 0) { + styleNames += " " + CLASSNAME + "-firstrow"; + } + formatter.setStyleName(row, column, styleNames); + + // Set cell height + int h; + if (c.hasAttribute("h")) { + h = c.getIntAttribute("h"); + } else { + h = 1; + } + ((FlexCellFormatter) getCellFormatter()) + .setRowSpan(row, column, h); + + final UIDL u = c.getChildUIDL(0); + if (u != null) { + + AlignmentInfo alignmentInfo = new AlignmentInfo( + alignments[alignmentIndex++]); + + VerticalAlignmentConstant va; + if (alignmentInfo.isBottom()) { + va = HasVerticalAlignment.ALIGN_BOTTOM; + } else if (alignmentInfo.isTop()) { + va = HasVerticalAlignment.ALIGN_TOP; + } else { + va = HasVerticalAlignment.ALIGN_MIDDLE; + } + + HorizontalAlignmentConstant ha; + + if (alignmentInfo.isLeft()) { + ha = HasHorizontalAlignment.ALIGN_LEFT; + } else if (alignmentInfo.isHorizontalCenter()) { + ha = HasHorizontalAlignment.ALIGN_CENTER; + } else { + ha = HasHorizontalAlignment.ALIGN_RIGHT; + } + + formatter.setAlignment(row, column, ha, va); + + final Paintable child = client.getPaintable(u); + CaptionWrapper wr; + if (widgetToCaptionWrapper.containsKey(child)) { + wr = (CaptionWrapper) widgetToCaptionWrapper + .get(child); + oldWidgetWrappers.remove(wr); + } else { + wr = new CaptionWrapper(child, client); + widgetToCaptionWrapper.put(child, wr); + } + + setWidget(row, column, wr); + + DOM.setStyleAttribute(wr.getElement(), + "textAlign", alignmentInfo + .getHorizontalAlignment()); + + if (!u.getBooleanAttribute("cached")) { + child.updateFromUIDL(u, client); + } + } + column += w; + } + } + row++; + } + } + + // loop oldWidgetWrappers that where not re-attached and unregister + // them + for (final Iterator it = oldWidgetWrappers.iterator(); it.hasNext();) { + final CaptionWrapper w = (CaptionWrapper) it.next(); + client.unregisterPaintable(w.getPaintable()); + widgetToCaptionWrapper.remove(w.getPaintable()); + } + // fix rendering bug on FF2 (#1838) + if (needsFF2Hack) { + DeferredCommand.addCommand(new Command() { + public void execute() { + Element firstcell = getCellFormatter().getElement(0, 0); + if (firstcell != null) { + String styleAttribute = DOM.getStyleAttribute( + firstcell, "verticalAlign"); + DOM.setStyleAttribute(firstcell, "verticalAlign", + ""); + int elementPropertyInt = DOM.getElementPropertyInt( + firstcell, "offsetWidth"); + DOM.setStyleAttribute(firstcell, "verticalAlign", + styleAttribute); + if (elementPropertyInt > 0) { + needsFF2Hack = false; + } + } + } + }); + } + } + + public boolean hasChildComponent(Widget component) { + if (widgetToCaptionWrapper.containsKey(component)) { + return true; + } + return false; + } + + public void replaceChildComponent(Widget oldComponent, + Widget newComponent) { + // TODO Auto-generated method stub + + } + + public void updateCaption(Paintable component, UIDL uidl) { + final CaptionWrapper wrapper = (CaptionWrapper) widgetToCaptionWrapper + .get(component); + wrapper.updateCaption(uidl); + } + + } + + public void iLayout() { + if (needsLayout) { + super.setWidth(width); + if (meterElement == null) { + meterElement = DOM.createDiv(); + DOM.setStyleAttribute(meterElement, "overflow", "hidden"); + DOM.setStyleAttribute(meterElement, "height", "0"); + DOM.appendChild(getContainerElement(), meterElement); + } + int contentWidth = DOM.getElementPropertyInt(meterElement, + "offsetWidth"); + int offsetWidth = getOffsetWidth(); + + grid.setWidth((offsetWidth - (offsetWidth - contentWidth)) + "px"); + } else { + grid.setWidth(""); + } + Util.runDescendentsLayout(this); + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IHorizontalExpandLayout.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IHorizontalExpandLayout.java new file mode 100644 index 0000000000..80ea9b945c --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IHorizontalExpandLayout.java @@ -0,0 +1,13 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +public class IHorizontalExpandLayout extends IExpandLayout { + + public IHorizontalExpandLayout() { + super(IExpandLayout.ORIENTATION_HORIZONTAL); + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ILabel.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ILabel.java new file mode 100644 index 0000000000..15e1eab9ef --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ILabel.java @@ -0,0 +1,49 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.ui.HTML; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class ILabel extends HTML implements Paintable { + + public static final String CLASSNAME = "i-label"; + + public ILabel() { + super(); + setStyleName(CLASSNAME); + } + + public ILabel(String text) { + super(text); + setStyleName(CLASSNAME); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + if (client.updateComponent(this, uidl, true)) { + return; + } + + final String mode = uidl.getStringAttribute("mode"); + if (mode == null || "text".equals(mode)) { + setText(uidl.getChildString(0)); + } else if ("pre".equals(mode)) { + setHTML(uidl.getChildrenAsXML()); + } else if ("uidl".equals(mode)) { + setHTML(uidl.getChildrenAsXML()); + } else if ("xhtml".equals(mode)) { + setHTML(uidl.getChildUIDL(0).getChildUIDL(0).getChildString(0)); + } else if ("xml".equals(mode)) { + setHTML(uidl.getChildUIDL(0).getChildString(0)); + } else if ("raw".equals(mode)) { + setHTML(uidl.getChildUIDL(0).getChildString(0)); + } else { + setText(""); + } + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ILink.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ILink.java new file mode 100644 index 0000000000..e7977c9def --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ILink.java @@ -0,0 +1,193 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.ClickListener; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.ErrorMessage; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class ILink extends HTML implements Paintable, ClickListener { + + public static final String CLASSNAME = "i-link"; + + private static final int BORDER_STYLE_DEFAULT = 0; + private static final int BORDER_STYLE_MINIMAL = 1; + private static final int BORDER_STYLE_NONE = 2; + + private String src; + + private String target; + + private int borderStyle = BORDER_STYLE_DEFAULT; + + private boolean enabled; + + private boolean readonly; + + private int targetWidth; + + private int targetHeight; + + private Element errorIndicatorElement; + + private final Element captionElement = DOM.createSpan(); + + private ErrorMessage errorMessage; + + private Icon icon; + + public ILink() { + super(); + DOM.appendChild(getElement(), captionElement); + addClickListener(this); + setStyleName(CLASSNAME); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + // Ensure correct implementation, + // but don't let container manage caption etc. + if (client.updateComponent(this, uidl, false)) { + return; + } + + enabled = uidl.hasAttribute("disabled") ? false : true; + readonly = uidl.hasAttribute("readonly") ? true : false; + + if (uidl.hasAttribute("name")) { + target = uidl.getStringAttribute("name"); + } + if (uidl.hasAttribute("src")) { + src = client.translateToolkitUri(uidl.getStringAttribute("src")); + } + + if (uidl.hasAttribute("border")) { + if ("none".equals(uidl.getStringAttribute("border"))) { + borderStyle = BORDER_STYLE_NONE; + } else { + borderStyle = BORDER_STYLE_MINIMAL; + } + } else { + borderStyle = BORDER_STYLE_DEFAULT; + } + + targetHeight = uidl.hasAttribute("height") ? uidl + .getIntAttribute("targetHeight") : -1; + targetWidth = uidl.hasAttribute("width") ? uidl + .getIntAttribute("targetHeidth") : -1; + + // Set link caption + DOM.setInnerText(captionElement, uidl.getStringAttribute("caption")); + + // handle error + if (uidl.hasAttribute("error")) { + final UIDL errorUidl = uidl.getErrors(); + if (errorIndicatorElement == null) { + errorIndicatorElement = DOM.createDiv(); + DOM.setElementProperty(errorIndicatorElement, "className", + "i-errorindicator"); + DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS); + sinkEvents(Event.MOUSEEVENTS); + } + DOM.insertChild(getElement(), errorIndicatorElement, 0); + if (errorMessage == null) { + errorMessage = new ErrorMessage(); + } + errorMessage.updateFromUIDL(errorUidl); + + } else if (errorIndicatorElement != null) { + DOM.setStyleAttribute(errorIndicatorElement, "display", "none"); + } + + if (uidl.hasAttribute("icon")) { + if (icon == null) { + icon = new Icon(client); + DOM.insertChild(getElement(), icon.getElement(), 0); + } + icon.setUri(uidl.getStringAttribute("icon")); + } + + // handle description + if (uidl.hasAttribute("description")) { + setTitle(uidl.getStringAttribute("description")); + } + + } + + public void onClick(Widget sender) { + if (enabled && !readonly) { + if (target == null) { + target = "_self"; + } + String features; + switch (borderStyle) { + case BORDER_STYLE_NONE: + features = "menubar=no,location=no,status=no"; + break; + case BORDER_STYLE_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; + } + + Window.open(src, target, features); + } + } + + public void onBrowserEvent(Event event) { + final Element target = DOM.eventGetTarget(event); + if (errorIndicatorElement != null + && DOM.compare(target, errorIndicatorElement)) { + switch (DOM.eventGetType(event)) { + case Event.ONMOUSEOVER: + showErrorMessage(); + break; + case Event.ONMOUSEOUT: + hideErrorMessage(); + break; + case Event.ONCLICK: + ApplicationConnection.getConsole().log( + DOM.getInnerHTML(errorMessage.getElement())); + return; + default: + break; + } + } + if (DOM.compare(target, captionElement) + || (icon != null && DOM.compare(target, icon.getElement()))) { + super.onBrowserEvent(event); + } + } + + private void hideErrorMessage() { + errorMessage.hide(); + } + + private void showErrorMessage() { + if (errorMessage != null) { + errorMessage.showAt(errorIndicatorElement); + } + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IListSelect.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IListSelect.java new file mode 100644 index 0000000000..ccc149a52e --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IListSelect.java @@ -0,0 +1,126 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.Iterator; +import java.util.Vector; + +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.Tooltip; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class IListSelect extends IOptionGroupBase { + + public static final String CLASSNAME = "i-select"; + + private static final int VISIBLE_COUNT = 10; + + protected TooltipListBox select; + + private int lastSelectedIndex = -1; + + public IListSelect() { + super(new TooltipListBox(true), CLASSNAME); + select = (TooltipListBox) optionsContainer; + select.setSelect(this); + select.addChangeListener(this); + select.addClickListener(this); + select.setStyleName(CLASSNAME + "-select"); + select.setVisibleItemCount(VISIBLE_COUNT); + } + + protected void buildOptions(UIDL uidl) { + select.setClient(client); + select.setMultipleSelect(isMultiselect()); + select.setEnabled(!isDisabled() && !isReadonly()); + select.clear(); + if (!isMultiselect() && isNullSelectionAllowed() + && !isNullSelectionItemAvailable()) { + // can't unselect last item in singleselect mode + select.addItem("", 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")) { + select.setItemSelected(select.getItemCount() - 1, true); + } + } + if (getRows() > 0) { + select.setVisibleItemCount(getRows()); + } + } + + protected Object[] getSelectedItems() { + final Vector selectedItemKeys = new Vector(); + for (int i = 0; i < select.getItemCount(); i++) { + if (select.isItemSelected(i)) { + selectedItemKeys.add(select.getValue(i)); + } + } + return selectedItemKeys.toArray(); + } + + public void onChange(Widget sender) { + final int si = select.getSelectedIndex(); + if (si == -1 && !isNullSelectionAllowed()) { + select.setSelectedIndex(lastSelectedIndex); + } else { + lastSelectedIndex = si; + if (isMultiselect()) { + client.updateVariable(id, "selected", getSelectedItems(), + isImmediate()); + } else { + client.updateVariable(id, "selected", new String[] { "" + + getSelectedItem() }, isImmediate()); + } + } + } + + public void setHeight(String height) { + select.setHeight(height); + super.setHeight(height); + } + + public void setWidth(String width) { + select.setWidth(width); + super.setWidth(width); + } + +} + +/** + * Extended ListBox to listen tooltip events and forward them to generic + * handler. + */ +class TooltipListBox extends ListBox { + private ApplicationConnection client; + private Paintable pntbl; + + TooltipListBox(boolean isMultiselect) { + super(isMultiselect); + sinkEvents(Tooltip.TOOLTIP_EVENTS); + } + + public void setClient(ApplicationConnection client) { + this.client = client; + } + + public void setSelect(Paintable s) { + pntbl = s; + } + + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (client != null) { + client.handleTooltipEvent(event, pntbl); + } + } +} \ No newline at end of file diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IMenuBar.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IMenuBar.java new file mode 100644 index 0000000000..6c8e5f9892 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IMenuBar.java @@ -0,0 +1,234 @@ +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Stack; + +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.ui.MenuBar; +import com.google.gwt.user.client.ui.MenuItem; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class IMenuBar extends MenuBar implements Paintable { + + /** Set the CSS class name to allow styling. */ + public static final String CLASSNAME = "i-menubar"; + + /** For server connections **/ + protected String uidlId; + protected ApplicationConnection client; + + protected final IMenuBar hostReference = this; + protected static final boolean vertical = true; + protected String submenuIcon = null; + protected boolean collapseItems = true; + + protected MenuItem moreItem = null; + + // Construct an empty command to be used when the item has no command + // associated + protected static final Command emptyCommand = null; + + /** + * The constructor should first call super() to initialize the component and + * then handle any initialization relevant to IT Mill Toolkit. + */ + public IMenuBar() { + // Create an empty horizontal menubar + super(); + + // This method call of the Paintable interface sets the component + // style name in DOM tree + setStyleName(CLASSNAME); + } + + public IMenuBar(boolean vertical) { + super(vertical); + + // This method call of the Paintable interface sets the component + // style name in DOM tree + setStyleName(CLASSNAME + "_submenu"); + } + + /** + * 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. + */ + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // This call should be made first. Ensure correct implementation, + // and let the containing layout manage caption, etc. + if (client.updateComponent(this, uidl, true)) { + return; + } + + // For future connections + this.client = client; + uidlId = uidl.getId(); + + // Empty the menu every time it receives new information + if (!this.getItems().isEmpty()) { + this.clearItems(); + } + + UIDL options = uidl.getChildUIDL(0); + // For GWT 1.5 + //this.setAnimationEnabled(options.getBooleanAttribute("animationEnabled" + // )) + // ; + + if (options.hasAttribute("submenuIcon")) { + submenuIcon = client.translateToolkitUri(uidl.getChildUIDL(0) + .getStringAttribute("submenuIcon")); + } else { + submenuIcon = null; + } + + collapseItems = options.getBooleanAttribute("collapseItems"); + + if (collapseItems) { + UIDL moreItemUIDL = options.getChildUIDL(0); + StringBuffer itemHTML = new StringBuffer(); + itemHTML.append("

"); + if (moreItemUIDL.hasAttribute("icon")) { + itemHTML.append(""); + } + itemHTML.append(moreItemUIDL.getStringAttribute("text")); + itemHTML.append("

"); + moreItem = new MenuItem(itemHTML.toString(), true, emptyCommand); + } + + UIDL items = uidl.getChildUIDL(1); + Iterator itr = items.getChildIterator(); + Stack iteratorStack = new Stack(); + Stack menuStack = new Stack(); + MenuBar currentMenu = this; + + while (itr.hasNext()) { + UIDL item = (UIDL) itr.next(); + MenuItem currentItem = null; // For receiving the item + + String itemText = item.getStringAttribute("text"); + final int itemId = item.getIntAttribute("id"); + + boolean itemHasCommand = item.getBooleanAttribute("command"); + + // Construct html from the text and the optional icon + StringBuffer itemHTML = new StringBuffer(); + + itemHTML.append("

"); + + if (item.hasAttribute("icon")) { + itemHTML.append(""); + } + + itemHTML.append(itemText); + + if (currentMenu != this && item.getChildCount() > 0 + && submenuIcon != null) { + itemHTML.append(""); + } + + itemHTML.append("

"); + + Command cmd = null; + + // Check if we need to create a command to this item + if (itemHasCommand) { + // Construct a command that fires onMenuClick(int) with the + // item's id-number + cmd = new Command() { + public void execute() { + hostReference.onMenuClick(itemId); + } + }; + } + + currentItem = currentMenu.addItem(itemHTML.toString(), true, cmd); + + if (item.getChildCount() > 0) { + menuStack.push(currentMenu); + iteratorStack.push(itr); + itr = item.getChildIterator(); + currentMenu = new IMenuBar(vertical); + currentItem.setSubMenu(currentMenu); + } + + while (!itr.hasNext() && !iteratorStack.empty()) { + itr = (Iterator) iteratorStack.pop(); + currentMenu = (MenuBar) menuStack.pop(); + } + }// while + + // we might need to collapse the top-level menu + if (collapseItems) { + int topLevelWidth = 0; + + int ourWidth = this.getOffsetWidth(); + + int i = 0; + for (; i < getItems().size() && topLevelWidth < ourWidth; i++) { + MenuItem item = (MenuItem) getItems().get(i); + topLevelWidth += item.getOffsetWidth(); + } + + if (topLevelWidth > this.getOffsetWidth()) { + ArrayList toBeCollapsed = new ArrayList(); + MenuBar collapsed = new IMenuBar(vertical); + for (int j = i - 2; j < getItems().size(); j++) { + toBeCollapsed.add(getItems().get(j)); + } + + for (int j = 0; j < toBeCollapsed.size(); j++) { + MenuItem item = (MenuItem) toBeCollapsed.get(j); + removeItem(item); + + // it's ugly, but we have to insert the submenu icon + if (item.getSubMenu() != null && submenuIcon != null) { + String itemHTML = item.getHTML(); + StringBuffer itemText = new StringBuffer(itemHTML + .substring(0, itemHTML.length() - 4)); + itemText.append("

"); + item.setHTML(itemText.toString()); + } + + collapsed.addItem(item); + } + + moreItem.setSubMenu(collapsed); + addItem(moreItem); + } + } + }// updateFromUIDL + + /** + * 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); + } + } + +}// class IMenuBar diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/INativeSelect.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/INativeSelect.java new file mode 100644 index 0000000000..b011928bf9 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/INativeSelect.java @@ -0,0 +1,77 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.Iterator; +import java.util.Vector; + +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class INativeSelect extends IOptionGroupBase implements Field { + + public static final String CLASSNAME = "i-select"; + + protected TooltipListBox select; + + public INativeSelect() { + super(new TooltipListBox(false), CLASSNAME); + select = (TooltipListBox) optionsContainer; + select.setSelect(this); + select.setVisibleItemCount(1); + select.addChangeListener(this); + select.setStyleName(CLASSNAME + "-select"); + + } + + protected void buildOptions(UIDL uidl) { + select.setClient(client); + select.setEnabled(!isDisabled() && !isReadonly()); + select.clear(); + if (isNullSelectionAllowed() && !isNullSelectionItemAvailable()) { + // can't unselect last item in singleselect mode + select.addItem("", 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")) { + select.setItemSelected(select.getItemCount() - 1, true); + } + } + } + + protected Object[] getSelectedItems() { + final Vector selectedItemKeys = new Vector(); + for (int i = 0; i < select.getItemCount(); i++) { + if (select.isItemSelected(i)) { + selectedItemKeys.add(select.getValue(i)); + } + } + return selectedItemKeys.toArray(); + } + + public void onChange(Widget sender) { + if (select.isMultipleSelect()) { + client.updateVariable(id, "selected", getSelectedItems(), + isImmediate()); + } else { + client.updateVariable(id, "selected", new String[] { "" + + getSelectedItem() }, isImmediate()); + } + } + + public void setHeight(String height) { + select.setHeight(height); + super.setHeight(height); + } + + public void setWidth(String width) { + select.setWidth(width); + super.setWidth(width); + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IOptionGroup.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IOptionGroup.java new file mode 100644 index 0000000000..5120d913ec --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IOptionGroup.java @@ -0,0 +1,78 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import com.google.gwt.user.client.ui.CheckBox; +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.itmill.toolkit.terminal.gwt.client.UIDL; + +public class IOptionGroup extends IOptionGroupBase { + + public static final String CLASSNAME = "i-select-optiongroup"; + + private final Panel panel; + + private final Map optionsToKeys; + + public IOptionGroup() { + super(CLASSNAME); + panel = (Panel) optionsContainer; + optionsToKeys = new HashMap(); + } + + /* + * Return true if no elements were changed, false otherwise. + */ + protected void buildOptions(UIDL uidl) { + panel.clear(); + for (final Iterator it = uidl.getChildIterator(); it.hasNext();) { + final UIDL opUidl = (UIDL) it.next(); + CheckBox op; + if (isMultiselect()) { + op = new ICheckBox(); + op.setText(opUidl.getStringAttribute("caption")); + } else { + op = new RadioButton(id, opUidl.getStringAttribute("caption")); + op.setStyleName("i-radiobutton"); + } + op.addStyleName(CLASSNAME_OPTION); + op.setChecked(opUidl.getBooleanAttribute("selected")); + op.setEnabled(!opUidl.getBooleanAttribute("disabled") + && !isReadonly() && !isDisabled()); + op.addClickListener(this); + optionsToKeys.put(op, opUidl.getStringAttribute("key")); + panel.add(op); + } + } + + protected Object[] getSelectedItems() { + return selectedKeys.toArray(); + } + + public void onClick(Widget sender) { + super.onClick(sender); + if (sender instanceof CheckBox) { + final boolean selected = ((CheckBox) sender).isChecked(); + final String key = (String) optionsToKeys.get(sender); + if (!isMultiselect()) { + selectedKeys.clear(); + } + if (selected) { + selectedKeys.add(key); + } else { + selectedKeys.remove(key); + } + client.updateVariable(id, "selected", getSelectedItems(), + isImmediate()); + } + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IOptionGroupBase.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IOptionGroupBase.java new file mode 100644 index 0000000000..04ac2cee78 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IOptionGroupBase.java @@ -0,0 +1,223 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.Set; + +import com.google.gwt.user.client.ui.ChangeListener; +import com.google.gwt.user.client.ui.ClickListener; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.KeyboardListener; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +abstract class IOptionGroupBase extends Composite implements Paintable, Field, + ClickListener, ChangeListener, KeyboardListener { + + public static final String CLASSNAME_OPTION = "i-select-option"; + + protected ApplicationConnection client; + + protected String id; + + protected Set selectedKeys; + + private boolean immediate; + + private boolean multiselect; + + private boolean disabled; + + private boolean readonly; + + private int cols = 0; + + private int rows = 0; + + private boolean nullSelectionAllowed = true; + + private boolean nullSelectionItemAvailable = false; + + /** + * Widget holding the different options (e.g. ListBox or Panel for radio + * buttons) (optional, fallbacks to container Panel) + */ + protected Widget optionsContainer; + + /** + * Panel containing the component + */ + private final Panel container; + + private ITextField newItemField; + + private IButton newItemButton; + + public IOptionGroupBase(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 IOptionGroupBase(Widget w, String classname) { + this(classname); + optionsContainer = w; + container.add(optionsContainer); + } + + protected boolean isImmediate() { + return immediate; + } + + protected boolean isMultiselect() { + return multiselect; + } + + protected boolean isDisabled() { + return disabled; + } + + protected boolean isReadonly() { + return readonly; + } + + protected boolean isNullSelectionAllowed() { + return nullSelectionAllowed; + } + + protected boolean isNullSelectionItemAvailable() { + return nullSelectionItemAvailable; + } + + /** + * @return "cols" specified in uidl, 0 if not specified + */ + protected int getColumns() { + return cols; + } + + /** + * @return "rows" specified in uidl, 0 if not specified + */ + + protected int getRows() { + return rows; + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + this.client = client; + id = uidl.getId(); + + if (client.updateComponent(this, uidl, true)) { + return; + } + + selectedKeys = uidl.getStringArrayVariableAsSet("selected"); + + readonly = uidl.getBooleanAttribute("readonly"); + disabled = uidl.getBooleanAttribute("disabled"); + multiselect = "multi".equals(uidl.getStringAttribute("selectmode")); + immediate = uidl.getBooleanAttribute("immediate"); + nullSelectionAllowed = uidl.getBooleanAttribute("nullselect"); + nullSelectionItemAvailable = uidl.getBooleanAttribute("nullselectitem"); + + cols = uidl.getIntAttribute("cols"); + rows = uidl.getIntAttribute("rows"); + + final UIDL ops = uidl.getChildUIDL(0); + + if (getColumns() > 0) { + container.setWidth(getColumns() + "em"); + if (container != optionsContainer) { + optionsContainer.setWidth("100%"); + } + } + + buildOptions(ops); + + if (uidl.getBooleanAttribute("allownewitem")) { + if (newItemField == null) { + newItemButton = new IButton(); + newItemButton.setText("+"); + newItemButton.setWidth("1.5em"); + newItemButton.addClickListener(this); + newItemField = new ITextField(); + newItemField.addKeyboardListener(this); + // newItemField.setColumns(16); + if (getColumns() > 0) { + newItemField.setWidth((getColumns() - 2) + "em"); + } + } + newItemField.setEnabled(!disabled && !readonly); + newItemButton.setEnabled(!disabled && !readonly); + + if (newItemField == null || newItemField.getParent() != container) { + container.add(newItemField); + container.add(newItemButton); + } + } else if (newItemField != null) { + container.remove(newItemField); + container.remove(newItemButton); + } + + } + + public void onClick(Widget sender) { + if (sender == newItemButton && !newItemField.getText().equals("")) { + client.updateVariable(id, "newitem", newItemField.getText(), true); + newItemField.setText(""); + } + } + + public void onChange(Widget sender) { + if (multiselect) { + client + .updateVariable(id, "selected", getSelectedItems(), + immediate); + } else { + client.updateVariable(id, "selected", new String[] { "" + + getSelectedItem() }, immediate); + } + } + + public void onKeyPress(Widget sender, char keyCode, int modifiers) { + if (sender == newItemField && keyCode == KeyboardListener.KEY_ENTER) { + newItemButton.click(); + } + } + + public void onKeyUp(Widget sender, char keyCode, int modifiers) { + // Ignore, subclasses may override + } + + public void onKeyDown(Widget sender, char keyCode, int modifiers) { + // Ignore, subclasses may override + } + + protected abstract void buildOptions(UIDL uidl); + + protected abstract Object[] getSelectedItems(); + + protected Object getSelectedItem() { + final Object[] sel = getSelectedItems(); + if (sel.length > 0) { + return sel[0]; + } else { + return null; + } + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IOrderedLayout.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IOrderedLayout.java new file mode 100644 index 0000000000..f6ec503857 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IOrderedLayout.java @@ -0,0 +1,1150 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.Iterator; +import java.util.Vector; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.UIObject; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.BrowserInfo; +import com.itmill.toolkit.terminal.gwt.client.Caption; +import com.itmill.toolkit.terminal.gwt.client.Container; +import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +/** + * Full implementation of OrderedLayout client peer. + * + * This class implements all features of OrderedLayout. It currently only + * supports use through UIDL updates. Direct client side use is not (currently) + * suported in all operation modes. + * + * @author IT Mill Ltd + */ +public class IOrderedLayout extends Panel implements Container, + ContainerResizedListener { + + public static final String CLASSNAME = "i-orderedlayout"; + + public static final int ORIENTATION_VERTICAL = 0; + public static final int ORIENTATION_HORIZONTAL = 1; + + private int hSpacing = -1; + private int vSpacing = -1; + private int marginTop = -1; + private int marginBottom = -1; + private int marginLeft = -1; + private int marginRight = -1; + + int orientationMode = ORIENTATION_VERTICAL; + + protected ApplicationConnection client; + + /** + * Reference to Element where wrapped childred are contained. Normally a + * DIV, TR or a TBODY element. + */ + private Element wrappedChildContainer; + + /** + * Elements that provides the Layout interface implementation. Root element + * of the component. This is the outmost div or table. + */ + private Element root; + + /** + * List of child widgets. This is not the list of wrappers, but the actual + * widgets + */ + private final Vector childWidgets = new Vector(); + + /** + * In table mode, the root element is table instead of div. + */ + private boolean tableMode = false; + + /** Last set width of the component. Null if undefined (instead of being ""). */ + private String width = null; + + /** + * Last set height of the component. Null if undefined (instead of being + * ""). + */ + private String height = null; + /** + * List of child widget wrappers. These wrappers are in exact same indexes + * as the widgets in childWidgets list. + */ + private final Vector childWidgetWrappers = new Vector(); + + /** Whether the component has spacing enabled. */ + private boolean hasComponentSpacing; + + /** Information about margin states. */ + private MarginInfo margins = new MarginInfo(0); + + /** + * Flag that indicates that the child layouts must be updated as soon as + * possible. This will be done in the end of updateFromUIDL. + */ + private boolean childLayoutsHaveChanged = false; + + /** + * Construct the DOM of the orderder layout. + * + *

+ * There are two modes - vertical and horizontal. + *

    + *
  • Vertical mode uses structure: div-root ( div-wrap ( child ) div-wrap ( + * child ))).
  • + *
  • Horizontal mode uses structure: table ( tbody ( tr-childcontainer ( + * td-wrap ( child ) td-wrap ( child) )) )
  • + *
+ * where root and childcontainer refer to the root element and the element + * that contain WidgetWrappers. + *

+ * + */ + public IOrderedLayout() { + wrappedChildContainer = root = DOM.createDiv(); + setElement(root); + setStyleName(CLASSNAME); + } + + /** + * Update orientation, if it has changed. + * + * @param newOrientationMode + */ + private void rebuildRootDomStructure(int oldOrientationMode) { + + // Should we have table as a root element? + boolean newTableMode = !(orientationMode == ORIENTATION_VERTICAL && width != null); + + // Already in correct mode? + if (oldOrientationMode == orientationMode && newTableMode == tableMode) { + return; + } + boolean oldTableMode = tableMode; + tableMode = newTableMode; + + // Constuct base DOM-structure and clean any already attached + // widgetwrappers from DOM. + if (tableMode) { + Element tmp = DOM.createDiv(); + final String structure = "" + + (orientationMode == ORIENTATION_HORIZONTAL ? "" + : "") + "
"; + DOM.setInnerHTML(tmp, structure); + root = DOM.getFirstChild(tmp); + DOM.removeChild(tmp, root); + // set TBODY to be the wrappedChildContainer + wrappedChildContainer = DOM.getFirstChild(root); + // In case of horizontal layouts, we must user TR instead of TBODY + if (orientationMode == ORIENTATION_HORIZONTAL) { + wrappedChildContainer = DOM + .getFirstChild(wrappedChildContainer); + } + } else { + wrappedChildContainer = root = DOM.createDiv(); + } + + // Restore component size + if (width != null && !"".equals(width)) { + DOM.setStyleAttribute(root, "width", width); + } + if (height != null && !"".equals(height)) { + DOM.setStyleAttribute(root, "height", height); + } + + // Reset widget main element + String styles = getStyleName(); + setElement(root); + setStyleName(styles); + + // Reinsert all widget wrappers to this container + final int currentOrientationMode = orientationMode; + for (int i = 0; i < childWidgetWrappers.size(); i++) { + WidgetWrapper wr = (WidgetWrapper) childWidgetWrappers.get(i); + orientationMode = oldOrientationMode; + tableMode = oldTableMode; + Element oldWrElement = wr.getElementWrappingWidgetAndCaption(); + orientationMode = currentOrientationMode; + tableMode = newTableMode; + String classe = DOM.getElementAttribute(oldWrElement, "class"); + wr.resetRootElement(); + Element newWrElement = wr.getElementWrappingWidgetAndCaption(); + if (classe != null && classe.length() > 0) { + DOM.setElementAttribute(newWrElement, "class", classe); + } + while (DOM.getChildCount(oldWrElement) > 0) { + Element c = DOM.getFirstChild(oldWrElement); + DOM.removeChild(oldWrElement, c); + DOM.appendChild(newWrElement, c); + } + + DOM.appendChild(wrappedChildContainer, wr.getElement()); + } + + // Update child layouts + childLayoutsHaveChanged = true; + } + + /** Update the contents of the layout from UIDL. */ + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + this.client = client; + + // Only non-cached UIDL:s can introduce changes + if (uidl.getBooleanAttribute("cached")) { + return; + } + + updateMarginAndSpacingSizesFromCSS(uidl); + + // Update sizes, ... + if (client.updateComponent(this, uidl, false)) { + return; + } + + // Rebuild DOM tree root if necessary + int oldO = orientationMode; + orientationMode = "horizontal".equals(uidl + .getStringAttribute("orientation")) ? ORIENTATION_HORIZONTAL + : ORIENTATION_VERTICAL; + rebuildRootDomStructure(oldO); + + // Handle component spacing later in handleAlignments() method + hasComponentSpacing = uidl.getBooleanAttribute("spacing"); + + // Collect the list of contained widgets after this update + final Vector newWidgets = new Vector(); + for (final Iterator it = uidl.getChildIterator(); it.hasNext();) { + final UIDL uidlForChild = (UIDL) it.next(); + final Paintable child = client.getPaintable(uidlForChild); + newWidgets.add(child); + } + + // Iterator for old widgets + final Iterator oldWidgetsIterator = (new Vector(childWidgets)) + .iterator(); + + // Iterator for new widgets + final Iterator newWidgetsIterator = newWidgets.iterator(); + + // Iterator for new UIDL + final Iterator newUIDLIterator = uidl.getChildIterator(); + + // List to collect all now painted widgets to in order to remove + // unpainted ones later + final Vector paintedWidgets = new Vector(); + + final Vector childsToPaint = new Vector(); + + // Add any new widgets to the ordered layout + Widget oldChild = null; + while (newWidgetsIterator.hasNext()) { + + final Widget newChild = (Widget) newWidgetsIterator.next(); + final UIDL newChildUIDL = (UIDL) newUIDLIterator.next(); + + // Remove any unneeded old widgets + if (oldChild == null && oldWidgetsIterator.hasNext()) { + // search for next old Paintable which still exists in layout + // and delete others + while (oldWidgetsIterator.hasNext()) { + oldChild = (Widget) oldWidgetsIterator.next(); + // now oldChild is an instance of Paintable + if (paintedWidgets.contains(oldChild)) { + continue; + } else if (newWidgets.contains(oldChild)) { + break; + } else { + remove(oldChild); + oldChild = null; + } + } + } + + if (oldChild == null) { + // we are adding components to the end of layout + add(newChild); + } else if (newChild == oldChild) { + // child already attached in correct position + oldChild = null; + } else if (hasChildComponent(newChild)) { + + // current child has been moved, re-insert before current + // oldChild + add(newChild, childWidgets.indexOf(oldChild)); + + } else { + // insert new child before old one + add(newChild, childWidgets.indexOf(oldChild)); + } + + // Update the child component + childsToPaint.add(new Object[] { newChild, newChildUIDL }); + + // Add this newly handled component to the list of painted + // components + paintedWidgets.add(newChild); + } + + // Remove possibly remaining old widgets which were not in painted UIDL + while (oldWidgetsIterator.hasNext()) { + oldChild = (Widget) oldWidgetsIterator.next(); + if (!newWidgets.contains(oldChild)) { + remove(oldChild); + } + } + + // Handle component alignments + handleAlignmentsSpacingAndMargins(uidl); + + // Reset sizes for the children + updateChildSizes(); + + // Paint children + for (int i = 0; i < childsToPaint.size(); i++) { + Object[] t = (Object[]) childsToPaint.get(i); + ((Paintable) t[0]).updateFromUIDL((UIDL) t[1], client); + } + + // Update child layouts + // TODO This is most probably unnecessary and should be done within + // update Child H/W + if (childLayoutsHaveChanged) { + Util.runDescendentsLayout(this); + childLayoutsHaveChanged = false; + } + } + + private void updateMarginAndSpacingSizesFromCSS(UIDL uidl) { + // TODO Read spacing and margins from CSS as documented in #1904. + // Somehow refresh after updates + + hSpacing = 8; + vSpacing = 8; + marginTop = 15; + marginBottom = 15; + marginLeft = 18; + marginRight = 18; + } + + /** + * While setting width, ensure that margin div is also resized properly. + * Furthermore, enable/disable fixed mode + */ + public void setWidth(String newWidth) { + + width = newWidth == null || "".equals(newWidth) ? null : newWidth; + + // When we use divs at root - for them using 100% width should be + // calculated with "" + if (!tableMode && "100%".equals(newWidth)) { + super.setWidth(""); + } else { + super.setWidth(newWidth); + } + + // Update child layouts + childLayoutsHaveChanged = true; + } + + /** + * While setting height, ensure that margin div is also resized properly. + * Furthermore, enable/disable fixed mode + */ + public void setHeight(String newHeight) { + super.setHeight(newHeight); + height = newHeight == null || "".equals(newHeight) ? null : newHeight; + + // Update child layouts + childLayoutsHaveChanged = true; + } + + /** Recalculate and apply the space given for each child in this layout. */ + private void updateChildSizes() { + + int numChild = childWidgets.size(); + int childHeightTotal = -1; + int childHeightDivisor = 1; + int childWidthTotal = -1; + int childWidthDivisor = 1; + + // Vertical layout is calculated by us + if (height != null) { + + // Calculate the space for fixed contents minus marginals + if (tableMode) { + + // If we know explicitly set pixel-size, use that + if (height != null && height.endsWith("px")) { + try { + childHeightTotal = Integer.parseInt(height.substring(0, + height.length() - 2)); + + // For negative sizes, use measurements + if (childHeightTotal < 0) { + childHeightTotal = rootOffsetMeasure("offsetHeight"); + } + } catch (NumberFormatException e) { + + // In case of invalid number, try to measure the size; + childHeightTotal = rootOffsetMeasure("offsetHeight"); + } + } + // If not, try to measure the size + else { + childHeightTotal = rootOffsetMeasure("offsetHeight"); + } + + } else { + childHeightTotal = DOM.getElementPropertyInt(root, + "offsetHeight"); + } + + childHeightTotal -= margins.hasTop() ? marginTop : 0; + childHeightTotal -= margins.hasBottom() ? marginBottom : 0; + + // Reduce spacing from the size + if (hasComponentSpacing) { + childHeightTotal -= ((orientationMode == ORIENTATION_HORIZONTAL) ? hSpacing + : vSpacing) + * (numChild - 1); + } + + // Total space is divided among the children + if (orientationMode == ORIENTATION_VERTICAL) { + childHeightDivisor = numChild; + } + } + + // layout is calculated by us + if (width != null) { + + // Calculate the space for fixed contents minus marginals + // If we know explicitly set pixel-size, use that + if (width != null && width.endsWith("px")) { + try { + childWidthTotal = Integer.parseInt(width.substring(0, width + .length() - 2)); + + // For negative sizes, use measurements + if (childWidthTotal < 0) { + childWidthTotal = rootOffsetMeasure("offsetWidth"); + } + + } catch (NumberFormatException e) { + + // In case of invalid number, try to measure the size; + childWidthTotal = rootOffsetMeasure("offsetWidth"); + } + } + // If not, try to measure the size + else { + childWidthTotal = rootOffsetMeasure("offsetWidth"); + } + + childWidthTotal -= margins.hasLeft() ? marginLeft : 0; + childWidthTotal -= margins.hasRight() ? marginRight : 0; + + // Reduce spacing from the size + if (hasComponentSpacing + && orientationMode == ORIENTATION_HORIZONTAL) { + childWidthTotal -= hSpacing * (numChild - 1); + } + + // Total space is divided among the children + if (orientationMode == ORIENTATION_HORIZONTAL) { + childWidthDivisor = numChild; + } + } + + // Set the sizes for each child + for (Iterator i = childWidgetWrappers.iterator(); i.hasNext();) { + int w, h; + if (childHeightDivisor > 1) { + h = Math.round(((float) childHeightTotal) + / (childHeightDivisor--)); + childHeightTotal -= h; + } else { + h = childHeightTotal; + } + if (childWidthDivisor > 1) { + w = Math.round(((float) childWidthTotal) + / (childWidthDivisor--)); + childWidthTotal -= h; + } else { + w = childWidthTotal; + } + WidgetWrapper ww = (WidgetWrapper) i.next(); + // TODO COMBINE THESE + ww.forceSize(w, h); + } + } + + /** + * Measure how much space the root element could get. + * + * This measures the space allocated by the parent for the root element + * without letting root element to affect the calculation. + * + * @param offset + * offsetWidth or offsetHeight + */ + private int rootOffsetMeasure(String offset) { + Element measure = DOM.createDiv(); + DOM.setStyleAttribute(measure, "height", "100%"); + Element parent = DOM.getParent(root); + DOM.insertBefore(parent, measure, root); + DOM.removeChild(parent, root); + int size = DOM.getElementPropertyInt(measure, offset); + DOM.insertBefore(parent, root, measure); + DOM.removeChild(parent, measure); + // In case the no space would be given for this element + // without pushing, use the current side of the root + return size; + } + + /** Parse alignments from UIDL and pass whem to correct widgetwrappers */ + private void handleAlignmentsSpacingAndMargins(UIDL uidl) { + + // Only update margins when they have changed + // TODO this should be optimized to avoid reupdating these + margins = new MarginInfo(uidl.getIntAttribute("margins")); + + // Component alignments as a comma separated list. + // See com.itmill.toolkit.terminal.gwt.client.ui.AlignmentInfo.java for + // possible values. + final int[] alignments = uidl.getIntArrayAttribute("alignments"); + int alignmentIndex = 0; + + // Insert alignment attributes + final Iterator it = childWidgetWrappers.iterator(); + + while (it.hasNext()) { + + // Calculate alignment info + final AlignmentInfo ai = new AlignmentInfo( + alignments[alignmentIndex++]); + + final WidgetWrapper wr = (WidgetWrapper) it.next(); + + wr.setAlignment(ai.getVerticalAlignment(), ai + .getHorizontalAlignment()); + + // Handle spacing and margins in this loop as well + wr.setSpacingAndMargins(alignmentIndex == 1, + alignmentIndex == alignments.length); + } + } + + /** + * Wrapper around single child in the layout. + * + * This helper also manages spacing, margins and alignment for individual + * cells handling. It also can put hard size limits for its contens by + * clipping the content to given pixel size. + * + */ + class WidgetWrapper extends UIObject { + + /** + * When alignment table structure is used, these elements correspond to + * the TD elements within the structure. If alignment is not used, these + * are null. + */ + Element alignmentTD, innermostTDinAlignmnetStructure; + + /** + * When clipping must be done and the element wrapping clipped content + * would be TD instead of DIV, this element points to additional DIV + * that is used for clipping. + */ + Element clipperDiv; + + /** Caption element when used. */ + Caption caption = null; + + /** + * Last set pixel height for the wrapper. -1 if vertical clipping is not + * used. + */ + int lastForcedPixelHeight = -1; + + /** + * Last set pidel width for the wrapper. -1 if horizontal clipping is + * not used. + */ + int lastForcedPixelWidth = -1; + + /** Set the root element */ + public WidgetWrapper() { + resetRootElement(); + } + + /** + * Set the width and height given for the wrapped widget in pixels. + * + * -1 if unconstrained. + */ + public void forceSize(int pixelWidth, int pixelHeight) { + + // If we are already at the correct size, do nothing + if (lastForcedPixelHeight == pixelHeight + && lastForcedPixelWidth == pixelWidth) { + return; + } + + // Clipper DIV is needed? + if (tableMode && (pixelHeight >= 0 || pixelWidth >= 0)) { + if (clipperDiv == null) { + createClipperDiv(); + } + } + + // ClipperDiv is not needed, remove if necessary + else if (clipperDiv != null) { + removeClipperDiv(); + } + + Element e = clipperDiv != null ? clipperDiv + : getElementWrappingAlignmentStructures(); + + // Overflow + DOM.setStyleAttribute(e, "overflowX", pixelWidth < 0 ? "" + : "hidden"); + DOM.setStyleAttribute(e, "overflowY", pixelHeight < 0 ? "" + : "hidden"); + + // Set size + DOM.setStyleAttribute(e, "width", pixelWidth < 0 ? "" : pixelWidth + + "px"); + DOM.setStyleAttribute(e, "height", pixelHeight < 0 ? "" + : pixelHeight + "px"); + + // Set cached values + lastForcedPixelWidth = pixelWidth; + lastForcedPixelHeight = pixelHeight; + } + + /** Create a DIV for clipping the child */ + private void createClipperDiv() { + clipperDiv = DOM.createDiv(); + final Element e = getElementWrappingClipperDiv(); + String classe = DOM.getElementAttribute(e, "class"); + while (DOM.getChildCount(e) > 0) { + final Element c = DOM.getFirstChild(e); + DOM.removeChild(e, c); + DOM.appendChild(clipperDiv, c); + } + if (classe != null && classe.length() > 0) { + DOM.removeElementAttribute(e, "class"); + DOM.setElementAttribute(clipperDiv, "class", classe); + } + DOM.appendChild(e, clipperDiv); + } + + /** Undo createClipperDiv() */ + private void removeClipperDiv() { + final Element e = getElementWrappingClipperDiv(); + String classe = DOM.getElementAttribute(clipperDiv, "class"); + while (DOM.getChildCount(clipperDiv) > 0) { + final Element c = DOM.getFirstChild(clipperDiv); + DOM.removeChild(clipperDiv, c); + DOM.appendChild(e, c); + } + DOM.removeChild(e, clipperDiv); + clipperDiv = null; + if (classe != null && classe.length() > 0) { + DOM.setElementAttribute(e, "class", classe); + } + } + + /** + * Get the element containing the caption and the wrapped widget. + * Returned element can one of the following: + *
    + *
  • (a) Root DIV of the WrapperElement when not in tableMode
  • + *
  • (b) TD in just below the root TR of the WrapperElement when in + * tableMode
  • + *
  • (c) clipperDiv inside the (a) or (b)
  • + *
  • (d) The innermost TD within alignment structures located in (a), + * (b) or (c)
  • + *
+ * + * @return Element described above + */ + private Element getElementWrappingWidgetAndCaption() { + + // When alignment is used, we will can safely return the innermost + // TD + if (innermostTDinAlignmnetStructure != null) { + return innermostTDinAlignmnetStructure; + } + + // In all other cases element wrapping the potential alignment + // structures is the correct one + return getElementWrappingAlignmentStructures(); + } + + /** + * Get the element where alignment structures should be placed in if + * they are in use. + * + * Returned element can one of the following: + *
    + *
  • (a) Root DIV of the WrapperElement when not in tableMode
  • + *
  • (b) TD in just below the root TR of the WrapperElement when in + * tableMode
  • + *
  • (c) clipperDiv inside the (a) or (b)
  • + *
+ * + * @return Element described above + */ + private Element getElementWrappingAlignmentStructures() { + + // Clipper DIV wraps the alignment structures if present + if (clipperDiv != null) { + return clipperDiv; + } + + // When Clipper DIV is not used, we just give the element + // that would wrap it if it would be used + return getElementWrappingClipperDiv(); + } + + /** + * Get the element where clipperDiv should be placed in if they it is in + * use. + * + * Returned element can one of the following: + *
    + *
  • (a) Root DIV of the WrapperElement when not in tableMode
  • + *
  • (b) TD in just below the root TR of the WrapperElement when in + * tableMode
  • + *
+ * + * @return Element described above + */ + private Element getElementWrappingClipperDiv() { + + // Only vertical layouts in non-table mode use TR as root, for the + // rest we can safely give root element + if (!tableMode || orientationMode == ORIENTATION_HORIZONTAL) { + return getElement(); + } + + // The root is TR, we'll thus give the TD that is immediately within + // the root + return DOM.getFirstChild(getElement()); + } + + /** + * Create tr, td or div - depending on the orientation of the layout and + * set it as root. + * + * All contents of the wrapper are cleared. Caller is responsible for + * preserving the contents and moving them into new root. + * + * @return Previous root element. + */ + private void resetRootElement() { + if (tableMode) { + if (orientationMode == ORIENTATION_HORIZONTAL) { + setElement(DOM.createTD()); + } else { + Element tr = DOM.createTR(); + DOM.appendChild(tr, DOM.createTD()); + setElement(tr); + } + } else { + setElement(DOM.createDiv()); + // Apply 'hasLayout' for IE (needed to get accurate dimension + // calculations) + if (BrowserInfo.get().isIE()) { + DOM.setStyleAttribute(getElement(), "zoom", "1"); + } + } + + // Clear any references to intermediate elements + clipperDiv = alignmentTD = innermostTDinAlignmnetStructure = null; + } + + /** Update the caption of the element contained in this wrapper. */ + public void updateCaption(UIDL uidl, Paintable paintable) { + + final Widget widget = (Widget) paintable; + final Element captionWrapper = getElementWrappingWidgetAndCaption(); + + // The widget needs caption + if (Caption.isNeeded(uidl)) { + + // If the caption element is missing, create it + boolean justAdded = false; + if (caption == null) { + justAdded = true; + caption = new Caption(paintable, client); + } + + // Update caption contents + caption.updateCaption(uidl); + + final boolean after = caption.shouldBePlacedAfterComponent(); + final Element captionElement = caption.getElement(); + final Element widgetElement = widget.getElement(); + + if (justAdded) { + + // As the caption has just been created, insert it to DOM + if (after) { + DOM.appendChild(captionWrapper, captionElement); + DOM.setElementAttribute(captionWrapper, "class", + "i-orderedlayout-w"); + caption.addStyleName("i-orderedlayout-c"); + widget.addStyleName("i-orderedlayout-w-e"); + } else { + DOM.insertChild(captionWrapper, captionElement, 0); + } + + } else + + // Caption exists. Move it to correct position if needed + if (after == (DOM.getChildIndex(captionWrapper, widgetElement) > DOM + .getChildIndex(captionWrapper, captionElement))) { + Element firstElement = DOM.getChild(captionWrapper, DOM + .getChildCount(captionWrapper) - 2); + if (firstElement != null) { + DOM.removeChild(captionWrapper, firstElement); + DOM.appendChild(captionWrapper, firstElement); + } + DOM.setElementAttribute(captionWrapper, "class", + after ? "i-orderedlayout-w" : ""); + if (after) { + caption.addStyleName("i-orderedlayout-c"); + widget.addStyleName("i-orderedlayout-w-e"); + } else { + widget.removeStyleName("i-orderedlayout-w-e"); + caption.removeStyleName("i-orderedlayout-w-c"); + } + } + + } + + // Caption is not needed + else { + + // Remove existing caption from DOM + if (caption != null) { + DOM.removeChild(captionWrapper, caption.getElement()); + caption = null; + DOM.setElementAttribute(captionWrapper, "class", ""); + widget.removeStyleName("i-orderedlayout-w-e"); + caption.removeStyleName("i-orderedlayout-w-c"); + } + } + } + + /** + * Set alignments for this wrapper. + */ + void setAlignment(String verticalAlignment, String horizontalAlignment) { + + // use one-cell table to implement horizontal alignments, only + // for values other than top-left (which is default) + if (!horizontalAlignment.equals("left") + || !verticalAlignment.equals("top")) { + + // The previous positioning has been left (or unspecified). + // Thus we need to create a one-cell-table to position + // this element. + if (alignmentTD == null) { + + // Store and remove the current childs (widget and caption) + Element c1 = DOM + .getFirstChild(getElementWrappingWidgetAndCaption()); + if (c1 != null) { + DOM.removeChild(getElementWrappingWidgetAndCaption(), + c1); + } + Element c2 = DOM + .getFirstChild(getElementWrappingWidgetAndCaption()); + if (c2 != null) { + DOM.removeChild(getElementWrappingWidgetAndCaption(), + c2); + } + + // Construct table structure to align children + final String t = "
" + + "
" + + "
"; + DOM.setInnerHTML(getElementWrappingWidgetAndCaption(), t); + alignmentTD = DOM + .getFirstChild(DOM + .getFirstChild(DOM + .getFirstChild(DOM + .getFirstChild(getElementWrappingWidgetAndCaption())))); + innermostTDinAlignmnetStructure = DOM.getFirstChild(DOM + .getFirstChild(DOM.getFirstChild(DOM + .getFirstChild(alignmentTD)))); + + // Restore children inside the + if (c1 != null) { + DOM.appendChild(innermostTDinAlignmnetStructure, c1); + if (c2 != null) { + DOM + .appendChild( + innermostTDinAlignmnetStructure, c2); + } + } + + } else { + + // Go around optimization bug in WebKit and ensure repaint + if (BrowserInfo.get().isSafari()) { + String prevValue = DOM.getElementAttribute(alignmentTD, + "align"); + if (!horizontalAlignment.equals(prevValue)) { + Element parent = DOM.getParent(alignmentTD); + DOM.removeChild(parent, alignmentTD); + DOM.appendChild(parent, alignmentTD); + } + } + + } + + // Set the alignment in td + DOM.setElementAttribute(alignmentTD, "align", + horizontalAlignment); + DOM.setElementAttribute(alignmentTD, "valign", + verticalAlignment); + + } else { + + // In this case we are requested to position this left + // while as it has had some other position in the past. + // Thus the one-cell wrapper table must be removed. + if (alignmentTD != null) { + + // Move content to main container + final Element itd = innermostTDinAlignmnetStructure; + final Element alignmentTable = DOM.getParent(DOM + .getParent(DOM.getParent(alignmentTD))); + final Element target = DOM.getParent(alignmentTable); + while (DOM.getChildCount(itd) > 0) { + Element content = DOM.getFirstChild(itd); + if (content != null) { + DOM.removeChild(itd, content); + DOM.appendChild(target, content); + } + } + + // Remove unneeded table element + DOM.removeChild(target, alignmentTable); + + alignmentTD = innermostTDinAlignmnetStructure = null; + } + } + } + + /** Set class for spacing */ + void setSpacingAndMargins(boolean first, boolean last) { + + final Element e = getElementWrappingWidgetAndCaption(); + + if (orientationMode == ORIENTATION_HORIZONTAL) { + DOM.setStyleAttribute(e, "paddingLeft", first ? (margins + .hasLeft() ? marginLeft + "px" : "0") + : (hasComponentSpacing ? hSpacing + "px" : "0")); + DOM.setStyleAttribute(e, "paddingRight", last ? (margins + .hasRight() ? marginRight + "px" : "0") : ""); + DOM.setStyleAttribute(e, "paddingTop", + margins.hasTop() ? marginTop + "px" : ""); + DOM.setStyleAttribute(e, "paddingBottom", + margins.hasBottom() ? marginBottom + "px" : ""); + } else { + DOM.setStyleAttribute(e, "paddingLeft", + margins.hasLeft() ? marginLeft + "px" : "0"); + DOM.setStyleAttribute(e, "paddingRight", + margins.hasRight() ? marginRight + "px" : "0"); + DOM.setStyleAttribute(e, "paddingTop", first ? (margins + .hasTop() ? marginTop + "px" : "") + : (hasComponentSpacing ? vSpacing + "px" : "0")); + DOM.setStyleAttribute(e, "paddingBottom", last + && margins.hasBottom() ? marginBottom + "px" : ""); + } + } + } + + /* documented at super */ + public void add(Widget child) { + add(child, childWidgets.size()); + } + + /** + * Add widget to this layout at given position. + * + * This methods supports reinserting exiting child into layout - it just + * moves the position of the child in the layout. + */ + public void add(Widget child, int atIndex) { + /* + * Validate: Perform any sanity checks to ensure the Panel can + * accept a new Widget. Examples: checking for a valid index on + * insertion; checking that the Panel is not full if there is a max + * capacity. + */ + if (atIndex < 0 || atIndex > childWidgets.size()) { + return; + } + + /* + * Adjust for Reinsertion: Some Panels need to handle the case + * where the Widget is already a child of this Panel. Example: when + * performing a reinsert, the index might need to be adjusted to account + * for the Widget's removal. See + * {@link ComplexPanel#adjustIndex(Widget, int)}. + */ + if (childWidgets.contains(child)) { + if (childWidgets.indexOf(child) == atIndex) { + return; + } + + final int removeFromIndex = childWidgets.indexOf(child); + final WidgetWrapper wrapper = (WidgetWrapper) childWidgetWrappers + .get(removeFromIndex); + Element wrapperElement = wrapper.getElement(); + final int nonWidgetChildElements = DOM + .getChildCount(wrappedChildContainer) + - childWidgets.size(); + DOM.removeChild(wrappedChildContainer, wrapperElement); + DOM.insertChild(wrappedChildContainer, wrapperElement, atIndex + + nonWidgetChildElements); + childWidgets.remove(removeFromIndex); + childWidgetWrappers.remove(removeFromIndex); + childWidgets.insertElementAt(child, atIndex); + childWidgetWrappers.insertElementAt(wrapper, atIndex); + return; + } + + /* + * Detach Child: Remove the Widget from its existing parent, if + * any. Most Panels will simply call {@link Widget#removeFromParent()} + * on the Widget. + */ + child.removeFromParent(); + + /* + * Logical Attach: Any state variables of the Panel should be + * updated to reflect the addition of the new Widget. Example: the + * Widget is added to the Panel's {@link WidgetCollection} at the + * appropriate index. + */ + childWidgets.insertElementAt(child, atIndex); + + /* + * Physical Attach: The Widget's Element must be physically + * attached to the Panel's Element, either directly or indirectly. + */ + final WidgetWrapper wrapper = new WidgetWrapper(); + final int nonWidgetChildElements = DOM + .getChildCount(wrappedChildContainer) + - childWidgetWrappers.size(); + childWidgetWrappers.insertElementAt(wrapper, atIndex); + DOM.insertChild(wrappedChildContainer, wrapper.getElement(), atIndex + + nonWidgetChildElements); + DOM.appendChild(wrapper.getElementWrappingWidgetAndCaption(), child + .getElement()); + + /* + * Adopt: Call {@link #adopt(Widget)} to finalize the add as the + * very last step. + */ + adopt(child); + } + + /* documented at super */ + public boolean remove(Widget child) { + + /* + * Validate: Make sure this Panel is actually the parent of the + * child Widget; return false if it is not. + */ + if (!childWidgets.contains(child)) { + return false; + } + + /* + * Orphan: Call {@link #orphan(Widget)} first while the child + * Widget is still attached. + */ + orphan(child); + + /* + * Physical Detach: Adjust the DOM to account for the removal of + * the child Widget. The Widget's Element must be physically removed + * from the DOM. + */ + final int index = childWidgets.indexOf(child); + final WidgetWrapper wrapper = (WidgetWrapper) childWidgetWrappers + .get(index); + DOM.removeChild(wrappedChildContainer, wrapper.getElement()); + childWidgetWrappers.remove(index); + + /* + * Logical Detach: Update the Panel's state variables to reflect + * the removal of the child Widget. Example: the Widget is removed from + * the Panel's {@link WidgetCollection}. + */ + childWidgets.remove(index); + + return true; + } + + /* documented at super */ + public boolean hasChildComponent(Widget component) { + return childWidgets.contains(component); + } + + /* documented at super */ + public void replaceChildComponent(Widget oldComponent, Widget newComponent) { + final int index = childWidgets.indexOf(oldComponent); + if (index >= 0) { + client.unregisterPaintable((Paintable) oldComponent); + remove(oldComponent); + add(newComponent, index); + } + } + + /* documented at super */ + public void updateCaption(Paintable component, UIDL uidl) { + final int index = childWidgets.indexOf(component); + if (index >= 0) { + ((WidgetWrapper) childWidgetWrappers.get(index)).updateCaption( + uidl, component); + } + } + + /* documented at super */ + public Iterator iterator() { + return childWidgets.iterator(); + } + + /* documented at super */ + public void iLayout() { + updateChildSizes(); + Util.runDescendentsLayout(this); + childLayoutsHaveChanged = false; + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IPanel.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IPanel.java new file mode 100644 index 0000000000..8a408597c3 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IPanel.java @@ -0,0 +1,388 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.BrowserInfo; +import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener; +import com.itmill.toolkit.terminal.gwt.client.ErrorMessage; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +public class IPanel extends SimplePanel implements Paintable, + ContainerResizedListener { + + public static final String CLASSNAME = "i-panel"; + + ApplicationConnection client; + + String id; + + private final Element captionNode = DOM.createDiv(); + + private final Element captionText = DOM.createSpan(); + + private Icon icon; + + private final Element bottomDecoration = DOM.createDiv(); + + private final Element contentNode = DOM.createDiv(); + + private Element errorIndicatorElement; + + private ErrorMessage errorMessage; + + private String height; + + private Paintable layout; + + ShortcutActionHandler shortcutHandler; + + private String width; + + private Element geckoCaptionMeter; + + private int scrollTop; + + private int scrollLeft; + + public IPanel() { + super(); + DOM.appendChild(getElement(), captionNode); + DOM.appendChild(captionNode, captionText); + DOM.appendChild(getElement(), contentNode); + DOM.appendChild(getElement(), bottomDecoration); + setStyleName(CLASSNAME); + DOM + .setElementProperty(captionNode, "className", CLASSNAME + + "-caption"); + DOM + .setElementProperty(contentNode, "className", CLASSNAME + + "-content"); + DOM.setElementProperty(bottomDecoration, "className", CLASSNAME + + "-deco"); + DOM.sinkEvents(getElement(), Event.ONKEYDOWN); + DOM.sinkEvents(contentNode, Event.ONSCROLL); + } + + protected Element getContainerElement() { + return contentNode; + } + + private void setCaption(String text) { + DOM.setInnerText(captionText, text); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Ensure correct implementation + if (client.updateComponent(this, uidl, false)) { + return; + } + + this.client = client; + id = uidl.getId(); + + // Panel size. Height needs to be saved for later use + height = uidl.hasVariable("height") ? uidl.getStringVariable("height") + : null; + setWidth(uidl.hasVariable("width") ? uidl.getStringVariable("width") + : ""); + + // Restore default stylenames + DOM + .setElementProperty(captionNode, "className", CLASSNAME + + "-caption"); + DOM + .setElementProperty(contentNode, "className", CLASSNAME + + "-content"); + DOM.setElementProperty(bottomDecoration, "className", CLASSNAME + + "-deco"); + + // Handle caption displaying + boolean hasCaption = false; + if (uidl.hasAttribute("caption") + && !uidl.getStringAttribute("caption").equals("")) { + setCaption(uidl.getStringAttribute("caption")); + hasCaption = true; + } else { + setCaption(""); + DOM.setElementProperty(captionNode, "className", CLASSNAME + + "-nocaption"); + } + + setIconUri(uidl, client); + + handleDescription(uidl); + + handleError(uidl); + + // Add proper stylenames for all elements. This way we can prevent + // unwanted CSS selector inheritance. + if (uidl.hasAttribute("style")) { + final String[] styles = uidl.getStringAttribute("style").split(" "); + final String captionBaseClass = CLASSNAME + + (hasCaption ? "-caption" : "-nocaption"); + final String contentBaseClass = CLASSNAME + "-content"; + final String decoBaseClass = CLASSNAME + "-deco"; + String captionClass = captionBaseClass; + String contentClass = contentBaseClass; + String decoClass = decoBaseClass; + for (int i = 0; i < styles.length; i++) { + captionClass += " " + captionBaseClass + "-" + styles[i]; + contentClass += " " + contentBaseClass + "-" + styles[i]; + decoClass += " " + decoBaseClass + "-" + styles[i]; + } + DOM.setElementProperty(captionNode, "className", captionClass); + DOM.setElementProperty(contentNode, "className", contentClass); + DOM.setElementProperty(bottomDecoration, "className", decoClass); + } + + // Height adjustment + iLayout(false); + + // Render content + final UIDL layoutUidl = uidl.getChildUIDL(0); + final Paintable newLayout = client.getPaintable(layoutUidl); + if (newLayout != layout) { + if (layout != null) { + client.unregisterPaintable(layout); + } + setWidget((Widget) newLayout); + layout = newLayout; + } + (layout).updateFromUIDL(layoutUidl, client); + + // We may have actions attached to this panel + if (uidl.getChildCount() > 1) { + final int cnt = uidl.getChildCount(); + for (int i = 1; i < cnt; i++) { + UIDL childUidl = uidl.getChildUIDL(i); + if (childUidl.getTag().equals("actions")) { + if (shortcutHandler == null) { + shortcutHandler = new ShortcutActionHandler(id, client); + } + shortcutHandler.updateActionMap(childUidl); + } + } + } + + if (uidl.hasVariable("scrollTop") + && uidl.getIntVariable("scrollTop") != scrollTop) { + scrollTop = uidl.getIntVariable("scrollTop"); + DOM.setElementPropertyInt(contentNode, "scrollTop", scrollTop); + } + + if (uidl.hasVariable("scrollLeft") + && uidl.getIntVariable("scrollLeft") != scrollLeft) { + scrollLeft = uidl.getIntVariable("scrollLeft"); + DOM.setElementPropertyInt(contentNode, "scrollLeft", scrollLeft); + } + + } + + private void handleError(UIDL uidl) { + if (uidl.hasAttribute("error")) { + final UIDL errorUidl = uidl.getErrors(); + if (errorIndicatorElement == null) { + errorIndicatorElement = DOM.createDiv(); + DOM.setElementProperty(errorIndicatorElement, "className", + "i-errorindicator"); + DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS); + sinkEvents(Event.MOUSEEVENTS); + } + DOM.insertBefore(captionNode, errorIndicatorElement, captionText); + if (errorMessage == null) { + errorMessage = new ErrorMessage(); + } + errorMessage.updateFromUIDL(errorUidl); + + } else if (errorIndicatorElement != null) { + DOM.removeChild(captionNode, errorIndicatorElement); + errorIndicatorElement = null; + } + } + + private void handleDescription(UIDL uidl) { + DOM.setElementProperty(captionText, "title", uidl + .hasAttribute("description") ? uidl + .getStringAttribute("description") : ""); + } + + private void setIconUri(UIDL uidl, ApplicationConnection client) { + final String iconUri = uidl.hasAttribute("icon") ? uidl + .getStringAttribute("icon") : null; + if (iconUri == null) { + if (icon != null) { + DOM.removeChild(captionNode, icon.getElement()); + icon = null; + } + } else { + if (icon == null) { + icon = new Icon(client); + DOM.insertChild(captionNode, icon.getElement(), 0); + } + icon.setUri(iconUri); + } + } + + public void iLayout() { + iLayout(true); + } + + public void iLayout(boolean runGeckoFix) { + if (height != null && height != "") { + final boolean hasChildren = getWidget() != null; + Element contentEl = null; + String origPositioning = null; + // save scroll position + int scrollTop = DOM.getElementPropertyInt(contentNode, "scrollTop"); + int scrollLeft = DOM.getElementPropertyInt(contentNode, + "scrollLeft"); + if (hasChildren) { + // Remove children temporary form normal flow to detect proper + // size + contentEl = getWidget().getElement(); + origPositioning = DOM.getStyleAttribute(contentEl, "position"); + DOM.setStyleAttribute(contentEl, "position", "absolute"); + } + + // Set defaults + DOM.setStyleAttribute(contentNode, "overflow", "hidden"); + DOM.setStyleAttribute(contentNode, "height", ""); + + // Calculate target height + super.setHeight(height); + final int targetHeight = getOffsetHeight(); + + // Calculate used height + super.setHeight(""); + final int usedHeight = DOM.getElementPropertyInt(bottomDecoration, + "offsetTop") + + DOM.getElementPropertyInt(bottomDecoration, + "offsetHeight") + - DOM.getElementPropertyInt(getElement(), "offsetTop"); + + // Calculate content area height (don't allow negative values) + int h = targetHeight - usedHeight; + if (h < 0) { + h = 0; + } + + // Set proper values for content element + DOM.setStyleAttribute(contentNode, "height", h + "px"); + DOM.setStyleAttribute(contentNode, "overflow", "auto"); + + // Restore content to flow + if (hasChildren) { + ApplicationConnection.getConsole().log( + "positioning:" + origPositioning); + DOM.setStyleAttribute(contentEl, "position", origPositioning); + } + // restore scroll position + DOM.setElementPropertyInt(contentNode, "scrollTop", scrollTop); + DOM.setElementPropertyInt(contentNode, "scrollLeft", scrollLeft); + + } else { + DOM.setStyleAttribute(contentNode, "height", ""); + } + + if (runGeckoFix && BrowserInfo.get().isGecko()) { + // workaround for #1764 + if (width == null || width.equals("")) { + if (geckoCaptionMeter == null) { + geckoCaptionMeter = DOM.createDiv(); + DOM.appendChild(captionNode, geckoCaptionMeter); + } + int captionWidth = DOM.getElementPropertyInt(captionText, + "offsetWidth"); + int availWidth = DOM.getElementPropertyInt(geckoCaptionMeter, + "offsetWidth"); + if (captionWidth == availWidth) { + /* + * Caption width defines panel width -> Gecko based browsers + * somehow fails to float things right, without the + * "noncode" below + */ + setWidth(getOffsetWidth() + "px"); + } else { + DOM.setStyleAttribute(captionNode, "width", ""); + } + } + } + Util.runDescendentsLayout(this); + } + + public void onBrowserEvent(Event event) { + final Element target = DOM.eventGetTarget(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)) { + ApplicationConnection.getConsole().log("scrollded panel"); + scrollLeft = newscrollLeft; + scrollTop = newscrollTop; + client.updateVariable(id, "scrollTop", scrollTop, false); + client.updateVariable(id, "scrollLeft", scrollLeft, false); + } + } else if (errorIndicatorElement != null + && DOM.compare(target, errorIndicatorElement)) { + switch (type) { + case Event.ONMOUSEOVER: + if (errorMessage != null) { + errorMessage.showAt(errorIndicatorElement); + } + break; + case Event.ONMOUSEOUT: + if (errorMessage != null) { + errorMessage.hide(); + } + break; + case Event.ONCLICK: + ApplicationConnection.getConsole().log( + DOM.getInnerHTML(errorMessage.getElement())); + return; + default: + break; + } + } + } + + /** + * Panel handles dimensions by itself. + */ + public void setHeight(String height) { + // NOP + } + + /** + * Panel handles dimensions by itself. + */ + public void setWidth(String width) { + this.width = width; + // Let browser handle 100% width (DIV element takes all size by + // default). + // This way we can specify borders for Panel's outer element. + if (width.equals("100%")) { + super.setWidth(""); + } else { + super.setWidth(width); + } + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IPasswordField.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IPasswordField.java new file mode 100644 index 0000000000..0690edff94 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IPasswordField.java @@ -0,0 +1,21 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; + +/** + * This class represents a password field. + * + * @author IT Mill Ltd. + * + */ +public class IPasswordField extends ITextField { + + public IPasswordField() { + super(DOM.createInputPassword()); + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IPopupCalendar.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IPopupCalendar.java new file mode 100644 index 0000000000..63af2f75d6 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IPopupCalendar.java @@ -0,0 +1,126 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; +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.ClickListener; +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.google.gwt.user.client.ui.PopupPanel.PositionCallback; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class IPopupCalendar extends ITextualDate implements Paintable, Field, + ClickListener, PopupListener { + + private final Button calendarToggle; + + private final CalendarPanel calendar; + + private final ToolkitOverlay popup; + private boolean open = false; + + public IPopupCalendar() { + super(); + + calendarToggle = new Button(); + calendarToggle.setStyleName(CLASSNAME + "-button"); + calendarToggle.setText("..."); + calendarToggle.addClickListener(this); + add(calendarToggle); + + calendar = new CalendarPanel(this); + popup = new ToolkitOverlay(true, true, true); + popup.setStyleName(IDateField.CLASSNAME + "-popup"); + popup.setWidget(calendar); + popup.addPopupListener(this); + + DOM.setElementProperty(calendar.getElement(), "id", + "PID_TOOLKIT_POPUPCAL"); + + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + super.updateFromUIDL(uidl, client); + if (date != null) { + calendar.updateCalendar(); + } + calendarToggle.setEnabled(enabled); + } + + public void onClick(Widget sender) { + if (sender == calendarToggle && !open) { + open = true; + calendar.updateCalendar(); + // clear previous values + popup.setWidth(""); + popup.setHeight(""); + popup.setPopupPositionAndShow(new PositionCallback() { + public void setPosition(int offsetWidth, int offsetHeight) { + final int w = offsetWidth; + final int h = offsetHeight; + int t = calendarToggle.getAbsoluteTop(); + int l = calendarToggle.getAbsoluteLeft(); + if (l + w > Window.getClientWidth() + + Window.getScrollLeft()) { + l = Window.getClientWidth() + Window.getScrollLeft() + - w; + } + if (t + h > Window.getClientHeight() + + Window.getScrollTop()) { + t = Window.getClientHeight() + Window.getScrollTop() + - h - calendarToggle.getOffsetHeight() - 30; + l += calendarToggle.getOffsetWidth(); + } + + // fix size + popup.setWidth(w + "px"); + popup.setHeight(h + "px"); + + popup.setPopupPosition(l, t + + calendarToggle.getOffsetHeight() + 2); + + setFocus(true); + } + }); + } + } + + public void onPopupClosed(PopupPanel sender, boolean autoClosed) { + if (sender == popup) { + buildDate(); + // Sigh. + Timer t = new Timer() { + public void run() { + open = false; + } + }; + t.schedule(100); + } + } + + /** + * Sets focus to Calendar panel. + * + * @param focus + */ + public void setFocus(boolean focus) { + calendar.setFocus(focus); + } + + protected int getFieldExtraWidth() { + if (fieldExtraWidth < 0) { + fieldExtraWidth = super.getFieldExtraWidth(); + fieldExtraWidth += calendarToggle.getOffsetWidth(); + } + return fieldExtraWidth; + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IProgressIndicator.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IProgressIndicator.java new file mode 100644 index 0000000000..67100b8544 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IProgressIndicator.java @@ -0,0 +1,96 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class IProgressIndicator extends Widget implements Paintable { + + private static final String CLASSNAME = "i-progressindicator"; + Element wrapper = DOM.createDiv(); + Element indicator = DOM.createDiv(); + private ApplicationConnection client; + private final Poller poller; + private boolean indeterminate = false; + private boolean pollerSuspendedDueDetach; + + public IProgressIndicator() { + setElement(wrapper); + setStyleName(CLASSNAME); + DOM.appendChild(wrapper, indicator); + poller = new Poller(); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, true)) { + return; + } + + poller.cancel(); + this.client = client; + if (client.updateComponent(this, uidl, true)) { + return; + } + + indeterminate = uidl.getBooleanAttribute("indeterminate"); + + String style = CLASSNAME; + if (uidl.getBooleanAttribute("disabled")) { + style += "-disabled"; + } + + if (indeterminate) { + this.setStyleName(style + "-indeterminate"); + } else { + setStyleName(style); + try { + final float f = Float.parseFloat(uidl + .getStringAttribute("state")); + final int size = Math.round(100 * f); + DOM.setStyleAttribute(indicator, "width", size + "%"); + } catch (final Exception e) { + } + } + + if (!uidl.getBooleanAttribute("disabled")) { + poller.scheduleRepeating(uidl.getIntAttribute("pollinginterval")); + } + } + + protected void onAttach() { + super.onAttach(); + if (pollerSuspendedDueDetach) { + poller.run(); + } + } + + protected void onDetach() { + super.onDetach(); + poller.cancel(); + pollerSuspendedDueDetach = true; + } + + public void setVisible(boolean visible) { + super.setVisible(visible); + if (!visible) { + poller.cancel(); + } + } + + class Poller extends Timer { + + public void run() { + client.sendPendingVariableChanges(); + } + + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IScrollTable.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IScrollTable.java new file mode 100644 index 0000000000..d15eacc769 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IScrollTable.java @@ -0,0 +1,2246 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.Vector; + +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.DeferredCommand; +import com.google.gwt.user.client.Element; +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.Panel; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.ScrollListener; +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.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; +import com.itmill.toolkit.terminal.gwt.client.ui.IScrollTable.IScrollTableBody.IScrollTableRow; + +/** + * IScrollTable + * + * IScrollTable 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 IScrollTableBody object which handles content. To save + * some bandwidth and to improve clients responsiveness with loads of data, in + * IScrollTableBody all rows are not necessary rendered. There are "spacers" in + * IScrollTableBody 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 IScrollTable 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 IScrollTable extends Composite implements Table, ScrollListener, + ContainerResizedListener { + + public static final String CLASSNAME = "i-table"; + /** + * multiple of pagelenght which component will cache when requesting more + * rows + */ + private static final double CACHE_RATE = 2; + /** + * fraction of pageLenght which can be scrolled without making new request + */ + private static final double CACHE_REACT_RATE = 1.5; + + public static final char ALIGN_CENTER = 'c'; + public static final char ALIGN_LEFT = 'b'; + public static final char ALIGN_RIGHT = 'e'; + private int firstRowInViewPort = 0; + private int pageLength = 15; + + private boolean showRowHeaders = false; + + private String[] columnOrder; + + private ApplicationConnection client; + private String paintableId; + + private boolean immediate; + + private int selectMode = Table.SELECT_MODE_NONE; + + private final HashSet selectedRowKeys = new HashSet(); + + private boolean initializedAndAttached = false; + + private final TableHead tHead = new TableHead(); + + private final ScrollPanel bodyContainer = new ScrollPanel(); + + private int totalRows; + + private Set collapsedColumns; + + private final RowRequestHandler rowRequestHandler; + private IScrollTableBody tBody; + private String width; + private String height; + private int firstvisible = 0; + private boolean sortAscending; + private String sortColumn; + 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; + private final FlowPanel panel; + private boolean enabled; + private boolean showColHeaders; + + /** flag to indicate that table body has changed */ + private boolean isNewBody = true; + + /** + * Stores old height for IE, that sometimes fails to return correct height + * for container element. Then this value is used as a fallback. + */ + private int oldAvailPixels; + + public IScrollTable() { + + bodyContainer.addScrollListener(this); + bodyContainer.setStyleName(CLASSNAME + "-body"); + + panel = new FlowPanel(); + panel.setStyleName(CLASSNAME); + panel.add(tHead); + panel.add(bodyContainer); + + rowRequestHandler = new RowRequestHandler(); + + initWidget(panel); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, true)) { + return; + } + + enabled = !uidl.hasAttribute("disabled"); + + this.client = client; + paintableId = uidl.getStringAttribute("id"); + immediate = uidl.getBooleanAttribute("immediate"); + final int newTotalRows = uidl.getIntAttribute("totalrows"); + if (newTotalRows != totalRows) { + totalRows = newTotalRows; + if (tBody != null) { + initializedAndAttached = false; + initialContentReceived = false; + isNewBody = true; + } + } + + pageLength = uidl.getIntAttribute("pagelength"); + if (pageLength == 0) { + pageLength = totalRows; + } + firstvisible = uidl.hasVariable("firstvisible") ? uidl + .getIntVariable("firstvisible") : 0; + + showRowHeaders = uidl.getBooleanAttribute("rowheaders"); + showColHeaders = uidl.getBooleanAttribute("colheaders"); + + if (uidl.hasAttribute("width")) { + width = uidl.getStringAttribute("width"); + } + if (uidl.hasAttribute("height")) { + height = uidl.getStringAttribute("height"); + } + + if (uidl.hasVariable("sortascending")) { + sortAscending = uidl.getBooleanVariable("sortascending"); + sortColumn = uidl.getStringVariable("sortcolumn"); + } + + if (uidl.hasVariable("selected")) { + final Set selectedKeys = uidl + .getStringArrayVariableAsSet("selected"); + selectedRowKeys.clear(); + for (final Iterator it = selectedKeys.iterator(); it.hasNext();) { + selectedRowKeys.add(it.next()); + } + } + + if (uidl.hasAttribute("selectmode")) { + if (uidl.getBooleanAttribute("readonly")) { + selectMode = Table.SELECT_MODE_NONE; + } else if (uidl.getStringAttribute("selectmode").equals("multi")) { + selectMode = Table.SELECT_MODE_MULTI; + } else if (uidl.getStringAttribute("selectmode").equals("single")) { + selectMode = Table.SELECT_MODE_SINGLE; + } else { + selectMode = Table.SELECT_MODE_NONE; + } + } + + if (uidl.hasVariable("columnorder")) { + columnReordering = true; + columnOrder = uidl.getStringArrayVariable("columnorder"); + } + + if (uidl.hasVariable("collapsedcolumns")) { + tHead.setColumnCollapsingAllowed(true); + collapsedColumns = uidl + .getStringArrayVariableAsSet("collapsedcolumns"); + } else { + tHead.setColumnCollapsingAllowed(false); + } + + UIDL rowData = null; + for (final Iterator it = uidl.getChildIterator(); it.hasNext();) { + final UIDL c = (UIDL) it.next(); + if (c.getTag().equals("rows")) { + rowData = c; + } else if (c.getTag().equals("actions")) { + updateActionMap(c); + } else if (c.getTag().equals("visiblecolumns")) { + tHead.updateCellsFromUIDL(c); + } + } + updateHeader(uidl.getStringArrayAttribute("vcolorder")); + + if (initializedAndAttached) { + updateBody(rowData, uidl.getIntAttribute("firstrow"), uidl + .getIntAttribute("rows")); + } else { + if (tBody != null) { + tBody.removeFromParent(); + client.unregisterChildPaintables(tBody); + } + tBody = new IScrollTableBody(); + + tBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"), + uidl.getIntAttribute("rows")); + bodyContainer.add(tBody); + initialContentReceived = true; + if (isAttached()) { + sizeInit(); + } + } + hideScrollPositionAnnotation(); + } + + private void updateActionMap(UIDL c) { + final Iterator it = c.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", client.translateToolkitUri(action + .getStringAttribute("icon"))); + } + } + + } + + public String getActionCaption(String actionKey) { + return (String) actionMap.get(actionKey + "_c"); + } + + public String getActionIcon(String actionKey) { + return (String) actionMap.get(actionKey + "_i"); + } + + private void updateHeader(String[] strings) { + if (strings == null) { + return; + } + + int visibleCols = strings.length; + int colIndex = 0; + if (showRowHeaders) { + tHead.enableColumn("0", colIndex); + visibleCols++; + visibleColOrder = new String[visibleCols]; + visibleColOrder[colIndex] = "0"; + colIndex++; + } else { + visibleColOrder = new String[visibleCols]; + tHead.removeCell("0"); + } + + 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); + + } + + /** + * @param uidl + * which contains row data + * @param firstRow + * first row in data set + * @param reqRows + * amount of rows in data set + */ + private void updateBody(UIDL uidl, int firstRow, int reqRows) { + if (uidl == null || reqRows < 1) { + // container is empty, remove possibly existing rows + if (firstRow < 0) { + while (tBody.getLastRendered() > tBody.firstRendered) { + tBody.unlinkRow(false); + } + tBody.unlinkRow(false); + } + return; + } + + tBody.renderRows(uidl, firstRow, reqRows); + + final int optimalFirstRow = (int) (firstRowInViewPort - pageLength + * CACHE_RATE); + boolean cont = true; + while (cont && tBody.getLastRendered() > optimalFirstRow + && tBody.getFirstRendered() < optimalFirstRow) { + // client.console.log("removing row from start"); + cont = tBody.unlinkRow(true); + } + final int optimalLastRow = (int) (firstRowInViewPort + pageLength + pageLength + * CACHE_RATE); + cont = true; + while (cont && tBody.getLastRendered() > optimalLastRow) { + // client.console.log("removing row from the end"); + cont = tBody.unlinkRow(false); + } + tBody.fixSpacers(); + + } + + /** + * 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 ("0".equals(colKey)) { + return 0; + } + for (int i = 0; i < visibleColOrder.length; i++) { + if (visibleColOrder[i].equals(colKey)) { + return i; + } + } + return -1; + } + + 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(); + } + + private void setColWidth(int colIndex, int w) { + final HeaderCell cell = tHead.getHeaderCell(colIndex); + cell.setWidth(w); + tBody.setColWidth(colIndex, w); + } + + private int getColWidth(String colKey) { + return tHead.getHeaderCell(colKey).getWidth(); + } + + private IScrollTableRow getRenderedRowByKey(String key) { + final Iterator it = tBody.iterator(); + IScrollTableRow r = null; + while (it.hasNext()) { + r = (IScrollTableRow) it.next(); + if (r.getKey().equals(key)) { + return r; + } + } + return null; + } + + private void reOrderColumn(String columnKey, int newIndex) { + + final int oldIndex = getColIndexByKey(columnKey); + + // Change header order + tHead.moveCell(oldIndex, newIndex); + + // Change body order + tBody.moveCol(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); + } + + protected void onAttach() { + super.onAttach(); + if (initialContentReceived) { + sizeInit(); + } + } + + protected void onDetach() { + 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); + } + } + } + + /** + * 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 + */ + private void sizeInit() { + /* + * 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(); + int i = 0; + int totalExplicitColumnsWidths = 0; + int total = 0; + + final int[] widths = new int[tHead.visibleCells.size()]; + + if (width == null) { + // if this is a re-init, remove old manually fixed size + bodyContainer.setWidth(""); + tHead.setWidth(""); + super.setWidth(""); + } + + tHead.enableBrowserIntelligence(); + // first loop: collect natural widths + while (headCells.hasNext()) { + final HeaderCell hCell = (HeaderCell) headCells.next(); + int w = hCell.getWidth(); + if (w > 0) { + // server has defined column width explicitly + totalExplicitColumnsWidths += w; + } else { + final int hw = hCell.getOffsetWidth(); + final int cw = tBody.getColWidth(i); + w = (hw > cw ? hw : cw) + IScrollTableBody.CELL_EXTRA_WIDTH; + } + widths[i] = w; + total += w; + i++; + } + + tHead.disableBrowserIntelligence(); + + if (height == null) { + bodyContainer.setHeight((tBody.getRowHeight() * pageLength) + "px"); + } else { + mySetHeight(height); + iLayout(); + } + + if (width == null) { + int w = total; + w += getScrollbarWidth(); + bodyContainer.setWidth(w + "px"); + tHead.setWidth(w + "px"); + super.setWidth(w + "px"); + } else { + if (width.indexOf("px") > 0) { + bodyContainer.setWidth(width); + tHead.setWidth(width); + super.setWidth(width); + } else if (width.indexOf("%") > 0) { + if (!width.equals("100%")) { + super.setWidth(width); + } + // contained blocks are relatively to container element + bodyContainer.setWidth("100%"); + tHead.setWidth("100%"); + + } + } + + int availW = tBody.getAvailableWidth(); + // Hey IE, are you really sure about this? + availW = tBody.getAvailableWidth(); + + if (availW > total) { + // natural size is smaller than available space + final int extraSpace = availW - total; + final int totalWidthR = total - totalExplicitColumnsWidths; + if (totalWidthR > 0) { + // now we will share this sum relatively to those without + // explicit width + headCells = tHead.iterator(); + i = 0; + HeaderCell hCell; + while (headCells.hasNext()) { + hCell = (HeaderCell) headCells.next(); + if (hCell.getWidth() == -1) { + int w = widths[i]; + final int newSpace = extraSpace * w / totalWidthR; + w += newSpace; + widths[i] = w; + } + i++; + } + } + } else { + // bodys 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); + } + i++; + } + + isNewBody = false; + + if (firstvisible > 0) { + // Deferred due some Firefox oddities. IE & Safari could survive + // without + DeferredCommand.addCommand(new Command() { + public void execute() { + bodyContainer.setScrollPosition(firstvisible + * tBody.getRowHeight()); + firstRowInViewPort = firstvisible; + } + }); + } + + if (enabled) { + // Do we need cache rows + if (tBody.getLastRendered() + 1 < firstRowInViewPort + pageLength + + CACHE_REACT_RATE * pageLength) { + DeferredCommand.addCommand(new Command() { + public void execute() { + if (totalRows - 1 > tBody.getLastRendered()) { + // fetch cache rows + rowRequestHandler.setReqFirstRow(tBody + .getLastRendered() + 1); + rowRequestHandler + .setReqRows((int) (pageLength * CACHE_RATE)); + rowRequestHandler.deferRowFetch(1); + } + } + }); + } + } + initializedAndAttached = true; + } + + public void iLayout() { + if (height != null) { + if (height.equals("100%")) { + /* + * We define height in pixels with 100% not to include borders + * which is what users usually want. So recalculate pixels via + * setHeight. + */ + mySetHeight(height); + } + + int contentH = (DOM.getElementPropertyInt(getElement(), + "clientHeight") - tHead.getOffsetHeight()); + if (contentH < 0) { + contentH = 0; + } + bodyContainer.setHeight(contentH + "px"); + } + } + + private int getScrollbarWidth() { + return bodyContainer.getOffsetWidth() + - DOM.getElementPropertyInt(bodyContainer.getElement(), + "clientWidth"); + } + + /** + * This method has logic which rows needs to be requested from server when + * user scrolls + */ + public void onScroll(Widget widget, int scrollLeft, int scrollTop) { + if (!initializedAndAttached) { + return; + } + if (!enabled) { + bodyContainer.setScrollPosition(firstRowInViewPort + * tBody.getRowHeight()); + return; + } + + rowRequestHandler.cancel(); + + // fix headers horizontal scrolling + tHead.setHorizontalScrollPosition(scrollLeft); + + firstRowInViewPort = (int) Math.ceil(scrollTop + / (double) tBody.getRowHeight()); + // ApplicationConnection.getConsole().log( + // "At scrolltop: " + scrollTop + " At row " + firstRowInViewPort); + + int postLimit = (int) (firstRowInViewPort + pageLength + 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 = tBody.getLastRendered(); + final int firstRendered = tBody.getFirstRendered(); + + if (postLimit <= lastRendered && preLimit >= firstRendered) { + client.updateVariable(paintableId, "firstvisible", + firstRowInViewPort, false); + return; // scrolled withing "non-react area" + } + + if (firstRowInViewPort - pageLength * CACHE_RATE > lastRendered + || firstRowInViewPort + pageLength + pageLength * CACHE_RATE < firstRendered) { + // need a totally new set + // ApplicationConnection.getConsole().log( + // "Table: need a totally new set"); + rowRequestHandler + .setReqFirstRow((int) (firstRowInViewPort - pageLength + * CACHE_RATE)); + int last = firstRowInViewPort + (int) CACHE_RATE * pageLength + + pageLength; + if (last > totalRows) { + last = totalRows - 1; + } + rowRequestHandler.setReqRows(last + - rowRequestHandler.getReqFirstRow() + 1); + rowRequestHandler.deferRowFetch(); + return; + } + if (preLimit < firstRendered) { + // need some rows to the beginning of the rendered area + // ApplicationConnection + // .getConsole() + // .log( + // "Table: 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 + // ApplicationConnection.getConsole().log( + // "need some rows to the end of the rendered area"); + rowRequestHandler.setReqFirstRow(lastRendered + 1); + rowRequestHandler.setReqRows((int) ((firstRowInViewPort + + pageLength + pageLength * CACHE_RATE) - lastRendered)); + rowRequestHandler.deferRowFetch(); + } + + } + + private void announceScrollPosition() { + if (scrollPositionElement == null) { + scrollPositionElement = DOM.createDiv(); + DOM.setElementProperty(scrollPositionElement, "className", + "i-table-scrollposition"); + DOM.appendChild(getElement(), scrollPositionElement); + } + + DOM.setStyleAttribute(scrollPositionElement, "position", "absolute"); + DOM.setStyleAttribute(scrollPositionElement, "marginLeft", (DOM + .getElementPropertyInt(getElement(), "offsetWidth") / 2 - 80) + + "px"); + DOM.setStyleAttribute(scrollPositionElement, "marginTop", -(DOM + .getElementPropertyInt(getElement(), "offsetHeight") - 2) + + "px"); + + // indexes go from 1-totalRows, as rowheaders in index-mode indicate + int last = (firstRowInViewPort + (bodyContainer.getOffsetHeight() / tBody + .getRowHeight())); + if (last > totalRows) { + last = totalRows; + } + DOM.setInnerHTML(scrollPositionElement, "" + + (firstRowInViewPort + 1) + " – " + last + "..." + + ""); + DOM.setStyleAttribute(scrollPositionElement, "display", "block"); + } + + private void hideScrollPositionAnnotation() { + if (scrollPositionElement != null) { + DOM.setStyleAttribute(scrollPositionElement, "display", "none"); + } + } + + private class RowRequestHandler extends Timer { + + private int reqFirstRow = 0; + private int reqRows = 0; + + public void deferRowFetch() { + deferRowFetch(250); + } + + public void deferRowFetch(int msec) { + if (reqRows > 0 && reqFirstRow < totalRows) { + schedule(msec); + + // tell scroll position to user if currently "visible" rows are + // not rendered + if ((firstRowInViewPort + pageLength > tBody.getLastRendered()) + || (firstRowInViewPort < tBody.getFirstRendered())) { + announceScrollPosition(); + } else { + hideScrollPositionAnnotation(); + } + } + } + + public void setReqFirstRow(int reqFirstRow) { + if (reqFirstRow < 0) { + reqFirstRow = 0; + } else if (reqFirstRow >= totalRows) { + reqFirstRow = totalRows - 1; + } + this.reqFirstRow = reqFirstRow; + } + + public void setReqRows(int reqRows) { + this.reqRows = reqRows; + } + + public void run() { + ApplicationConnection.getConsole().log( + "Getting " + reqRows + " rows from " + reqFirstRow); + + int firstToBeRendered = tBody.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; + } + } + + int lastToBeRendered = tBody.lastRendered; + + if (reqFirstRow + reqRows - 1 > lastToBeRendered) { + lastToBeRendered = reqFirstRow + reqRows - 1; + } else if (firstRowInViewPort + pageLength + pageLength + * CACHE_RATE < lastToBeRendered) { + lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * CACHE_RATE)); + if (lastToBeRendered >= totalRows) { + lastToBeRendered = totalRows - 1; + } + } + + client.updateVariable(paintableId, "firstToBeRendered", + firstToBeRendered, false); + + client.updateVariable(paintableId, "lastToBeRendered", + lastToBeRendered, false); + + client.updateVariable(paintableId, "firstvisible", + firstRowInViewPort, false); + client.updateVariable(paintableId, "reqfirstrow", reqFirstRow, + false); + client.updateVariable(paintableId, "reqrows", reqRows, true); + } + + public int getReqFirstRow() { + return reqFirstRow; + } + + public int getReqRows() { + return reqRows; + } + + /** + * Sends request to refresh content at this position. + */ + public void refreshContent() { + 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 { + + private static final int DRAG_WIDGET_WIDTH = 4; + + private static final int MINIMUM_COL_WIDTH = 20; + + Element td = DOM.createTD(); + + Element captionContainer = DOM.createDiv(); + + Element colResizeWidget = DOM.createDiv(); + + Element floatingCopyOfHeaderCell; + + private boolean sortable = false; + private final String cid; + private boolean dragging; + + 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 char align = ALIGN_LEFT; + + public void setSortable(boolean b) { + sortable = b; + } + + public HeaderCell(String colId, String headerText) { + cid = colId; + + DOM.setElementProperty(colResizeWidget, "className", CLASSNAME + + "-resizer"); + DOM.setStyleAttribute(colResizeWidget, "width", DRAG_WIDGET_WIDTH + + "px"); + DOM.sinkEvents(colResizeWidget, Event.MOUSEEVENTS); + + setText(headerText); + + DOM.appendChild(td, colResizeWidget); + + DOM.setElementProperty(captionContainer, "className", CLASSNAME + + "-caption-container"); + + // ensure no clipping initially (problem on column additions) + DOM.setStyleAttribute(captionContainer, "overflow", "visible"); + + DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS); + + DOM.appendChild(td, captionContainer); + + DOM.sinkEvents(td, Event.MOUSEEVENTS); + + setElement(td); + } + + public void setWidth(int w) { + if (width == -1) { + // go to default mode, clip content if necessary + DOM.setStyleAttribute(captionContainer, "overflow", ""); + } + width = w; + DOM.setStyleAttribute(captionContainer, "width", (w + - DRAG_WIDGET_WIDTH - 4) + + "px"); + setWidth(w + "px"); + } + + public int getWidth() { + return width; + } + + public void setText(String headerText) { + DOM.setInnerHTML(captionContainer, headerText); + } + + public String getColKey() { + return cid; + } + + private void setSorted(boolean sorted) { + if (sorted) { + if (sortAscending) { + this.setStyleName(CLASSNAME + "-header-cell-asc"); + } else { + this.setStyleName(CLASSNAME + "-header-cell-desc"); + } + } else { + this.setStyleName(CLASSNAME + "-header-cell"); + } + } + + /** + * Handle column reordering. + */ + public void onBrowserEvent(Event event) { + if (enabled) { + if (isResizing + || DOM.compare(DOM.eventGetTarget(event), + colResizeWidget)) { + onResizeEvent(event); + } else { + handleCaptionEvent(event); + } + } + } + + private void createFloatingCopy() { + floatingCopyOfHeaderCell = DOM.createDiv(); + DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td)); + floatingCopyOfHeaderCell = DOM + .getChild(floatingCopyOfHeaderCell, 1); + DOM.setElementProperty(floatingCopyOfHeaderCell, "className", + CLASSNAME + "-header-drag"); + updateFloatingCopysPosition(DOM.getAbsoluteLeft(td), DOM + .getAbsoluteTop(td)); + DOM.appendChild(RootPanel.get().getElement(), + floatingCopyOfHeaderCell); + } + + private void updateFloatingCopysPosition(int x, int y) { + x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell, + "offsetWidth") / 2; + DOM.setStyleAttribute(floatingCopyOfHeaderCell, "left", x + "px"); + if (y > 0) { + DOM.setStyleAttribute(floatingCopyOfHeaderCell, "top", (y + 7) + + "px"); + } + } + + private void hideFloatingCopy() { + DOM.removeChild(RootPanel.get().getElement(), + floatingCopyOfHeaderCell); + floatingCopyOfHeaderCell = null; + } + + protected void handleCaptionEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONMOUSEDOWN: + ApplicationConnection.getConsole().log( + "HeaderCaption: mouse down"); + if (columnReordering) { + dragging = true; + moved = false; + colIndex = getColIndexByKey(cid); + DOM.setCapture(getElement()); + headerX = tHead.getAbsoluteLeft(); + ApplicationConnection + .getConsole() + .log( + "HeaderCaption: Caption set to capture mouse events"); + DOM.eventPreventDefault(event); // prevent selecting text + } + break; + case Event.ONMOUSEUP: + ApplicationConnection.getConsole() + .log("HeaderCaption: mouseUP"); + if (columnReordering) { + dragging = false; + DOM.releaseCapture(getElement()); + ApplicationConnection.getConsole().log( + "HeaderCaption: Stopped column reordering"); + if (moved) { + hideFloatingCopy(); + tHead.removeSlotFocus(); + if (closestSlot != colIndex + && closestSlot != (colIndex + 1)) { + if (closestSlot > colIndex) { + reOrderColumn(cid, closestSlot - 1); + } else { + reOrderColumn(cid, closestSlot); + } + } + } + } + + if (!moved) { + // mouse event was a click to header -> sort column + if (sortable) { + if (sortColumn.equals(cid)) { + // just toggle order + client.updateVariable(paintableId, "sortascending", + !sortAscending, false); + } else { + // set table scrolled by this column + client.updateVariable(paintableId, "sortcolumn", + cid, false); + } + // get also cache columns at the same request + bodyContainer.setScrollPosition(0); + firstvisible = 0; + rowRequestHandler.setReqFirstRow(0); + rowRequestHandler.setReqRows((int) (2 * pageLength + * CACHE_RATE + pageLength)); + rowRequestHandler.deferRowFetch(); + } + break; + } + break; + case Event.ONMOUSEMOVE: + if (dragging) { + ApplicationConnection.getConsole().log( + "HeaderCaption: Dragging column, optimal index..."); + if (!moved) { + createFloatingCopy(); + moved = true; + } + final int x = DOM.eventGetClientX(event) + + DOM.getElementPropertyInt(tHead.hTableWrapper, + "scrollLeft"); + 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); + slotX += getColWidth(colKey); + } + final int dist = Math.abs(x - slotX); + if (closestDistance == -1 || dist < closestDistance) { + closestDistance = dist; + closestSlot = i; + } + } + tHead.focusSlot(closestSlot); + + updateFloatingCopysPosition(DOM.eventGetClientX(event), -1); + ApplicationConnection.getConsole().log("" + closestSlot); + } + break; + default: + break; + } + } + + private void onResizeEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONMOUSEDOWN: + isResizing = true; + DOM.setCapture(getElement()); + dragStartX = DOM.eventGetClientX(event); + colIndex = getColIndexByKey(cid); + originalWidth = getWidth(); + DOM.eventPreventDefault(event); + break; + case Event.ONMOUSEUP: + isResizing = false; + DOM.releaseCapture(getElement()); + break; + case Event.ONMOUSEMOVE: + if (isResizing) { + final int deltaX = DOM.eventGetClientX(event) - dragStartX; + if (deltaX == 0) { + return; + } + + int newWidth = originalWidth + deltaX; + if (newWidth < MINIMUM_COL_WIDTH) { + newWidth = MINIMUM_COL_WIDTH; + } + setColWidth(colIndex, newWidth); + } + break; + default: + break; + } + } + + public String getCaption() { + return DOM.getInnerText(captionContainer); + } + + public boolean isEnabled() { + return getParent() != null; + } + + public void setAlign(char c) { + if (align != c) { + switch (c) { + case ALIGN_CENTER: + DOM.setStyleAttribute(captionContainer, "textAlign", + "center"); + break; + case ALIGN_RIGHT: + DOM.setStyleAttribute(captionContainer, "textAlign", + "right"); + break; + default: + DOM.setStyleAttribute(captionContainer, "textAlign", ""); + break; + } + } + align = c; + } + + public char getAlign() { + return align; + } + + } + + /** + * HeaderCell that is header cell for row headers. + * + * Reordering disabled and clicking on it resets sorting. + */ + public class RowHeadersHeaderCell extends HeaderCell { + + RowHeadersHeaderCell() { + super("0", ""); + } + + 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 = 9000; + + Vector visibleCells = new Vector(); + + 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() { + DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden"); + DOM.setElementProperty(hTableWrapper, "className", CLASSNAME + + "-header"); + + // TODO move styles to CSS + DOM.setElementProperty(columnSelector, "className", CLASSNAME + + "-column-selector"); + DOM.setStyleAttribute(columnSelector, "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); + + setStyleName(CLASSNAME + "-header-wrap"); + + DOM.sinkEvents(columnSelector, Event.ONCLICK); + + availableCells.put("0", new RowHeadersHeaderCell()); + } + + public void updateCellsFromUIDL(UIDL uidl) { + Iterator it = uidl.getChildIterator(); + HashSet updated = new HashSet(); + updated.add("0"); + while (it.hasNext()) { + final UIDL col = (UIDL) it.next(); + final String cid = col.getStringAttribute("cid"); + updated.add(cid); + HeaderCell c = getHeaderCell(cid); + if (c == null) { + c = new HeaderCell(cid, col.getStringAttribute("caption")); + availableCells.put(cid, c); + if (initializedAndAttached) { + // we will need a column width recalculation + initializedAndAttached = false; + initialContentReceived = false; + isNewBody = true; + } + } else { + c.setText(col.getStringAttribute("caption")); + } + + if (col.hasAttribute("sortable")) { + c.setSortable(true); + if (cid.equals(sortColumn)) { + c.setSorted(true); + } else { + c.setSorted(false); + } + } + if (col.hasAttribute("align")) { + c.setAlign(col.getStringAttribute("align").charAt(0)); + } + if (col.hasAttribute("width")) { + final String width = col.getStringAttribute("width"); + c.setWidth(Integer.parseInt(width)); + } + // TODO icon + } + // check for orphaned header cells + it = availableCells.keySet().iterator(); + while (it.hasNext()) { + String cid = (String) it.next(); + if (!updated.contains(cid)) { + removeCell(cid); + it.remove(); + } + } + + } + + public void enableColumn(String cid, int index) { + final HeaderCell c = getHeaderCell(cid); + if (!c.isEnabled() || getHeaderCell(index) != c) { + setHeaderCell(index, c); + if (c.getWidth() == -1) { + if (initializedAndAttached) { + // column is not drawn before, + // we will need a column width recalculation + initializedAndAttached = false; + initialContentReceived = false; + isNewBody = true; + } + } + } + } + + public int getVisibleCellCount() { + return visibleCells.size(); + } + + public void setHorizontalScrollPosition(int scrollLeft) { + DOM.setElementPropertyInt(hTableWrapper, "scrollLeft", scrollLeft); + } + + public void setColumnCollapsingAllowed(boolean cc) { + if (cc) { + DOM.setStyleAttribute(columnSelector, "display", "block"); + } else { + DOM.setStyleAttribute(columnSelector, "display", "none"); + } + } + + public void disableBrowserIntelligence() { + DOM.setStyleAttribute(hTableContainer, "width", WRAPPER_WIDTH + + "px"); + } + + public void enableBrowserIntelligence() { + DOM.setStyleAttribute(hTableContainer, "width", ""); + } + + public void setHeaderCell(int index, HeaderCell cell) { + if (cell.isEnabled()) { + // we're moving the cell + DOM.removeChild(tr, cell.getElement()); + orphan(cell); + } + if (index < visibleCells.size()) { + // insert to right slot + DOM.insertChild(tr, cell.getElement(), index); + adopt(cell); + visibleCells.insertElementAt(cell, index); + + } 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 < 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 (HeaderCell) 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.insertElementAt(hCell, newIndex); + } + + public Iterator iterator() { + return visibleCells.iterator(); + } + + 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) { + DOM.setElementProperty(DOM.getFirstChild(DOM.getChild(tr, + index - 1)), "className", CLASSNAME + "-resizer " + + CLASSNAME + "-focus-slot-right"); + } else { + DOM.setElementProperty(DOM.getFirstChild(DOM + .getChild(tr, index)), "className", CLASSNAME + + "-resizer " + CLASSNAME + "-focus-slot-left"); + } + focusedSlot = index; + } + + private void removeSlotFocus() { + if (focusedSlot < 0) { + return; + } + if (focusedSlot == 0) { + DOM.setElementProperty(DOM.getFirstChild(DOM.getChild(tr, + focusedSlot)), "className", CLASSNAME + "-resizer"); + } else if (focusedSlot > 0) { + DOM.setElementProperty(DOM.getFirstChild(DOM.getChild(tr, + focusedSlot - 1)), "className", CLASSNAME + "-resizer"); + } + focusedSlot = -1; + } + + public void onBrowserEvent(Event event) { + if (enabled) { + if (DOM.compare(DOM.eventGetTarget(event), columnSelector)) { + final int left = DOM.getAbsoluteLeft(columnSelector); + final int top = DOM.getAbsoluteTop(columnSelector) + + DOM.getElementPropertyInt(columnSelector, + "offsetHeight"); + client.getContextMenu().showAt(this, left, top); + } + } + } + + class VisibleColumnAction extends Action { + + String colKey; + private boolean collapsed; + + public VisibleColumnAction(String colKey) { + super(IScrollTable.TableHead.this); + this.colKey = colKey; + caption = tHead.getHeaderCell(colKey).getCaption(); + } + + public void execute() { + client.getContextMenu().hide(); + // toggle selected column + if (collapsedColumns.contains(colKey)) { + collapsedColumns.remove(colKey); + } else { + tHead.removeCell(colKey); + collapsedColumns.add(colKey); + } + + // update variable to server + client.updateVariable(paintableId, "collapsedcolumns", + collapsedColumns.toArray(), false); + // let rowRequestHandler determine proper rows + rowRequestHandler.refreshContent(); + } + + public void setCollapsed(boolean b) { + collapsed = b; + } + + /** + * Override default method to distinguish on/off columns + */ + public String getHTML() { + final StringBuffer buf = new StringBuffer(); + if (collapsed) { + buf.append(""); + } else { + buf.append(""); + } + buf.append(super.getHTML()); + buf.append(""); + + return buf.toString(); + } + + } + + /* + * Returns columns as Action array for column select popup + */ + public Action[] getActions() { + Object[] cols; + if (columnReordering) { + 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(); + } + } + final Action[] actions = new Action[cols.length]; + + for (int i = 0; i < cols.length; i++) { + final String cid = (String) cols[i]; + final HeaderCell c = getHeaderCell(cid); + final VisibleColumnAction a = new VisibleColumnAction(c + .getColKey()); + a.setCaption(c.getCaption()); + if (!c.isEnabled()) { + a.setCollapsed(true); + } + actions[i] = a; + } + return actions; + } + + public ApplicationConnection getClient() { + return client; + } + + 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; + } + + } + + /** + * This Panel can only contain IScrollTableRow type of widgets. This + * "simulates" very large table, keeping spacers which take room of + * unrendered rows. + * + */ + public class IScrollTableBody extends Panel { + + public static final int CELL_EXTRA_WIDTH = 20; + + public static final int DEFAULT_ROW_HEIGHT = 24; + + /** + * Amount of padding inside one table cell (this is reduced from the + * "cellContent" element's width). You may override this in your own + * widgetset. + */ + public static final int CELL_CONTENT_PADDING = 8; + + private int rowHeight = -1; + + private final List renderedRows = new Vector(); + + private boolean initDone = false; + + Element preSpacer = DOM.createDiv(); + Element postSpacer = DOM.createDiv(); + + Element container = DOM.createDiv(); + + Element tBody = DOM.createTBody(); + Element table = DOM.createTable(); + + private int firstRendered; + + private int lastRendered; + + private char[] aligns; + + IScrollTableBody() { + constructDOM(); + setElement(container); + } + + private void constructDOM() { + DOM.setElementProperty(table, "className", CLASSNAME + "-table"); + DOM.setElementProperty(preSpacer, "className", CLASSNAME + + "-row-spacer"); + DOM.setElementProperty(postSpacer, "className", CLASSNAME + + "-row-spacer"); + + DOM.appendChild(table, tBody); + DOM.appendChild(container, preSpacer); + DOM.appendChild(container, table); + DOM.appendChild(container, postSpacer); + + } + + public int getAvailableWidth() { + return DOM.getElementPropertyInt(preSpacer, "offsetWidth"); + } + + public void renderInitialRows(UIDL rowData, int firstIndex, int rows) { + firstRendered = firstIndex; + lastRendered = firstIndex + rows - 1; + final Iterator it = rowData.getChildIterator(); + aligns = tHead.getColumnAlignments(); + while (it.hasNext()) { + final IScrollTableRow row = new IScrollTableRow((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 IScrollTableRow row = createRow((UIDL) it.next()); + addRow(row); + lastRendered++; + } + fixSpacers(); + } else if (firstIndex + rows == firstRendered) { + final IScrollTableRow[] rowArray = new IScrollTableRow[rows]; + int i = rows; + while (it.hasNext()) { + i--; + rowArray[i] = createRow((UIDL) it.next()); + } + for (i = 0; i < rows; i++) { + addRowBeforeFirstRendered(rowArray[i]); + firstRendered--; + } + } else { + // completely new set of rows + while (lastRendered + 1 > firstRendered) { + unlinkRow(false); + } + final IScrollTableRow row = createRow((UIDL) it.next()); + firstRendered = firstIndex; + lastRendered = firstIndex - 1; + addRow(row); + lastRendered++; + setContainerHeight(); + fixSpacers(); + while (it.hasNext()) { + addRow(createRow((UIDL) it.next())); + lastRendered++; + } + fixSpacers(); + DeferredCommand.addCommand(new Command() { + public void execute() { + // this may be a new set of rows due content change, + // ensure we have proper cache rows + 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 (reactFirstRow < firstRendered + || reactLastRow > lastRendered) { + // re-fetch full cache area + reactFirstRow = (int) (firstRowInViewPort - pageLength + * CACHE_RATE); + reactLastRow = (int) (firstRowInViewPort + + pageLength + pageLength * CACHE_RATE); + if (reactFirstRow < 0) { + reactFirstRow = 0; + } + if (reactLastRow > totalRows) { + reactLastRow = totalRows - 1; + } + // fetch some lines before + rowRequestHandler.setReqFirstRow(reactFirstRow); + rowRequestHandler.setReqRows((int) (2 * pageLength + * CACHE_RATE + pageLength)); + rowRequestHandler.deferRowFetch(1); + } + } + }); + } + } + + /** + * 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 IScrollTableRow createRow(UIDL uidl) { + final IScrollTableRow row = new IScrollTableRow(uidl, aligns); + final int cells = DOM.getChildCount(row.getElement()); + for (int i = 0; i < cells; i++) { + final Element cell = DOM.getChild(row.getElement(), i); + final int w = IScrollTable.this + .getColWidth(getColKeyByIndex(i)); + DOM.setStyleAttribute(DOM.getFirstChild(cell), "width", + (w - CELL_CONTENT_PADDING) + "px"); + DOM.setStyleAttribute(cell, "width", w + "px"); + } + return row; + } + + private void addRowBeforeFirstRendered(IScrollTableRow row) { + IScrollTableRow first = null; + if (renderedRows.size() > 0) { + first = (IScrollTableRow) renderedRows.get(0); + } + if (first != null && first.getStyleName().indexOf("-odd") == -1) { + row.setStyleName(CLASSNAME + "-row-odd"); + } + if (row.isSelected()) { + row.addStyleName("i-selected"); + } + DOM.insertChild(tBody, row.getElement(), 0); + adopt(row); + renderedRows.add(0, row); + } + + private void addRow(IScrollTableRow row) { + IScrollTableRow last = null; + if (renderedRows.size() > 0) { + last = (IScrollTableRow) renderedRows + .get(renderedRows.size() - 1); + } + if (last != null && last.getStyleName().indexOf("-odd") == -1) { + row.setStyleName(CLASSNAME + "-row-odd"); + } + if (row.isSelected()) { + row.addStyleName("i-selected"); + } + DOM.appendChild(tBody, row.getElement()); + adopt(row); + renderedRows.add(row); + } + + public Iterator iterator() { + return renderedRows.iterator(); + } + + /** + * @return false if couldn't remove row + */ + public boolean unlinkRow(boolean fromBeginning) { + if (lastRendered - firstRendered < 0) { + return false; + } + int index; + if (fromBeginning) { + index = 0; + firstRendered++; + } else { + index = renderedRows.size() - 1; + lastRendered--; + } + final IScrollTableRow toBeRemoved = (IScrollTableRow) renderedRows + .get(index); + client.unregisterChildPaintables(toBeRemoved); + DOM.removeChild(tBody, toBeRemoved.getElement()); + orphan(toBeRemoved); + renderedRows.remove(index); + fixSpacers(); + return true; + } + + public boolean remove(Widget w) { + throw new UnsupportedOperationException(); + } + + protected void onAttach() { + super.onAttach(); + setContainerHeight(); + } + + /** + * Fix container blocks height according to totalRows to avoid + * "bouncing" when scrolling + */ + private void setContainerHeight() { + fixSpacers(); + DOM.setStyleAttribute(container, "height", totalRows + * getRowHeight() + "px"); + } + + private void fixSpacers() { + int prepx = getRowHeight() * firstRendered; + if (prepx < 0) { + prepx = 0; + } + DOM.setStyleAttribute(preSpacer, "height", prepx + "px"); + int postpx = getRowHeight() * (totalRows - 1 - lastRendered); + if (postpx < 0) { + postpx = 0; + } + DOM.setStyleAttribute(postSpacer, "height", postpx + "px"); + } + + public int getRowHeight() { + if (initDone) { + return rowHeight; + } else { + if (DOM.getChildCount(tBody) > 0) { + rowHeight = DOM + .getElementPropertyInt(tBody, "offsetHeight") + / DOM.getChildCount(tBody); + } else { + return DEFAULT_ROW_HEIGHT; + } + initDone = true; + return rowHeight; + } + } + + public int getColWidth(int i) { + if (initDone) { + final Element e = DOM.getChild(DOM.getChild(tBody, 0), i); + return DOM.getElementPropertyInt(e, "offsetWidth"); + } else { + return 0; + } + } + + public void setColWidth(int colIndex, int w) { + final int rows = DOM.getChildCount(tBody); + for (int i = 0; i < rows; i++) { + final Element cell = DOM.getChild(DOM.getChild(tBody, i), + colIndex); + DOM.setStyleAttribute(DOM.getFirstChild(cell), "width", + (w - CELL_CONTENT_PADDING) + "px"); + DOM.setStyleAttribute(cell, "width", w + "px"); + } + } + + public int getLastRendered() { + return lastRendered; + } + + public int getFirstRendered() { + return firstRendered; + } + + 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 IScrollTableRow row = (IScrollTableRow) rows.next(); + + final Element td = DOM.getChild(row.getElement(), oldIndex); + DOM.removeChild(row.getElement(), td); + + DOM.insertChild(row.getElement(), td, newIndex); + + } + + } + + public class IScrollTableRow extends Panel implements ActionOwner { + + Vector childWidgets = new Vector(); + private boolean selected = false; + private final int rowKey; + + private String[] actionKeys = null; + + private IScrollTableRow(int rowKey) { + this.rowKey = rowKey; + setElement(DOM.createElement("tr")); + DOM.sinkEvents(getElement(), Event.ONCLICK); + attachContextMenuEvent(getElement()); + setStyleName(CLASSNAME + "-row"); + } + + protected void onDetach() { + Util.removeContextMenuEvent(getElement()); + super.onDetach(); + } + + /** + * Attaches context menu event handler to given element. Attached + * handler fires showContextMenu function. + * + * @param el + * element where to attach contenxt menu event + */ + private native void attachContextMenuEvent(Element el) + /*-{ + var row = this; + el.oncontextmenu = function(e) { + if(!e) + e = $wnd.event; + row.@com.itmill.toolkit.terminal.gwt.client.ui.IScrollTable.IScrollTableBody.IScrollTableRow::showContextMenu(Lcom/google/gwt/user/client/Event;)(e); + return false; + }; + }-*/; + + public String getKey() { + return String.valueOf(rowKey); + } + + public IScrollTableRow(UIDL uidl, char[] aligns) { + this(uidl.getIntAttribute("key")); + + tHead.getColumnAlignments(); + int col = 0; + // row header + if (showRowHeaders) { + addCell(uidl.getStringAttribute("caption"), aligns[col++], + ""); + } + + if (uidl.hasAttribute("al")) { + actionKeys = uidl.getStringArrayAttribute("al"); + } + + final Iterator cells = uidl.getChildIterator(); + while (cells.hasNext()) { + final Object cell = cells.next(); + if (cell instanceof String) { + String style = ""; + if (uidl.hasAttribute("style-" + col)) { + style = uidl.getStringAttribute("style-" + col); + } + addCell(cell.toString(), aligns[col++], style); + } else { + final Paintable cellContent = client + .getPaintable((UIDL) cell); + (cellContent).updateFromUIDL((UIDL) cell, client); + String style = ""; + if (uidl.hasAttribute("style")) { + style = uidl.getStringAttribute("style"); + } + addCell((Widget) cellContent, aligns[col++], style); + } + } + if (uidl.hasAttribute("selected") && !isSelected()) { + toggleSelection(); + } + } + + public void addCell(String text, char align, String style) { + // String only content is optimized by not using Label widget + final Element td = DOM.createTD(); + final Element container = DOM.createDiv(); + String className = CLASSNAME + "-cell-content"; + if (style != null && !style.equals("")) { + className += " " + CLASSNAME + "-cell-content-" + style; + } + + DOM.setElementProperty(container, "className", className); + DOM.setInnerHTML(container, text); + if (align != ALIGN_LEFT) { + switch (align) { + case ALIGN_CENTER: + DOM.setStyleAttribute(container, "textAlign", "center"); + break; + case ALIGN_RIGHT: + default: + DOM.setStyleAttribute(container, "textAlign", "right"); + break; + } + } + DOM.appendChild(td, container); + DOM.appendChild(getElement(), td); + } + + public void addCell(Widget w, char align, String style) { + final Element td = DOM.createTD(); + final Element container = DOM.createDiv(); + String className = CLASSNAME + "-cell-content"; + if (style != null && !style.equals("")) { + className += " " + CLASSNAME + "-cell-content-" + style; + } + DOM.setElementProperty(container, "className", className); + // TODO most components work with this, but not all (e.g. + // Select) + // Old comment: make widget cells respect align. + // text-align:center for IE, margin: auto for others + if (align != ALIGN_LEFT) { + switch (align) { + case ALIGN_CENTER: + DOM.setStyleAttribute(container, "textAlign", "center"); + break; + case ALIGN_RIGHT: + default: + DOM.setStyleAttribute(container, "textAlign", "right"); + break; + } + } + DOM.appendChild(td, container); + DOM.appendChild(getElement(), td); + DOM.appendChild(container, w.getElement()); + adopt(w); + childWidgets.add(w); + } + + public Iterator iterator() { + return childWidgets.iterator(); + } + + public boolean remove(Widget w) { + // TODO Auto-generated method stub + return false; + } + + /* + * 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))) { + if (selectMode > Table.SELECT_MODE_NONE) { + toggleSelection(); + client.updateVariable(paintableId, "selected", + selectedRowKeys.toArray(), immediate); + } + } + break; + + default: + break; + } + super.onBrowserEvent(event); + } + + public void showContextMenu(Event event) { + ApplicationConnection.getConsole().log("Context menu"); + if (enabled && actionKeys != null) { + int left = DOM.eventGetClientX(event); + int top = DOM.eventGetClientY(event); + top += Window.getScrollTop(); + left += Window.getScrollLeft(); + client.getContextMenu().showAt(this, left, top); + } + } + + public boolean isSelected() { + return selected; + } + + private void toggleSelection() { + selected = !selected; + if (selected) { + if (selectMode == Table.SELECT_MODE_SINGLE) { + deselectAll(); + } + selectedRowKeys.add(String.valueOf(rowKey)); + addStyleName("i-selected"); + } else { + selectedRowKeys.remove(String.valueOf(rowKey)); + removeStyleName("i-selected"); + } + } + + /* + * (non-Javadoc) + * + * @see com.itmill.toolkit.terminal.gwt.client.ui.IActionOwner#getActions() + */ + 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); + a.setCaption(getActionCaption(actionKey)); + a.setIconUrl(getActionIcon(actionKey)); + actions[i] = a; + } + return actions; + } + + public ApplicationConnection getClient() { + return client; + } + + public String getPaintableId() { + return paintableId; + } + } + } + + public void deselectAll() { + final Object[] keys = selectedRowKeys.toArray(); + for (int i = 0; i < keys.length; i++) { + final IScrollTableRow row = getRenderedRowByKey((String) keys[i]); + if (row != null && row.isSelected()) { + row.toggleSelection(); + } + } + // still ensure all selects are removed from (not necessary rendered) + selectedRowKeys.clear(); + + } + + public void add(Widget w) { + throw new UnsupportedOperationException( + "ITable can contain only rows created by itself."); + } + + public void clear() { + panel.clear(); + } + + public Iterator iterator() { + return panel.iterator(); + } + + public boolean remove(Widget w) { + return panel.remove(w); + } + + public void mySetHeight(String height) { + // workaround very common 100% height problem - extract borders + if (height.equals("100%")) { + final int borders = getBorderSpace(); + final Element parentElem = DOM.getParent(getElement()); + + // put table away from flow for a moment + DOM.setStyleAttribute(getElement(), "position", "absolute"); + // get containers natural space for table + int availPixels = DOM.getElementPropertyInt(parentElem, + "offsetHeight"); + if (Util.isIE()) { + if (availPixels == 0) { + // In complex layouts IE sometimes rather randomly returns 0 + // although container really has height. Use old value if + // one exits. + if (oldAvailPixels > 0) { + availPixels = oldAvailPixels; + } + } else { + oldAvailPixels = availPixels; + } + } + // put table back to flow + DOM.setStyleAttribute(getElement(), "position", "static"); + // set 100% height with borders + int pixelSize = (availPixels - borders); + if (pixelSize < 0) { + pixelSize = 0; + } + super.setHeight(pixelSize + "px"); + } else { + // normally height don't include borders + super.setHeight(height); + } + } + + private int getBorderSpace() { + final Element el = getElement(); + return DOM.getElementPropertyInt(el, "offsetHeight") + - DOM.getElementPropertyInt(el, "clientHeight"); + } + + public void setWidth(String width) { + // NOP size handled internally + } + + public void setHeight(String height) { + // NOP size handled internally + } + + /* + * Overridden due Table might not survive of visibility change (scroll pos + * lost). Example ITabPanel just set contained components invisible and back + * when changing tabs. + */ + public void setVisible(boolean visible) { + if (isVisible() != visible) { + super.setVisible(visible); + if (initializedAndAttached) { + if (visible) { + DeferredCommand.addCommand(new Command() { + public void execute() { + bodyContainer.setScrollPosition(firstRowInViewPort + * tBody.getRowHeight()); + } + }); + } + } + } + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISlider.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISlider.java new file mode 100644 index 0000000000..4cf56de855 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISlider.java @@ -0,0 +1,415 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ +// +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.DeferredCommand; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +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.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +public class ISlider extends Widget implements Paintable, Field, + ContainerResizedListener { + + public static final String CLASSNAME = "i-slider"; + + /** + * Minimum size (width or height, depending on orientation) of the slider + * base. + */ + private static final int MIN_SIZE = 50; + + ApplicationConnection client; + + String id; + + private boolean immediate; + private boolean disabled; + private boolean readonly; + private boolean scrollbarStyle; + + private int handleSize; + private double min; + private double max; + private int resolution; + private Double value; + private boolean vertical; + private int size = -1; + private boolean arrows; + + /* DOM element for slider's base */ + private final Element base; + + /* 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; + + public ISlider() { + super(); + + setElement(DOM.createDiv()); + base = DOM.createDiv(); + handle = DOM.createDiv(); + smaller = DOM.createDiv(); + bigger = DOM.createDiv(); + + setStyleName(CLASSNAME); + DOM.setElementProperty(base, "className", CLASSNAME + "-base"); + DOM.setElementProperty(handle, "className", CLASSNAME + "-handle"); + DOM.setElementProperty(smaller, "className", CLASSNAME + "-smaller"); + DOM.setElementProperty(bigger, "className", CLASSNAME + "-bigger"); + + DOM.appendChild(getElement(), bigger); + DOM.appendChild(getElement(), smaller); + DOM.appendChild(getElement(), base); + DOM.appendChild(base, handle); + + // Hide initially + DOM.setStyleAttribute(smaller, "display", "none"); + DOM.setStyleAttribute(bigger, "display", "none"); + DOM.setStyleAttribute(handle, "visibility", "hidden"); + + DOM.sinkEvents(getElement(), Event.MOUSEEVENTS | Event.ONMOUSEWHEEL); + DOM.sinkEvents(base, Event.ONCLICK); + DOM.sinkEvents(handle, Event.MOUSEEVENTS); + DOM.sinkEvents(smaller, Event.ONMOUSEDOWN | Event.ONMOUSEUP + | Event.ONMOUSEOUT); + DOM.sinkEvents(bigger, Event.ONMOUSEDOWN | Event.ONMOUSEUP + | Event.ONMOUSEOUT); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + this.client = client; + id = uidl.getId(); + + // Ensure correct implementation + if (client.updateComponent(this, uidl, true)) { + return; + } + + immediate = uidl.getBooleanAttribute("immediate"); + disabled = uidl.getBooleanAttribute("disabled"); + readonly = uidl.getBooleanAttribute("readonly"); + + vertical = uidl.hasAttribute("vertical"); + arrows = uidl.hasAttribute("arrows"); + + String style = ""; + if (uidl.hasAttribute("style")) { + style = uidl.getStringAttribute("style"); + } + + scrollbarStyle = style.indexOf("scrollbar") > -1; + + if (arrows) { + DOM.setStyleAttribute(smaller, "display", "block"); + DOM.setStyleAttribute(bigger, "display", "block"); + } + + if (vertical) { + addStyleName(CLASSNAME + "-vertical"); + } else { + removeStyleName(CLASSNAME + "-vertical"); + } + + min = uidl.getDoubleAttribute("min"); + max = uidl.getDoubleAttribute("max"); + resolution = uidl.getIntAttribute("resolution"); + value = new Double(uidl.getDoubleVariable("value")); + + handleSize = uidl.getIntAttribute("hsize"); + + buildBase(); + + if (!vertical) { + // Draw handle with a delay to allow base to gain maximum width + DeferredCommand.addCommand(new Command() { + public void execute() { + buildHandle(); + setValue(value, false, false); + } + }); + } else { + buildHandle(); + setValue(value, false, false); + } + } + + private void buildBase() { + final String styleAttribute = vertical ? "height" : "width"; + final String domProperty = vertical ? "offsetHeight" : "offsetWidth"; + + if (size == -1) { + final Element p = DOM.getParent(getElement()); + if (DOM.getElementPropertyInt(p, domProperty) > 50) { + if (vertical) { + setHeight(); + } else { + DOM.setStyleAttribute(base, styleAttribute, ""); + } + } else { + // Set minimum size and adjust after all components have + // (supposedly) been drawn completely. + DOM.setStyleAttribute(base, styleAttribute, MIN_SIZE + "px"); + DeferredCommand.addCommand(new Command() { + public void execute() { + final Element p = DOM.getParent(getElement()); + if (DOM.getElementPropertyInt(p, domProperty) > (MIN_SIZE + 5)) { + if (vertical) { + setHeight(); + } else { + DOM.setStyleAttribute(base, styleAttribute, ""); + } + // Ensure correct position + setValue(value, false, false); + } + } + }); + } + } else { + DOM.setStyleAttribute(base, styleAttribute, size + "px"); + } + + // TODO attach listeners for focusing and arrow keys + } + + private void buildHandle() { + final String styleAttribute = vertical ? "height" : "width"; + final String handleAttribute = vertical ? "marginTop" : "marginLeft"; + final String domProperty = vertical ? "offsetHeight" : "offsetWidth"; + + DOM.setStyleAttribute(handle, handleAttribute, "0"); + + if (scrollbarStyle) { + // Only stretch the handle if scrollbar style is set. + int s = (int) (Double.parseDouble(DOM.getElementProperty(base, + domProperty)) / 100 * handleSize); + if (handleSize == -1) { + final int baseS = Integer.parseInt(DOM.getElementProperty(base, + domProperty)); + final double range = (max - min) * (resolution + 1) * 3; + s = (int) (baseS - range); + } + if (s < 3) { + s = 3; + } + DOM.setStyleAttribute(handle, styleAttribute, s + "px"); + } else { + DOM.setStyleAttribute(handle, styleAttribute, ""); + } + + // Restore visibility + DOM.setStyleAttribute(handle, "visibility", "visible"); + + } + + private void setValue(Double value, boolean animate, boolean updateToServer) { + if (value == null) { + return; + } + + if (value.doubleValue() < min) { + value = new Double(min); + } else if (value.doubleValue() > max) { + value = new Double(max); + } + + // Update handle position + final String styleAttribute = vertical ? "marginTop" : "marginLeft"; + final String domProperty = vertical ? "offsetHeight" : "offsetWidth"; + final int handleSize = Integer.parseInt(DOM.getElementProperty(handle, + domProperty)); + final int baseSize = Integer.parseInt(DOM.getElementProperty(base, + domProperty)); + 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 (vertical) { + // IE6 rounding behaves a little unstable, reduce one pixel so the + // containing element (base) won't expand without limits + p = range - p - (Util.isIE6() ? 1 : 0); + } + final double pos = p; + + final int current = DOM.getIntStyleAttribute(handle, styleAttribute); + + DOM.setStyleAttribute(handle, styleAttribute, (Math.round(pos)) + "px"); + + // TODO give more detailed info when dragging and do roundup + DOM.setElementAttribute(handle, "title", "" + v); + + // Update value + this.value = new Double(v); + + if (updateToServer) { + client.updateVariable(id, "value", this.value.doubleValue(), + immediate); + } + } + + 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 || DOM.compare(targ, handle)) { + processHandleEvent(event); + } else if (DOM.compare(targ, smaller)) { + decreaseValue(event); + } else if (DOM.compare(targ, bigger)) { + increaseValue(event); + } else { + processBaseEvent(event); + } + } + + private void processMouseWheelEvent(Event event) { + final int dir = DOM.eventGetMouseWheelVelocityY(event); + if (dir < 0) { + increaseValue(event); + } else { + decreaseValue(event); + } + DOM.eventPreventDefault(event); + DOM.eventCancelBubble(event, true); + } + + private void processHandleEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONMOUSEDOWN: + if (!disabled && !readonly) { + dragging = true; + DOM.setCapture(getElement()); + DOM.eventPreventDefault(event); // prevent selecting text + DOM.eventCancelBubble(event, true); + } + break; + case Event.ONMOUSEMOVE: + if (dragging) { + // DOM.setCapture(getElement()); + setValueByEvent(event, false, false); + } + break; + case Event.ONMOUSEUP: + dragging = false; + DOM.releaseCapture(getElement()); + setValueByEvent(event, true, true); + break; + default: + break; + } + } + + private void processBaseEvent(Event event) { + if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) { + if (!disabled && !readonly && !dragging) { + setValueByEvent(event, true, true); + DOM.eventCancelBubble(event, true); + } + } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN && dragging) { + dragging = false; + DOM.releaseCapture(getElement()); + setValueByEvent(event, true, true); + } + } + + private void decreaseValue(Event event) { + setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)), + false, true); + } + + private void increaseValue(Event event) { + setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)), + false, true); + } + + private void setValueByEvent(Event event, boolean animate, boolean roundup) { + double v = min; // Fallback to min + + final int coord = vertical ? DOM.eventGetClientY(event) : DOM + .eventGetClientX(event); + final String domProperty = vertical ? "offsetHeight" : "offsetWidth"; + + final double handleSize = Integer.parseInt(DOM.getElementProperty( + handle, domProperty)); + final double baseSize = Integer.parseInt(DOM.getElementProperty(base, + domProperty)); + final double baseOffset = vertical ? DOM.getAbsoluteTop(base) + - handleSize / 2 : DOM.getAbsoluteLeft(base) + handleSize / 2; + + if (vertical) { + v = ((baseSize - (coord - baseOffset)) / (baseSize - handleSize)) + * (max - min) + min; + } else { + v = ((coord - baseOffset) / (baseSize - handleSize)) * (max - min) + + min; + } + + if (v < min) { + v = min; + } else if (v > max) { + v = max; + } + + setValue(new Double(v), animate, roundup); + } + + public void iLayout() { + if (vertical) { + setHeight(); + } + // Update handle position + setValue(value, false, false); + } + + private void setHeight() { + if (size == -1) { + // Calculate decoration size + DOM.setStyleAttribute(base, "height", "0"); + DOM.setStyleAttribute(base, "overflow", "hidden"); + int h = DOM.getElementPropertyInt(getElement(), "offsetHeight"); + if (h < MIN_SIZE) { + h = MIN_SIZE; + } + DOM.setStyleAttribute(base, "height", h + "px"); + } else { + DOM.setStyleAttribute(base, "height", size + "px"); + } + DOM.setStyleAttribute(base, "overflow", ""); + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISplitPanel.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISplitPanel.java new file mode 100644 index 0000000000..efdc13b0dd --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISplitPanel.java @@ -0,0 +1,410 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.DeferredCommand; +import com.google.gwt.user.client.Element; +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.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +public class ISplitPanel extends ComplexPanel implements Paintable, + ContainerResizedListener { + public static final String CLASSNAME = "i-splitpanel"; + + public static final int ORIENTATION_HORIZONTAL = 0; + + public static final int ORIENTATION_VERTICAL = 1; + + private static final int MIN_SIZE = 30; + + private int orientation = ORIENTATION_HORIZONTAL; + + private Widget firstChild; + + private Widget secondChild; + + private final Element wrapper = DOM.createDiv(); + + private final Element firstContainer = DOM.createDiv(); + + private final Element secondContainer = DOM.createDiv(); + + private final Element splitter = DOM.createDiv(); + + private boolean resizing; + + private int origX; + + private int origY; + + private int origMouseX; + + private int origMouseY; + + private boolean locked; + + private String splitterStyleName; + + public ISplitPanel() { + this(ORIENTATION_HORIZONTAL); + } + + public ISplitPanel(int orientation) { + setElement(DOM.createDiv()); + switch (orientation) { + case ORIENTATION_HORIZONTAL: + setStyleName(CLASSNAME + "-horizontal"); + break; + case ORIENTATION_VERTICAL: + default: + setStyleName(CLASSNAME + "-vertical"); + break; + } + // size below will be overridden in update from uidl, initial size + // needed to keep IE alive + setWidth(MIN_SIZE + "px"); + setHeight(MIN_SIZE + "px"); + constructDom(); + setOrientation(orientation); + DOM.sinkEvents(splitter, (Event.MOUSEEVENTS)); + DOM.sinkEvents(getElement(), (Event.MOUSEEVENTS)); + } + + protected void constructDom() { + DOM.appendChild(splitter, DOM.createDiv()); // for styling + DOM.appendChild(getElement(), wrapper); + DOM.setStyleAttribute(wrapper, "position", "relative"); + DOM.setStyleAttribute(wrapper, "width", "100%"); + DOM.setStyleAttribute(wrapper, "height", "100%"); + + DOM.appendChild(wrapper, splitter); + DOM.appendChild(wrapper, secondContainer); + DOM.appendChild(wrapper, firstContainer); + + DOM.setStyleAttribute(splitter, "position", "absolute"); + DOM.setStyleAttribute(secondContainer, "position", "absolute"); + + DOM.setStyleAttribute(firstContainer, "overflow", "auto"); + DOM.setStyleAttribute(secondContainer, "overflow", "auto"); + if (Util.isIE7()) { + /* + * Part I of IE7 weirdness hack, will be set to auto in layout phase + * + * With IE7 one will sometimes get scrollbars with overflow auto + * even though there is nothing to scroll (content fits into area). + * + */ + DOM.setStyleAttribute(firstContainer, "overflow", "hidden"); + DOM.setStyleAttribute(secondContainer, "overflow", "hidden"); + } + } + + private void setOrientation(int orientation) { + this.orientation = orientation; + if (orientation == ORIENTATION_HORIZONTAL) { + DOM.setStyleAttribute(splitter, "height", "100%"); + DOM.setStyleAttribute(firstContainer, "height", "100%"); + DOM.setStyleAttribute(secondContainer, "height", "100%"); + } else { + DOM.setStyleAttribute(splitter, "width", "100%"); + DOM.setStyleAttribute(firstContainer, "width", "100%"); + DOM.setStyleAttribute(secondContainer, "width", "100%"); + } + + splitterStyleName = CLASSNAME + + (orientation == ORIENTATION_HORIZONTAL ? "-hsplitter" + : "-vsplitter"); + DOM.setElementProperty(splitter, "className", splitterStyleName); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, true)) { + return; + } + + setSplitPosition(uidl.getStringAttribute("position")); + + locked = uidl.hasAttribute("locked"); + if (locked) { + DOM.setElementProperty(splitter, "className", splitterStyleName + + "-locked"); + } else { + DOM.setElementProperty(splitter, "className", splitterStyleName); + } + + final Paintable newFirstChild = client.getPaintable(uidl + .getChildUIDL(0)); + final Paintable newSecondChild = client.getPaintable(uidl + .getChildUIDL(1)); + if (firstChild != newFirstChild) { + if (firstChild != null) { + client.unregisterPaintable((Paintable) firstChild); + } + setFirstWidget((Widget) newFirstChild); + } + if (secondChild != newSecondChild) { + if (secondChild != null) { + client.unregisterPaintable((Paintable) secondChild); + } + setSecondWidget((Widget) newSecondChild); + } + newFirstChild.updateFromUIDL(uidl.getChildUIDL(0), client); + newSecondChild.updateFromUIDL(uidl.getChildUIDL(1), client); + + if (Util.isIE7()) { + // Part III of IE7 hack + DeferredCommand.addCommand(new Command() { + public void execute() { + iLayout(); + } + }); + } + } + + private void setSplitPosition(String pos) { + if (orientation == ORIENTATION_HORIZONTAL) { + DOM.setStyleAttribute(splitter, "left", pos); + } else { + DOM.setStyleAttribute(splitter, "top", pos); + } + iLayout(); + } + + /* + * Calculates absolutely positioned container places/sizes (non-Javadoc) + * + * @see com.itmill.toolkit.terminal.gwt.client.NeedsLayout#layout() + */ + public void iLayout() { + if (!isAttached()) { + return; + } + int wholeSize; + int pixelPosition; + + DOM.setStyleAttribute(firstContainer, "overflow", "hidden"); + DOM.setStyleAttribute(secondContainer, "overflow", "hidden"); + + switch (orientation) { + case ORIENTATION_HORIZONTAL: + wholeSize = DOM.getElementPropertyInt(wrapper, "clientWidth"); + pixelPosition = DOM.getElementPropertyInt(splitter, "offsetLeft"); + + // reposition splitter in case it is out of box + if (pixelPosition > 0 + && pixelPosition + getSplitterSize() > wholeSize) { + pixelPosition = wholeSize - getSplitterSize(); + if (pixelPosition < 0) { + pixelPosition = 0; + } + setSplitPosition(pixelPosition + "px"); + return; + } + + DOM + .setStyleAttribute(firstContainer, "width", pixelPosition + + "px"); + int secondContainerWidth = (wholeSize - pixelPosition - getSplitterSize()); + if (secondContainerWidth < 0) { + secondContainerWidth = 0; + } + DOM.setStyleAttribute(secondContainer, "width", + secondContainerWidth + "px"); + DOM.setStyleAttribute(secondContainer, "left", + (pixelPosition + getSplitterSize()) + "px"); + + break; + case ORIENTATION_VERTICAL: + wholeSize = DOM.getElementPropertyInt(wrapper, "clientHeight"); + pixelPosition = DOM.getElementPropertyInt(splitter, "offsetTop"); + + // reposition splitter in case it is out of box + if (pixelPosition > 0 + && pixelPosition + getSplitterSize() > wholeSize) { + pixelPosition = wholeSize - getSplitterSize(); + if (pixelPosition < 0) { + pixelPosition = 0; + } + setSplitPosition(pixelPosition + "px"); + return; + } + + DOM.setStyleAttribute(firstContainer, "height", pixelPosition + + "px"); + int secondContainerHeight = (wholeSize - pixelPosition - getSplitterSize()); + if (secondContainerHeight < 0) { + secondContainerHeight = 0; + } + DOM.setStyleAttribute(secondContainer, "height", + secondContainerHeight + "px"); + DOM.setStyleAttribute(secondContainer, "top", + (pixelPosition + getSplitterSize()) + "px"); + break; + } + + if (Util.isIE7()) { + // Part I of IE7 weirdness hack, will be set to auto in layout phase + Util.runDescendentsLayout(this); + DeferredCommand.addCommand(new Command() { + public void execute() { + DOM.setStyleAttribute(firstContainer, "overflow", "auto"); + DOM.setStyleAttribute(secondContainer, "overflow", "auto"); + } + }); + } else { + Util.runDescendentsLayout(this); + DOM.setStyleAttribute(firstContainer, "overflow", "auto"); + DOM.setStyleAttribute(secondContainer, "overflow", "auto"); + } + + } + + private void setFirstWidget(Widget w) { + if (firstChild != null) { + firstChild.removeFromParent(); + } + super.add(w, firstContainer); + firstChild = w; + } + + private void setSecondWidget(Widget w) { + if (secondChild != null) { + secondChild.removeFromParent(); + } + super.add(w, secondContainer); + secondChild = w; + } + + public void setHeight(String height) { + super.setHeight(height); + // give sane height + getOffsetHeight(); // shake IE + if (getOffsetHeight() < MIN_SIZE) { + super.setHeight(MIN_SIZE + "px"); + } + } + + public void setWidth(String width) { + super.setWidth(width); + // give sane width + getOffsetWidth(); // shake IE + if (getOffsetWidth() < MIN_SIZE) { + super.setWidth(MIN_SIZE + "px"); + } + } + + public void onBrowserEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONMOUSEMOVE: + if (resizing) { + onMouseMove(event); + } + break; + case Event.ONMOUSEDOWN: + onMouseDown(event); + break; + case Event.ONMOUSEUP: + if (resizing) { + onMouseUp(event); + } + break; + case Event.ONCLICK: + resizing = false; + break; + } + } + + public void onMouseDown(Event event) { + if (locked) { + return; + } + final Element trg = DOM.eventGetTarget(event); + if (DOM.compare(trg, splitter) + || DOM.compare(trg, DOM.getChild(splitter, 0))) { + resizing = true; + DOM.setCapture(getElement()); + origX = DOM.getElementPropertyInt(splitter, "offsetLeft"); + origY = DOM.getElementPropertyInt(splitter, "offsetTop"); + origMouseX = DOM.eventGetClientX(event); + origMouseY = DOM.eventGetClientY(event); + DOM.eventCancelBubble(event, true); + DOM.eventPreventDefault(event); + } + } + + public void onMouseMove(Event event) { + switch (orientation) { + case ORIENTATION_HORIZONTAL: + final int x = DOM.eventGetClientX(event); + onHorizontalMouseMove(x); + break; + case ORIENTATION_VERTICAL: + default: + final int y = DOM.eventGetClientY(event); + onVerticalMouseMove(y); + break; + } + iLayout(); + } + + private void onHorizontalMouseMove(int x) { + int newX = origX + x - origMouseX; + if (newX < 0) { + newX = 0; + } + if (newX + getSplitterSize() > getOffsetWidth()) { + newX = getOffsetWidth() - getSplitterSize(); + } + DOM.setStyleAttribute(splitter, "left", newX + "px"); + } + + private void onVerticalMouseMove(int y) { + int newY = origY + y - origMouseY; + if (newY < 0) { + newY = 0; + } + + if (newY + getSplitterSize() > getOffsetHeight()) { + newY = getOffsetHeight() - getSplitterSize(); + } + DOM.setStyleAttribute(splitter, "top", newY + "px"); + } + + public void onMouseUp(Event event) { + DOM.releaseCapture(getElement()); + resizing = false; + onMouseMove(event); + } + + private static int splitterSize = -1; + + private int getSplitterSize() { + if (splitterSize < 0) { + if (isAttached()) { + switch (orientation) { + case ORIENTATION_HORIZONTAL: + splitterSize = DOM.getElementPropertyInt(splitter, + "offsetWidth"); + break; + + default: + splitterSize = DOM.getElementPropertyInt(splitter, + "offsetHeight"); + break; + } + } + } + return splitterSize; + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISplitPanelHorizontal.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISplitPanelHorizontal.java new file mode 100644 index 0000000000..cfc686b7c2 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISplitPanelHorizontal.java @@ -0,0 +1,12 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +public class ISplitPanelHorizontal extends ISplitPanel { + + public ISplitPanelHorizontal() { + super(ISplitPanel.ORIENTATION_HORIZONTAL); + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISplitPanelVertical.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISplitPanelVertical.java new file mode 100644 index 0000000000..d6e72385e3 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ISplitPanelVertical.java @@ -0,0 +1,12 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +public class ISplitPanelVertical extends ISplitPanel { + + public ISplitPanelVertical() { + super(ISplitPanel.ORIENTATION_VERTICAL); + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITablePaging.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITablePaging.java new file mode 100644 index 0000000000..e1dbf37e43 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITablePaging.java @@ -0,0 +1,438 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Set; +import java.util.Vector; + +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.Button; +import com.google.gwt.user.client.ui.ClickListener; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +/** + * TODO make this work (just an early prototype). We may want to have paging + * style table which will be much lighter than IScrollTable is. + */ +public class ITablePaging extends Composite implements Table, Paintable, + ClickListener { + + private final Grid tBody = new Grid(); + private final Button nextPage = new Button(">"); + private final Button prevPage = new Button("<"); + private final Button firstPage = new Button("<<"); + private final Button lastPage = new Button(">>"); + + private int pageLength = 15; + + private boolean rowHeaders = false; + + private ApplicationConnection client; + private String id; + + private boolean immediate = false; + + private int selectMode = Table.SELECT_MODE_NONE; + + private final Vector selectedRowKeys = new Vector(); + + private int totalRows; + + private final HashMap visibleColumns = new HashMap(); + + private int rows; + + private int firstRow; + private boolean sortAscending = true; + private final HorizontalPanel pager; + + public HashMap rowKeysToTableRows = new HashMap(); + + public ITablePaging() { + + tBody.setStyleName("itable-tbody"); + + final VerticalPanel panel = new VerticalPanel(); + + pager = new HorizontalPanel(); + pager.add(firstPage); + firstPage.addClickListener(this); + pager.add(prevPage); + prevPage.addClickListener(this); + pager.add(nextPage); + nextPage.addClickListener(this); + pager.add(lastPage); + lastPage.addClickListener(this); + + panel.add(pager); + panel.add(tBody); + + initWidget(panel); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, true)) { + return; + } + + this.client = client; + id = uidl.getStringAttribute("id"); + immediate = uidl.getBooleanAttribute("immediate"); + totalRows = uidl.getIntAttribute("totalrows"); + pageLength = uidl.getIntAttribute("pagelength"); + firstRow = uidl.getIntAttribute("firstrow"); + rows = uidl.getIntAttribute("rows"); + + if (uidl.hasAttribute("selectmode")) { + if (uidl.getStringAttribute("selectmode").equals("multi")) { + selectMode = Table.SELECT_MODE_MULTI; + } else { + selectMode = Table.SELECT_MODE_SINGLE; + } + + if (uidl.hasAttribute("selected")) { + final Set selectedKeys = uidl + .getStringArrayVariableAsSet("selected"); + selectedRowKeys.clear(); + for (final Iterator it = selectedKeys.iterator(); it.hasNext();) { + selectedRowKeys.add(it.next()); + } + } + } + + if (uidl.hasVariable("sortascending")) { + sortAscending = uidl.getBooleanVariable("sortascending"); + } + + if (uidl.hasAttribute("rowheaders")) { + rowHeaders = true; + } + + UIDL rowData = null; + UIDL visibleColumns = null; + for (final Iterator it = uidl.getChildIterator(); it.hasNext();) { + final UIDL c = (UIDL) it.next(); + if (c.getTag().equals("rows")) { + rowData = c; + } else if (c.getTag().equals("actions")) { + updateActionMap(c); + } else if (c.getTag().equals("visiblecolumns")) { + visibleColumns = c; + } + } + tBody.resize(rows + 1, uidl.getIntAttribute("cols") + + (rowHeaders ? 1 : 0)); + updateHeader(visibleColumns); + updateBody(rowData); + + updatePager(); + } + + private void updateHeader(UIDL c) { + final Iterator it = c.getChildIterator(); + visibleColumns.clear(); + int colIndex = (rowHeaders ? 1 : 0); + while (it.hasNext()) { + final UIDL col = (UIDL) it.next(); + final String cid = col.getStringAttribute("cid"); + if (!col.hasAttribute("collapsed")) { + tBody.setWidget(0, colIndex, new HeaderCell(cid, col + .getStringAttribute("caption"))); + + } + colIndex++; + } + } + + private void updateActionMap(UIDL c) { + // TODO Auto-generated method stub + + } + + /** + * Updates row data from uidl. UpdateFromUIDL delegates updating tBody to + * this method. + * + * Updates may be to different part of tBody, depending on update type. It + * can be initial row data, scroll up, scroll down... + * + * @param uidl + * which contains row data + */ + private void updateBody(UIDL uidl) { + final Iterator it = uidl.getChildIterator(); + + int curRowIndex = 1; + while (it.hasNext()) { + final UIDL rowUidl = (UIDL) it.next(); + final TableRow row = new TableRow(curRowIndex, String + .valueOf(rowUidl.getIntAttribute("key")), rowUidl + .hasAttribute("selected")); + int colIndex = 0; + if (rowHeaders) { + tBody.setWidget(curRowIndex, colIndex, new BodyCell(row, + rowUidl.getStringAttribute("caption"))); + colIndex++; + } + final Iterator cells = rowUidl.getChildIterator(); + while (cells.hasNext()) { + final Object cell = cells.next(); + if (cell instanceof String) { + tBody.setWidget(curRowIndex, colIndex, new BodyCell(row, + (String) cell)); + } else { + final Paintable cellContent = client + .getPaintable((UIDL) cell); + final BodyCell bodyCell = new BodyCell(row); + bodyCell.setWidget((Widget) cellContent); + tBody.setWidget(curRowIndex, colIndex, bodyCell); + } + colIndex++; + } + curRowIndex++; + } + } + + private void updatePager() { + if (pageLength == 0) { + pager.setVisible(false); + return; + } + if (isFirstPage()) { + firstPage.setEnabled(false); + prevPage.setEnabled(false); + } else { + firstPage.setEnabled(true); + prevPage.setEnabled(true); + } + if (hasNextPage()) { + nextPage.setEnabled(true); + lastPage.setEnabled(true); + } else { + nextPage.setEnabled(false); + lastPage.setEnabled(false); + + } + } + + private boolean hasNextPage() { + if (firstRow + rows + 1 > totalRows) { + return false; + } + return true; + } + + private boolean isFirstPage() { + if (firstRow == 0) { + return true; + } + return false; + } + + public void onClick(Widget sender) { + if (sender instanceof Button) { + if (sender == firstPage) { + client.updateVariable(id, "firstvisible", 0, true); + } else if (sender == nextPage) { + client.updateVariable(id, "firstvisible", + firstRow + pageLength, true); + } else if (sender == prevPage) { + int newFirst = firstRow - pageLength; + if (newFirst < 0) { + newFirst = 0; + } + client.updateVariable(id, "firstvisible", newFirst, true); + } else if (sender == lastPage) { + client.updateVariable(id, "firstvisible", totalRows + - pageLength, true); + } + } + if (sender instanceof HeaderCell) { + final HeaderCell hCell = (HeaderCell) sender; + client.updateVariable(id, "sortcolumn", hCell.getCid(), false); + client.updateVariable(id, "sortascending", (sortAscending ? false + : true), true); + } + } + + private class HeaderCell extends HTML { + + private String cid; + + public String getCid() { + return cid; + } + + public void setCid(String pid) { + cid = pid; + } + + HeaderCell(String pid, String caption) { + super(); + cid = pid; + addClickListener(ITablePaging.this); + setText(caption); + // TODO remove debug color + DOM.setStyleAttribute(getElement(), "color", "brown"); + DOM.setStyleAttribute(getElement(), "font-weight", "bold"); + } + } + + /** + * Abstraction of table cell content. In needs to know on which row it is in + * case of context click. + * + * @author mattitahvonen + */ + public class BodyCell extends SimplePanel { + private final TableRow row; + + public BodyCell(TableRow row) { + super(); + sinkEvents(Event.BUTTON_LEFT | Event.BUTTON_RIGHT); + this.row = row; + } + + public BodyCell(TableRow row2, String textContent) { + super(); + sinkEvents(Event.BUTTON_LEFT | Event.BUTTON_RIGHT); + row = row2; + setWidget(new Label(textContent)); + } + + public void onBrowserEvent(Event event) { + System.out.println("CEll event: " + event.toString()); + switch (DOM.eventGetType(event)) { + case Event.BUTTON_RIGHT: + row.showContextMenu(event); + Window.alert("context menu un-implemented"); + DOM.eventCancelBubble(event, true); + break; + case Event.BUTTON_LEFT: + if (selectMode > Table.SELECT_MODE_NONE) { + row.toggleSelected(); + } + break; + default: + break; + } + super.onBrowserEvent(event); + } + } + + private class TableRow { + + private final String key; + private final int rowIndex; + private boolean selected = false; + + public TableRow(int rowIndex, String rowKey, boolean selected) { + rowKeysToTableRows.put(rowKey, this); + this.rowIndex = rowIndex; + key = rowKey; + setSelected(selected); + } + + /** + * This method is used to set row status. Does not change value on + * server. + * + * @param selected + */ + public void setSelected(boolean sel) { + selected = sel; + if (selected) { + selectedRowKeys.add(key); + DOM.setStyleAttribute(tBody.getRowFormatter().getElement( + rowIndex), "background", "yellow"); + + } else { + selectedRowKeys.remove(key); + DOM.setStyleAttribute(tBody.getRowFormatter().getElement( + rowIndex), "background", "transparent"); + } + } + + public void setContextMenuOptions(HashMap options) { + + } + + /** + * Toggles rows select state. Also updates state to server according to + * tables immediate flag. + * + */ + public void toggleSelected() { + if (selected) { + setSelected(false); + } else { + if (selectMode == Table.SELECT_MODE_SINGLE) { + deselectAll(); + } + setSelected(true); + } + client.updateVariable(id, "selected", selectedRowKeys.toArray(), + immediate); + } + + /** + * Shows context menu for this row. + * + * @param event + * Event which triggered context menu. Correct place for + * context menu can be determined with it. + */ + public void showContextMenu(Event event) { + System.out.println("TODO: Show context menu"); + } + } + + public void deselectAll() { + final Object[] keys = selectedRowKeys.toArray(); + for (int i = 0; i < keys.length; i++) { + final TableRow tableRow = (TableRow) rowKeysToTableRows + .get(keys[i]); + if (tableRow != null) { + tableRow.setSelected(false); + } + } + // still ensure all selects are removed from + selectedRowKeys.clear(); + } + + public void add(Widget w) { + // TODO Auto-generated method stub + + } + + public void clear() { + // TODO Auto-generated method stub + + } + + public Iterator iterator() { + // TODO Auto-generated method stub + return null; + } + + public boolean remove(Widget w) { + // TODO Auto-generated method stub + return false; + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITabsheet.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITabsheet.java new file mode 100644 index 0000000000..3c76cbd47b --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITabsheet.java @@ -0,0 +1,383 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.Iterator; + +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.DeferredCommand; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.SourcesTabEvents; +import com.google.gwt.user.client.ui.TabBar; +import com.google.gwt.user.client.ui.TabListener; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Caption; +import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +public class ITabsheet extends ITabsheetBase implements + ContainerResizedListener { + + public static final String CLASSNAME = "i-tabsheet"; + + public static final String TABS_CLASSNAME = "i-tabsheet-tabcontainer"; + public static final String SCROLLER_CLASSNAME = "i-tabsheet-scroller"; + private final Element tabs; // tabbar and 'scroller' container + private final Element scroller; // tab-scroller element + private final Element scrollerNext; // tab-scroller next button element + private final Element scrollerPrev; // tab-scroller prev button element + private int scrollerIndex = 0; + + private final TabBar tb; + private final ITabsheetPanel tp; + private final Element contentNode, deco; + + private String height; + private String width; + + private boolean waitingForResponse; + + /** + * Previous visible widget is set invisible with CSS (not display: none, but + * visibility: hidden), to avoid flickering during render process. Normal + * visibility must be returned later when new widget is rendered. + */ + private Widget previousVisibleWidget; + + private final TabListener tl = new TabListener() { + + public void onTabSelected(SourcesTabEvents sender, final int tabIndex) { + if (client != null && activeTabIndex != tabIndex) { + addStyleDependentName("loading"); + // run updating variables in deferred command to bypass some + // FF + // optimization issues + DeferredCommand.addCommand(new Command() { + public void execute() { + previousVisibleWidget = tp.getWidget(tp + .getVisibleWidget()); + DOM.setStyleAttribute(previousVisibleWidget + .getElement(), "visibility", "hidden"); + client.updateVariable(id, "selected", tabKeys.get( + tabIndex).toString(), true); + } + }); + waitingForResponse = true; + } + } + + public boolean onBeforeTabSelected(SourcesTabEvents sender, int tabIndex) { + if (disabled || waitingForResponse) { + return false; + } + final Object tabKey = tabKeys.get(tabIndex); + if (disabledTabKeys.contains(tabKey)) { + return false; + } + + return true; + } + + }; + + public ITabsheet() { + super(CLASSNAME); + + // Tab scrolling + DOM.setStyleAttribute(getElement(), "overflow", "hidden"); + tabs = DOM.createDiv(); + DOM.setElementProperty(tabs, "className", TABS_CLASSNAME); + scroller = DOM.createDiv(); + + DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME); + scrollerPrev = DOM.createButton(); + DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME + + "Prev"); + DOM.sinkEvents(scrollerPrev, Event.ONCLICK); + scrollerNext = DOM.createButton(); + DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME + + "Next"); + DOM.sinkEvents(scrollerNext, Event.ONCLICK); + DOM.appendChild(getElement(), tabs); + + // Tabs + tb = new TabBar(); + tp = new ITabsheetPanel(); + tp.setStyleName(CLASSNAME + "-tabsheetpanel"); + contentNode = DOM.createDiv(); + + deco = DOM.createDiv(); + + addStyleDependentName("loading"); // Indicate initial progress + 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(tp, contentNode); + DOM.appendChild(getElement(), deco); + + DOM.appendChild(tabs, scroller); + + tb.addTabListener(tl); + + // TODO Use for Safari only. Fix annoying 1px first cell in TabBar. + DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM + .getFirstChild(tb.getElement()))), "display", "none"); + + } + + public void onBrowserEvent(Event event) { + + // Tab scrolling + if (isScrolledTabs() + && DOM.compare(DOM.eventGetTarget(event), scrollerPrev)) { + if (scrollerIndex > 0) { + DOM.setStyleAttribute(DOM.getChild(DOM.getFirstChild(DOM + .getFirstChild(tb.getElement())), scrollerIndex), + "display", ""); + scrollerIndex--; + updateTabScroller(); + } + } else if (isClippedTabs() + && DOM.compare(DOM.eventGetTarget(event), scrollerNext)) { + int tabs = tb.getTabCount(); + if (scrollerIndex + 1 <= tabs) { + scrollerIndex++; + DOM.setStyleAttribute(DOM.getChild(DOM.getFirstChild(DOM + .getFirstChild(tb.getElement())), scrollerIndex), + "display", "none"); + updateTabScroller(); + } + } else { + super.onBrowserEvent(event); + } + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + super.updateFromUIDL(uidl, client); + + // Add proper stylenames for all elements (easier to prevent unwanted + // style inheritance) + if (uidl.hasAttribute("style")) { + final String[] styles = uidl.getStringAttribute("style").split(" "); + final String contentBaseClass = CLASSNAME + "-content"; + String contentClass = contentBaseClass; + final String decoBaseClass = CLASSNAME + "-deco"; + String decoClass = decoBaseClass; + for (int i = 0; i < styles.length; i++) { + tb.addStyleDependentName(styles[i]); + contentClass += " " + contentBaseClass + "-" + styles[i]; + decoClass += " " + decoBaseClass + "-" + styles[i]; + } + DOM.setElementProperty(contentNode, "className", contentClass); + DOM.setElementProperty(deco, "className", decoClass); + } else { + tb.setStyleName(CLASSNAME + "-tabs"); + DOM.setElementProperty(contentNode, "className", CLASSNAME + + "-content"); + DOM.setElementProperty(deco, "className", CLASSNAME + "-deco"); + } + + if (uidl.hasAttribute("hidetabs")) { + tb.setVisible(false); + addStyleName(CLASSNAME + "-hidetabs"); + } else { + tb.setVisible(true); + removeStyleName(CLASSNAME + "-hidetabs"); + } + + // tabs; push or not + if (uidl.hasAttribute("width")) { + // update width later, in updateTabScroller(); + DOM.setStyleAttribute(tabs, "width", "1px"); + DOM.setStyleAttribute(tabs, "overflow", "hidden"); + } else { + showAllTabs(); + DOM.setStyleAttribute(tabs, "width", ""); + DOM.setStyleAttribute(tabs, "overflow", "visible"); + } + + updateTabScroller(); + waitingForResponse = false; + } + + protected void renderTab(final UIDL tabUidl, int index, boolean selected) { + // TODO check indexes, now new tabs get placed last (changing tab order + // is not supported from server-side) + Caption c = new Caption(null, client); + c.updateCaption(tabUidl); + tb.addTab(c); + if (selected) { + renderContent(tabUidl.getChildUIDL(0)); + tb.selectTab(index); + } else if (tabUidl.getChildCount() > 0) { + // updating a drawn child on hidden tab + Paintable paintable = client.getPaintable(tabUidl.getChildUIDL(0)); + paintable.updateFromUIDL(tabUidl.getChildUIDL(0), client); + } + // Add place-holder content + tp.add(new Label("")); + } + + protected void selectTab(int index, final UIDL contentUidl) { + if (index != activeTabIndex) { + activeTabIndex = index; + tb.selectTab(activeTabIndex); + } + renderContent(contentUidl); + } + + private void renderContent(final UIDL contentUIDL) { + final Paintable content = client.getPaintable(contentUIDL); + if (tp.getWidgetCount() > activeTabIndex) { + Widget old = tp.getWidget(activeTabIndex); + if (old != content) { + tp.remove(activeTabIndex); + if (old instanceof Paintable) { + client.unregisterPaintable((Paintable) old); + } + tp.insert((Widget) content, activeTabIndex); + } + } else { + tp.add((Widget) content); + } + + tp.showWidget(activeTabIndex); + + ITabsheet.this.iLayout(); + (content).updateFromUIDL(contentUIDL, client); + ITabsheet.this.removeStyleDependentName("loading"); + if (previousVisibleWidget != null) { + DOM.setStyleAttribute(previousVisibleWidget.getElement(), + "visibility", ""); + previousVisibleWidget = null; + } + } + + public void setHeight(String height) { + if (this.height == null && height == null) { + return; + } + String oldHeight = this.height; + this.height = height; + if ((this.height != null && height == null) + || (this.height == null && height != null) + || !height.equals(oldHeight)) { + iLayout(); + } + } + + public void setWidth(String width) { + String oldWidth = this.width; + this.width = width; + if ("100%".equals(width)) { + // Allow browser to calculate width + super.setWidth(""); + } else { + super.setWidth(width); + } + if ((this.width != null && width == null) + || (this.width == null && width != null) + || !width.equals(oldWidth)) { + // Run descendant layout functions + Util.runDescendentsLayout(this); + } + } + + public void iLayout() { + if (height != null && height != "") { + super.setHeight(height); + + final int contentHeight = getOffsetHeight() + - DOM.getElementPropertyInt(deco, "offsetHeight") + - tb.getOffsetHeight(); + + // Set proper values for content element + DOM.setStyleAttribute(contentNode, "height", contentHeight + "px"); + DOM.setStyleAttribute(contentNode, "overflow", "auto"); + tp.setHeight("100%"); + + } else { + DOM.setStyleAttribute(contentNode, "height", ""); + DOM.setStyleAttribute(contentNode, "overflow", ""); + } + Util.runDescendentsLayout(this); + + updateTabScroller(); + } + + /** + * Layouts the tab-scroller elements, and applies styles. + */ + private void updateTabScroller() { + if (width != null) { + DOM.setStyleAttribute(tabs, "width", width); + } + if (scrollerIndex > tb.getTabCount()) { + scrollerIndex = 0; + } + boolean scrolled = isScrolledTabs(); + boolean clipped = isClippedTabs(); + if (tb.isVisible() && (scrolled || clipped)) { + DOM.setStyleAttribute(scroller, "display", ""); + DOM.setElementProperty(scrollerPrev, "className", + SCROLLER_CLASSNAME + (scrolled ? "Prev" : "Prev-disabled")); + DOM.setElementProperty(scrollerNext, "className", + SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled")); + } else { + DOM.setStyleAttribute(scroller, "display", "none"); + } + + } + + private void showAllTabs() { + scrollerIndex = 0; + for (int i = 0; i < tb.getTabCount(); i++) { + DOM.setStyleAttribute(DOM.getChild(DOM.getFirstChild(DOM + .getFirstChild(tb.getElement())), i + 1), "display", ""); + } + } + + private boolean isScrolledTabs() { + return scrollerIndex > 0; + } + + private boolean isClippedTabs() { + return tb.getOffsetWidth() > getOffsetWidth(); + } + + protected void clearPaintables() { + + int i = tb.getTabCount(); + while (i > 0) { + tb.removeTab(--i); + } + tp.clear(); + + // Get rid of unnecessary 100% cell heights in TabBar (really ugly hack) + final Element tr = DOM.getChild(DOM.getChild(tb.getElement(), 0), 0); + final Element rest = DOM.getChild(DOM.getChild(tr, DOM + .getChildCount(tr) - 1), 0); + DOM.removeElementAttribute(rest, "style"); + + } + + protected Iterator getPaintableIterator() { + return tp.iterator(); + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITabsheetBase.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITabsheetBase.java new file mode 100644 index 0000000000..05a77cfaf2 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITabsheetBase.java @@ -0,0 +1,157 @@ +package com.itmill.toolkit.terminal.gwt.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.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +abstract class ITabsheetBase extends ComplexPanel implements Paintable { + + String id; + ApplicationConnection client; + + protected final ArrayList tabKeys = new ArrayList(); + protected final ArrayList captions = new ArrayList(); + protected int activeTabIndex = 0; + protected boolean disabled; + protected boolean readonly; + protected Set disabledTabKeys = new HashSet(); + + public ITabsheetBase(String classname) { + setElement(DOM.createDiv()); + setStylePrimaryName(classname); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + // Ensure correct implementation + if (client.updateComponent(this, uidl, true)) { + return; + } + + // Update member references + this.client = client; + id = uidl.getId(); + disabled = uidl.hasAttribute("disabled"); + + // Render content + final UIDL tabs = uidl.getChildUIDL(0); + if (keepCurrentTabs(uidl)) { + int index = 0; + for (final Iterator it = tabs.getChildIterator(); it.hasNext();) { + final UIDL tab = (UIDL) it.next(); + final boolean selected = tab.getBooleanAttribute("selected"); + if (selected) { + selectTab(index, tab.getChildUIDL(0)); + } else if (tab.getChildCount() > 0) { + // updating a drawn child on hidden tab + Paintable paintable = client.getPaintable(tab + .getChildUIDL(0)); + // TODO widget may flash on screen + paintable.updateFromUIDL(tab.getChildUIDL(0), client); + // Hack #1 in ITabsheetBase: due ITabsheets content has no + // wrappers for each tab, we need to hide the actual widgets + ((Widget) paintable).setVisible(false); + } + index++; + } + } else { + + ArrayList oldPaintables = new ArrayList(); + for (Iterator iterator = getPaintableIterator(); iterator.hasNext();) { + oldPaintables.add(iterator.next()); + } + + // Clear previous values + tabKeys.clear(); + captions.clear(); + disabledTabKeys.clear(); + + clearPaintables(); + + int index = 0; + for (final Iterator it = tabs.getChildIterator(); it.hasNext();) { + final UIDL tab = (UIDL) it.next(); + final String key = tab.getStringAttribute("key"); + final boolean selected = tab.getBooleanAttribute("selected"); + String caption = tab.getStringAttribute("caption"); + if (caption == null) { + caption = " "; + } + + if (tab.getBooleanAttribute("disabled")) { + disabledTabKeys.add(key); + } + + captions.add(caption); + tabKeys.add(key); + + if (selected) { + activeTabIndex = index; + } + if (tab.getChildCount() > 0) { + Paintable p = client.getPaintable(tab.getChildUIDL(0)); + oldPaintables.remove(p); + } + renderTab(tab, index, selected); + index++; + } + + for (Iterator iterator = oldPaintables.iterator(); iterator + .hasNext();) { + Object oldPaintable = iterator.next(); + if (oldPaintable instanceof Paintable) { + client.unregisterPaintable((Paintable) oldPaintable); + } + } + } + + } + + /** + * @return a list of currently shown Paintables + */ + abstract protected Iterator getPaintableIterator(); + + /** + * Clears current tabs and contents + */ + abstract protected void clearPaintables(); + + protected boolean keepCurrentTabs(UIDL uidl) { + final UIDL tabs = uidl.getChildUIDL(0); + boolean retval = tabKeys.size() == tabs.getNumberOfChildren(); + for (int i = 0; retval && i < tabKeys.size(); i++) { + String key = (String) tabKeys.get(i); + UIDL tabUIDL = tabs.getChildUIDL(i); + retval = key.equals(tabUIDL.getStringAttribute("key")) + && captions.get(i).equals( + tabUIDL.getStringAttribute("caption")) + && (tabUIDL.hasAttribute("disabled") == disabledTabKeys + .contains(key)); + } + return retval; + } + + /** + * Implement in extending classes. This method should render needed elements + * and set the visibility of the tab according to the 'selected' parameter. + */ + protected abstract void renderTab(final UIDL tabUidl, int index, + boolean selected); + + /** + * Implement in extending classes. This method should render any previously + * non-cached content and set the activeTabIndex property to the specified + * index. + */ + protected abstract void selectTab(int index, final UIDL contentUidl); + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITabsheetPanel.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITabsheetPanel.java new file mode 100644 index 0000000000..01ac63645e --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITabsheetPanel.java @@ -0,0 +1,112 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; + +/** + * 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.itmill.toolkit.terminal.gwt.client.ui.ITabsheetPanel}. + * + * 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 ITabsheetPanel extends ComplexPanel { + + private Widget visibleWidget; + + /** + * Creates an empty tabsheet panel. + */ + public ITabsheetPanel() { + setElement(DOM.createDiv()); + } + + /** + * Adds the specified widget to the deck. + * + * @param w + * the widget to be added + */ + public void add(Widget w) { + super.add(w, getElement()); + initChildWidget(w); + } + + /** + * 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) { + super.insert(w, getElement(), beforeIndex, true); + initChildWidget(w); + } + + public boolean remove(Widget w) { + final boolean removed = super.remove(w); + if (removed) { + resetChildWidget(w); + + if (visibleWidget == w) { + visibleWidget = null; + } + } + 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) { + visibleWidget.setVisible(false); + } + visibleWidget = newVisible; + visibleWidget.setVisible(true); + } + } + + /** + * Make the widget invisible, and set its width and height to full. + */ + private void initChildWidget(Widget w) { + w.setVisible(false); + } + + /** + * Make the widget visible, and clear the widget's width and height + * attributes. This is done so that any changes to the visibility, height, + * or width of the widget that were done by the panel are undone. + */ + private void resetChildWidget(Widget w) { + w.setVisible(true); + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITextArea.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITextArea.java new file mode 100644 index 0000000000..94bccd7bfd --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITextArea.java @@ -0,0 +1,50 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +/** + * 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 IT Mill Ltd. + * + */ +public class ITextArea extends ITextField { + public static final String CLASSNAME = "i-textarea"; + + public ITextArea() { + super(DOM.createTextArea()); + setStyleName(CLASSNAME); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Call parent renderer explicitly + super.updateFromUIDL(uidl, client); + + if (uidl.hasAttribute("rows")) { + setRows(new Integer(uidl.getStringAttribute("rows")).intValue()); + } + } + + public void setRows(int rows) { + setRows(getElement(), rows); + } + + private native void setRows(Element e, int r) + /*-{ + try { + if(e.tagName.toLowerCase() == "textarea") + e.rows = r; + } catch (e) {} + }-*/; + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITextField.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITextField.java new file mode 100644 index 0000000000..5438cc9f28 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITextField.java @@ -0,0 +1,204 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.ChangeListener; +import com.google.gwt.user.client.ui.FocusListener; +import com.google.gwt.user.client.ui.TextBoxBase; +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.Paintable; +import com.itmill.toolkit.terminal.gwt.client.Tooltip; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +/** + * This class represents a basic text input field with one row. + * + * @author IT Mill Ltd. + * + */ +public class ITextField extends TextBoxBase implements Paintable, Field, + ChangeListener, FocusListener, ContainerResizedListener { + + /** + * The input node CSS classname. + */ + public static final String CLASSNAME = "i-textfield"; + /** + * This CSS classname is added to the input node on hover. + */ + public static final String CLASSNAME_FOCUS = "focus"; + + protected String id; + + protected ApplicationConnection client; + + private boolean immediate = false; + private float proportionalHeight = -1; + private float proportionalWidth = -1; + private int extraHorizontalPixels = -1; + private int extraVerticalPixels = -1; + + public ITextField() { + this(DOM.createInputText()); + } + + protected ITextField(Element node) { + super(node); + setStyleName(CLASSNAME); + addChangeListener(this); + addFocusListener(this); + sinkEvents(Tooltip.TOOLTIP_EVENTS); + } + + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (client != null) { + client.handleTooltipEvent(event, this); + } + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + this.client = client; + id = uidl.getId(); + + if (client.updateComponent(this, uidl, true)) { + return; + } + + if (uidl.getBooleanAttribute("readonly")) { + setReadOnly(true); + } else { + setReadOnly(false); + } + + immediate = uidl.getBooleanAttribute("immediate"); + + if (uidl.hasAttribute("cols")) { + setColumns(new Integer(uidl.getStringAttribute("cols")).intValue()); + } + + setText(uidl.getStringVariable("text")); + + } + + public void onChange(Widget sender) { + if (client != null && id != null) { + client.updateVariable(id, "text", getText(), immediate); + } + } + + public void onFocus(Widget sender) { + addStyleDependentName(CLASSNAME_FOCUS); + } + + public void onLostFocus(Widget sender) { + removeStyleDependentName(CLASSNAME_FOCUS); + } + + public void setColumns(int columns) { + setColumns(getElement(), columns); + } + + private native void setColumns(Element e, int c) + /*-{ + try { + switch(e.tagName.toLowerCase()) { + case "input": + //e.size = c; + e.style.width = c+"em"; + break; + case "textarea": + //e.cols = c; + e.style.width = c+"em"; + break; + default:; + } + } catch (e) {} + }-*/; + + public void setHeight(String height) { + if (height != null && height.indexOf("%") > 0) { + // special handling for proportional height + proportionalHeight = Float.parseFloat(height.substring(0, height + .indexOf("%"))) / 100; + iLayout(); + } else { + super.setHeight(height); + proportionalHeight = -1; + } + } + + public void setWidth(String width) { + if (width != null && width.indexOf("%") > 0) { + // special handling for proportional w + proportionalWidth = Float.parseFloat(width.substring(0, width + .indexOf("%"))) / 100; + iLayout(); + } else { + super.setHeight(width); + proportionalWidth = -1; + } + } + + public void iLayout() { + if (proportionalWidth >= 0) { + int availPixels = (int) (DOM.getElementPropertyInt(DOM + .getParent(getElement()), "clientWidth") * proportionalWidth); + availPixels -= getExtraHorizontalPixels(); + super.setWidth(availPixels + "px"); + } + if (proportionalHeight >= 0) { + int availPixels = (int) (DOM.getElementPropertyInt(DOM + .getParent(getElement()), "clientHeight") * proportionalHeight); + availPixels -= getExtraVerticalPixels(); + super.setHeight(availPixels + "px"); + } + } + + /** + * @return space used by components paddings and borders + */ + private int getExtraHorizontalPixels() { + if (extraHorizontalPixels < 0) { + detectExtraSizes(); + } + return extraHorizontalPixels; + } + + /** + * @return space used by components paddings and borders + */ + private int getExtraVerticalPixels() { + if (extraVerticalPixels < 0) { + detectExtraSizes(); + } + return extraVerticalPixels; + } + + /** + * Detects space used by components paddings and borders. Used when + * relational size are used. + */ + private void detectExtraSizes() { + Element clone = Util.cloneNode(getElement(), false); + DOM.setElementAttribute(clone, "id", ""); + DOM.setStyleAttribute(clone, "visibility", "hidden"); + DOM.setStyleAttribute(clone, "position", "absolute"); + // due FF3 bug set size to 10px and later subtract it from extra pixels + DOM.setStyleAttribute(clone, "width", "10px"); + DOM.setStyleAttribute(clone, "height", "10px"); + DOM.appendChild(DOM.getParent(getElement()), clone); + extraHorizontalPixels = DOM.getElementPropertyInt(clone, "offsetWidth") - 10; + extraVerticalPixels = DOM.getElementPropertyInt(clone, "offsetHeight") - 10; + DOM.removeChild(DOM.getParent(getElement()), clone); + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITextualDate.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITextualDate.java new file mode 100644 index 0000000000..a08dcba1be --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITextualDate.java @@ -0,0 +1,279 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.Date; + +import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.ChangeListener; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.BrowserInfo; +import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener; +import com.itmill.toolkit.terminal.gwt.client.Focusable; +import com.itmill.toolkit.terminal.gwt.client.LocaleNotLoadedException; +import com.itmill.toolkit.terminal.gwt.client.LocaleService; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class ITextualDate extends IDateField implements Paintable, Field, + ChangeListener, ContainerResizedListener, Focusable { + + private static final String PARSE_ERROR_CLASSNAME = CLASSNAME + + "-parseerror"; + + private final TextBox text; + + private String formatStr; + + private String width; + + private boolean needLayout; + + protected int fieldExtraWidth = -1; + + public ITextualDate() { + super(); + text = new TextBox(); + // use normal textfield styles as a basis + text.setStyleName(ITextField.CLASSNAME); + // add datefield spesific style name also + text.addStyleName(CLASSNAME + "-textfield"); + text.addChangeListener(this); + add(text); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + int origRes = currentResolution; + super.updateFromUIDL(uidl, client); + if (origRes != currentResolution) { + // force recreating format string + formatStr = null; + } + buildDate(); + } + + 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 >= RESOLUTION_HOUR) { + if (dts.isTwelveHourClock()) { + frmString += " hh"; + } else { + frmString += " HH"; + } + if (currentResolution >= RESOLUTION_MIN) { + frmString += ":mm"; + if (currentResolution >= RESOLUTION_SEC) { + frmString += ":ss"; + if (currentResolution >= RESOLUTION_MSEC) { + frmString += ".SSS"; + } + } + } + if (dts.isTwelveHourClock()) { + frmString += " aaa"; + } + + } + + formatStr = frmString; + } catch (LocaleNotLoadedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + return formatStr; + } + + /** + * + */ + protected void buildDate() { + removeStyleName(PARSE_ERROR_CLASSNAME); + // Create the initial text for the textfield + String dateText; + if (date != null) { + dateText = DateTimeFormat.getFormat(getFormatString()).format(date); + } else { + dateText = ""; + } + + text.setText(dateText); + text.setEnabled(enabled && !readonly); + + if (readonly) { + text.addStyleName("i-readonly"); + } else { + text.removeStyleName("i-readonly"); + } + + } + + public void onChange(Widget sender) { + if (sender == text) { + if (!text.getText().equals("")) { + try { + date = DateTimeFormat.getFormat(getFormatString()).parse( + text.getText()); + // remove possibly added invalid value indication + removeStyleName(PARSE_ERROR_CLASSNAME); + } catch (final Exception e) { + ApplicationConnection.getConsole().log(e.getMessage()); + addStyleName(PARSE_ERROR_CLASSNAME); + client.updateVariable(id, "lastInvalidDateString", text + .getText(), false); + date = null; + } + } else { + date = null; + // remove possibly added invalid value indication + removeStyleName(PARSE_ERROR_CLASSNAME); + } + + if (date != null) { + showingDate = new Date(date.getTime()); + } + + // Update variables + // (only the smallest defining resolution needs to be + // immediate) + client.updateVariable(id, "year", + date != null ? date.getYear() + 1900 : -1, + currentResolution == IDateField.RESOLUTION_YEAR + && immediate); + if (currentResolution >= IDateField.RESOLUTION_MONTH) { + client.updateVariable(id, "month", date != null ? date + .getMonth() + 1 : -1, + currentResolution == IDateField.RESOLUTION_MONTH + && immediate); + } + if (currentResolution >= IDateField.RESOLUTION_DAY) { + client.updateVariable(id, "day", date != null ? date.getDate() + : -1, currentResolution == IDateField.RESOLUTION_DAY + && immediate); + } + if (currentResolution >= IDateField.RESOLUTION_HOUR) { + client.updateVariable(id, "hour", date != null ? date + .getHours() : -1, + currentResolution == IDateField.RESOLUTION_HOUR + && immediate); + } + if (currentResolution >= IDateField.RESOLUTION_MIN) { + client.updateVariable(id, "min", date != null ? date + .getMinutes() : -1, + currentResolution == IDateField.RESOLUTION_MIN + && immediate); + } + if (currentResolution >= IDateField.RESOLUTION_SEC) { + client.updateVariable(id, "sec", date != null ? date + .getSeconds() : -1, + currentResolution == IDateField.RESOLUTION_SEC + && immediate); + } + if (currentResolution == IDateField.RESOLUTION_MSEC) { + client.updateVariable(id, "msec", + date != null ? getMilliseconds() : -1, immediate); + } + + } + } + + private String cleanFormat(String format) { + // Remove unnecessary d & M if resolution is too low + if (currentResolution < IDateField.RESOLUTION_DAY) { + format = format.replaceAll("d", ""); + } + if (currentResolution < IDateField.RESOLUTION_MONTH) { + 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(); + } + + public void setWidth(String newWidth) { + if (!"".equals(newWidth) && (width == null || !newWidth.equals(width))) { + if (BrowserInfo.get().isIE6()) { + // in IE6 cols ~ min-width + DOM.setElementProperty(text.getElement(), "size", "1"); + } + needLayout = true; + width = newWidth; + super.setWidth(width); + iLayout(); + if (newWidth.indexOf("%") < 0) { + needLayout = false; + } + } else { + if ("".equals(newWidth) && width != null && !"".equals(width)) { + if (BrowserInfo.get().isIE6()) { + // revert IE6 hack + DOM.setElementProperty(text.getElement(), "size", ""); + } + super.setWidth(""); + needLayout = true; + iLayout(); + needLayout = false; + width = null; + } + } + } + + /** + * Returns pixels in x-axis reserved for other than textfield content. + * + * @return extra width in pixels + */ + protected int getFieldExtraWidth() { + if (fieldExtraWidth < 0) { + text.setWidth("0px"); + fieldExtraWidth = text.getOffsetWidth(); + } + return fieldExtraWidth; + } + + public void iLayout() { + if (needLayout) { + text.setWidth((getOffsetWidth() - getFieldExtraWidth()) + "px"); + } + } + + public void focus() { + text.setFocus(true); + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITree.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITree.java new file mode 100644 index 0000000000..2169a41ab8 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITree.java @@ -0,0 +1,409 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.HashMap; +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.Element; +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.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +/** + * + */ +public class ITree extends FlowPanel implements Paintable { + + public static final String CLASSNAME = "i-tree"; + + private Set selectedIds = new HashSet(); + private ApplicationConnection client; + private String paintableId; + private boolean selectable; + private boolean isMultiselect; + + 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(); + + private boolean immediate; + + private boolean isNullSelectionAllowed = true; + + private boolean disabled = false; + + private boolean readonly; + + public ITree() { + super(); + setStyleName(CLASSNAME); + } + + private void updateActionMap(UIDL c) { + final Iterator it = c.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", client.translateToolkitUri(action + .getStringAttribute("icon"))); + } + } + + } + + public String getActionCaption(String actionKey) { + return (String) actionMap.get(actionKey + "_c"); + } + + public String getActionIcon(String actionKey) { + return (String) actionMap.get(actionKey + "_i"); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Ensure correct implementation and let container manage caption + if (client.updateComponent(this, uidl, true)) { + return; + } + + this.client = client; + + if (uidl.hasAttribute("partialUpdate")) { + handleUpdate(uidl); + return; + } + + paintableId = uidl.getId(); + + immediate = uidl.hasAttribute("immediate"); + + disabled = uidl.getBooleanAttribute("disabled"); + readonly = uidl.getBooleanAttribute("readonly"); + + isNullSelectionAllowed = uidl.getBooleanAttribute("nullselect"); + + clear(); + for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { + final UIDL childUidl = (UIDL) i.next(); + if ("actions".equals(childUidl.getTag())) { + updateActionMap(childUidl); + continue; + } + final TreeNode childTree = new TreeNode(); + this.add(childTree); + childTree.updateFromUIDL(childUidl, client); + } + final String selectMode = uidl.getStringAttribute("selectmode"); + selectable = !"none".equals(selectMode); + isMultiselect = "multi".equals(selectMode); + + selectedIds = uidl.getStringArrayVariableAsSet("selected"); + + } + + private void handleUpdate(UIDL uidl) { + final TreeNode rootNode = (TreeNode) keyToNode.get(uidl + .getStringAttribute("rootKey")); + if (rootNode != null) { + if (!rootNode.getState()) { + // expanding node happened server side + rootNode.setState(true, false); + } + rootNode.renderChildNodes(uidl.getChildIterator()); + } + + } + + public void setSelected(TreeNode treeNode, boolean selected) { + if (selected) { + if (!isMultiselect) { + while (selectedIds.size() > 0) { + final String id = (String) selectedIds.iterator().next(); + final TreeNode oldSelection = (TreeNode) keyToNode.get(id); + if (oldSelection != null) { + // can be null if the node is not visible (parent + // expanded) + 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); + } + client.updateVariable(paintableId, "selected", selectedIds.toArray(), + immediate); + } + + public boolean isSelected(TreeNode treeNode) { + return selectedIds.contains(treeNode.key); + } + + protected class TreeNode extends SimplePanel implements ActionOwner { + + public static final String CLASSNAME = "i-tree-node"; + + String key; + + private String[] actionKeys = null; + + private boolean childrenLoaded; + + private Element nodeCaptionDiv; + + protected Element nodeCaptionSpan; + + private FlowPanel childNodeContainer; + + private boolean open; + + private Icon icon; + + public TreeNode() { + constructDom(); + sinkEvents(Event.ONCLICK); + } + + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (disabled) { + return; + } + final Element target = DOM.eventGetTarget(event); + if (DOM.compare(getElement(), target)) { + // state change + toggleState(); + } else if (!readonly && DOM.compare(target, nodeCaptionSpan)) { + // caption click = selection change + toggleSelection(); + } + + } + + private void toggleSelection() { + if (selectable) { + ITree.this.setSelected(this, !isSelected()); + } + } + + private void toggleState() { + setState(!getState(), true); + } + + protected void constructDom() { + nodeCaptionDiv = DOM.createDiv(); + DOM.setElementProperty(nodeCaptionDiv, "className", CLASSNAME + + "-caption"); + nodeCaptionSpan = DOM.createSpan(); + DOM.appendChild(getElement(), nodeCaptionDiv); + DOM.appendChild(nodeCaptionDiv, nodeCaptionSpan); + + childNodeContainer = new FlowPanel(); + childNodeContainer.setStylePrimaryName(CLASSNAME + "-children"); + setWidget(childNodeContainer); + } + + public void onDetach() { + Util.removeContextMenuEvent(nodeCaptionSpan); + super.onDetach(); + } + + public void onAttach() { + attachContextMenuEvent(nodeCaptionSpan); + super.onAttach(); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + setText(uidl.getStringAttribute("caption")); + key = uidl.getStringAttribute("key"); + + keyToNode.put(key, this); + + if (uidl.hasAttribute("al")) { + actionKeys = uidl.getStringArrayAttribute("al"); + } + + if (uidl.getTag().equals("node")) { + if (uidl.getChildCount() == 0) { + childNodeContainer.setVisible(false); + } else { + renderChildNodes(uidl.getChildIterator()); + childrenLoaded = true; + } + } else { + addStyleName(CLASSNAME + "-leaf"); + } + addStyleName(CLASSNAME); + + if (uidl.getBooleanAttribute("expanded") && !getState()) { + setState(true, false); + } + + if (uidl.getBooleanAttribute("selected")) { + setSelected(true); + } + + if (uidl.hasAttribute("icon")) { + if (icon == null) { + icon = new Icon(client); + DOM.insertBefore(nodeCaptionDiv, icon.getElement(), + nodeCaptionSpan); + } + icon.setUri(uidl.getStringAttribute("icon")); + } else { + if (icon != null) { + DOM.removeChild(nodeCaptionDiv, icon.getElement()); + icon = null; + } + } + } + + private 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"); + childNodeContainer.setVisible(true); + } else { + removeStyleName(CLASSNAME + "-expanded"); + childNodeContainer.setVisible(false); + if (notifyServer) { + client.updateVariable(paintableId, "collapse", + new String[] { key }, true); + } + } + + open = state; + } + + private boolean getState() { + return open; + } + + private void setText(String text) { + DOM.setInnerText(nodeCaptionSpan, text); + } + + private void renderChildNodes(Iterator i) { + childNodeContainer.clear(); + childNodeContainer.setVisible(true); + while (i.hasNext()) { + final UIDL childUidl = (UIDL) 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 = new TreeNode(); + childNodeContainer.add(childTree); + childTree.updateFromUIDL(childUidl, client); + } + childrenLoaded = true; + } + + public boolean isChildrenLoaded() { + return childrenLoaded; + } + + 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; + } + + public ApplicationConnection getClient() { + return client; + } + + public String getPaintableId() { + return paintableId; + } + + /** + * Adds/removes IT Mill Toolkit spesific style name. This method ought + * to be called only from Tree. + * + * @param selected + */ + public void setSelected(boolean selected) { + // add style name to caption dom structure only, not to subtree + setStyleName(nodeCaptionDiv, "i-tree-node-selected", selected); + } + + public boolean isSelected() { + return ITree.this.isSelected(this); + } + + public void showContextMenu(Event event) { + if (!readonly && !disabled) { + if (actionKeys != null) { + int left = DOM.eventGetClientX(event); + int top = DOM.eventGetClientY(event); + top += Window.getScrollTop(); + left += Window.getScrollLeft(); + client.getContextMenu().showAt(this, left, top); + } + DOM.eventCancelBubble(event, true); + } + + } + + private native void attachContextMenuEvent(Element el) + /*-{ + var node = this; + el.oncontextmenu = function(e) { + if(!e) + e = $wnd.event; + node.@com.itmill.toolkit.terminal.gwt.client.ui.ITree.TreeNode::showContextMenu(Lcom/google/gwt/user/client/Event;)(e); + return false; + }; + }-*/; + + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITwinColSelect.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITwinColSelect.java new file mode 100644 index 0000000000..40aae9d852 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ITwinColSelect.java @@ -0,0 +1,220 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.Iterator; +import java.util.Vector; + +import com.google.gwt.user.client.DOM; +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.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class ITwinColSelect extends IOptionGroupBase { + + private static final String CLASSNAME = "i-select-twincol"; + + private static final int VISIBLE_COUNT = 10; + + private static final int DEFAULT_COLUMN_COUNT = 10; + + private final ListBox options; + + private final ListBox selections; + + private final IButton add; + + private final IButton remove; + + private FlowPanel buttons; + + private Panel panel; + + private boolean widthSet = false; + + public ITwinColSelect() { + super(CLASSNAME); + options = new ListBox(); + options.addClickListener(this); + selections = new ListBox(); + selections.addClickListener(this); + options.setVisibleItemCount(VISIBLE_COUNT); + selections.setVisibleItemCount(VISIBLE_COUNT); + options.setStyleName(CLASSNAME + "-options"); + selections.setStyleName(CLASSNAME + "-selections"); + buttons = new FlowPanel(); + buttons.setStyleName(CLASSNAME + "-buttons"); + add = new IButton(); + add.setText(">>"); + add.addClickListener(this); + remove = new IButton(); + remove.setText("<<"); + remove.addClickListener(this); + panel = ((Panel) optionsContainer); + 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); + } + + protected void buildOptions(UIDL uidl) { + final boolean enabled = !isDisabled() && !isReadonly(); + options.setMultipleSelect(isMultiselect()); + selections.setMultipleSelect(isMultiselect()); + options.setEnabled(enabled); + selections.setEnabled(enabled); + add.setEnabled(enabled); + remove.setEnabled(enabled); + 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 (getColumns() > 0) { + options.setWidth(getColumns() + "em"); + selections.setWidth(getColumns() + "em"); + optionsContainer.setWidth((getColumns() * 2 + 3) + "em"); + } else if (!widthSet) { + options.setWidth(DEFAULT_COLUMN_COUNT + "em"); + selections.setWidth(DEFAULT_COLUMN_COUNT + "em"); + optionsContainer.setWidth((DEFAULT_COLUMN_COUNT * 2 + 2) + "em"); + } + if (getRows() > 0) { + options.setVisibleItemCount(getRows()); + selections.setVisibleItemCount(getRows()); + } + } + + protected Object[] getSelectedItems() { + final Vector selectedItemKeys = new Vector(); + for (int i = 0; i < selections.getItemCount(); i++) { + selectedItemKeys.add(selections.getValue(i)); + } + return selectedItemKeys.toArray(); + } + + private boolean[] getItemsToAdd() { + final boolean[] selectedIndexes = new boolean[options.getItemCount()]; + for (int i = 0; i < options.getItemCount(); i++) { + if (options.isItemSelected(i)) { + selectedIndexes[i] = true; + } else { + selectedIndexes[i] = false; + } + } + return selectedIndexes; + } + + private boolean[] getItemsToRemove() { + final boolean[] selectedIndexes = new boolean[selections.getItemCount()]; + for (int i = 0; i < selections.getItemCount(); i++) { + if (selections.isItemSelected(i)) { + selectedIndexes[i] = true; + } else { + selectedIndexes[i] = false; + } + } + return selectedIndexes; + } + + public void onClick(Widget sender) { + super.onClick(sender); + if (sender == add) { + final boolean[] sel = getItemsToAdd(); + for (int i = 0; i < sel.length; i++) { + if (sel[i]) { + final int optionIndex = i + - (sel.length - options.getItemCount()); + selectedKeys.add(options.getValue(optionIndex)); + + // Move selection to another column + final String text = options.getItemText(optionIndex); + final String value = options.getValue(optionIndex); + selections.addItem(text, value); + selections.setItemSelected(selections.getItemCount() - 1, + true); + options.removeItem(optionIndex); + } + } + client.updateVariable(id, "selected", selectedKeys.toArray(), + isImmediate()); + + } else if (sender == remove) { + final boolean[] sel = getItemsToRemove(); + for (int i = 0; i < sel.length; i++) { + if (sel[i]) { + final int selectionIndex = i + - (sel.length - selections.getItemCount()); + selectedKeys.remove(selections.getValue(selectionIndex)); + + // Move selection to another column + final String text = selections.getItemText(selectionIndex); + final String value = selections.getValue(selectionIndex); + options.addItem(text, value); + options.setItemSelected(options.getItemCount() - 1, true); + selections.removeItem(selectionIndex); + } + } + client.updateVariable(id, "selected", selectedKeys.toArray(), + isImmediate()); + } else if (sender == 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 (sender == 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); + } + } + } + + public void setHeight(String height) { + super.setHeight(height); + if ("".equals(height)) { + options.setHeight(""); + selections.setHeight(""); + } else { + setFullHeightInternals(); + } + } + + private void setFullHeightInternals() { + options.setHeight("100%"); + selections.setHeight("100%"); + } + + public void setWidth(String width) { + super.setWidth(width); + if (!"".equals(width) && width != null) { + setRelativeInternalWidths(); + } + } + + private void setRelativeInternalWidths() { + DOM.setStyleAttribute(getElement(), "position", "relative"); + buttons.setWidth("16%"); + options.setWidth("42%"); + selections.setWidth("42%"); + widthSet = true; + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IUnknownComponent.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IUnknownComponent.java new file mode 100644 index 0000000000..e90de5cb2d --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IUnknownComponent.java @@ -0,0 +1,40 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Tree; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class IUnknownComponent extends Composite implements Paintable { + + com.google.gwt.user.client.ui.Label caption = new com.google.gwt.user.client.ui.Label();; + Tree uidlTree = new Tree(); + + public IUnknownComponent() { + final VerticalPanel panel = new VerticalPanel(); + panel.add(caption); + panel.add(uidlTree); + initWidget(panel); + setStyleName("itmill-unknown"); + caption.setStyleName("itmill-unknown-caption"); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, false)) { + return; + } + setCaption("Client faced an unknown component type. Unrendered UIDL:"); + uidlTree.clear(); + uidlTree.addItem(uidl.dir()); + } + + public void setCaption(String c) { + caption.setText(c); + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IUpload.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IUpload.java new file mode 100644 index 0000000000..84707dec69 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IUpload.java @@ -0,0 +1,151 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.ClickListener; +import com.google.gwt.user.client.ui.FileUpload; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FormHandler; +import com.google.gwt.user.client.ui.FormPanel; +import com.google.gwt.user.client.ui.FormSubmitCompleteEvent; +import com.google.gwt.user.client.ui.FormSubmitEvent; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +public class IUpload extends FormPanel implements Paintable, ClickListener, + FormHandler { + + public static final String CLASSNAME = "i-upload"; + + /** + * FileUpload component that opens native OS dialog to select file. + */ + FileUpload fu = new FileUpload(); + + Panel panel = new FlowPanel(); + + ApplicationConnection client; + + private String paintableId; + + /** + * Button that initiates uploading + */ + private final Button 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. + */ + private 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; + + public IUpload() { + super(); + setEncoding(FormPanel.ENCODING_MULTIPART); + setMethod(FormPanel.METHOD_POST); + + setWidget(panel); + panel.add(fu); + submitButton = new Button(); + submitButton.addClickListener(this); + panel.add(submitButton); + + addFormHandler(this); + + setStyleName(CLASSNAME); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (client.updateComponent(this, uidl, true)) { + return; + } + this.client = client; + paintableId = uidl.getId(); + setAction(client.getAppUri()); + submitButton.setText(uidl.getStringAttribute("buttoncaption")); + fu.setName(paintableId + "_file"); + + if (uidl.hasAttribute("disabled") || uidl.hasAttribute("readonly")) { + disableUpload(); + } else if (uidl.getBooleanAttribute("state")) { + enableUploaod(); + } + + } + + public void onClick(Widget sender) { + submit(); + } + + public void onSubmit(FormSubmitEvent event) { + if (fu.getFilename().length() == 0 || submitted || !enabled) { + event.setCancelled(true); + ApplicationConnection + .getConsole() + .log( + "Submit cancelled (disabled, no file or already submitted)"); + return; + } + // flush possibly pending variable changes, so they will be handled + // before upload + client.sendPendingVariableChanges(); + + submitted = true; + ApplicationConnection.getConsole().log("Submitted form"); + + disableUpload(); + + /* + * Visit server a moment after upload has started to see possible + * changes from UploadStarted event. Will be cleared on complete. + */ + t = new Timer() { + public void run() { + client.sendPendingVariableChanges(); + } + }; + t.schedule(800); + } + + protected void disableUpload() { + submitButton.setEnabled(false); + fu.setVisible(false); + enabled = false; + } + + protected void enableUploaod() { + submitButton.setEnabled(true); + fu.setVisible(true); + enabled = true; + } + + public void onSubmitComplete(FormSubmitCompleteEvent event) { + if (client != null) { + if (t != null) { + t.cancel(); + } + ApplicationConnection.getConsole().log("Submit complete"); + client.sendPendingVariableChanges(); + } + submitted = false; + enableUploaod(); + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IView.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IView.java new file mode 100644 index 0000000000..13a41fc82e --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IView.java @@ -0,0 +1,295 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.HashSet; +import java.util.Iterator; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +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.WindowResizeListener; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.BrowserInfo; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +/** + * + */ +public class IView extends SimplePanel implements Paintable, + WindowResizeListener { + + private static final String CLASSNAME = "i-view"; + + private String theme; + + private Paintable layout; + + private final HashSet subWindows = new HashSet(); + + private String id; + + private ShortcutActionHandler actionHandler; + + /** stored width for IE resize optimization */ + private int width; + + /** stored height for IE resize optimization */ + private int height; + + /** + * We are postponing resize process with IE. IE bugs with scrollbars in some + * situations, that causes false onWindowResized calls. With Timer we will + * give IE some time to decide if it really wants to keep current size + * (scrollbars). + */ + private Timer resizeTimer; + + public IView(String elementId) { + super(); + setStyleName(CLASSNAME); + + DOM.sinkEvents(getElement(), Event.ONKEYDOWN); + + DOM.setElementProperty(getElement(), "tabIndex", "0"); + + RootPanel.get(elementId).add(this); + + Window.addWindowResizeListener(this); + + // set focus to iview element by default to listen possible keyboard + // shortcuts + if (BrowserInfo.get().isOpera() || BrowserInfo.get().isSafari() + && BrowserInfo.get().getWebkitVersion() < 526) { + // old webkits don't support focusing div elements + Element fElem = DOM.createInputCheck(); + DOM.setStyleAttribute(fElem, "margin", "0"); + DOM.setStyleAttribute(fElem, "padding", "0"); + DOM.setStyleAttribute(fElem, "border", "0"); + DOM.setStyleAttribute(fElem, "outline", "0"); + DOM.setStyleAttribute(fElem, "width", "1px"); + DOM.setStyleAttribute(fElem, "height", "1px"); + DOM.setStyleAttribute(fElem, "position", "absolute"); + DOM.setStyleAttribute(fElem, "opacity", "0.1"); + DOM.appendChild(getElement(), fElem); + focus(fElem); + } else { + focus(getElement()); + } + + } + + private static native void focus(Element el) + /*-{ + try { + el.focus(); + } catch (e) { + + } + }-*/; + + public String getTheme() { + return theme; + } + + /** + * Used to reload host page on theme changes. + */ + private static native void reloadHostPage() + /*-{ + $wnd.location.reload(); + }-*/; + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + id = uidl.getId(); + + String newTheme = uidl.getStringAttribute("theme"); + if (theme != null && !newTheme.equals(theme)) { + // Complete page refresh is needed due css can affect layout + // calculations etc + reloadHostPage(); + } else { + theme = newTheme; + } + if (uidl.hasAttribute("style")) { + addStyleName(uidl.getStringAttribute("style")); + } + + com.google.gwt.user.client.Window.setTitle(uidl + .getStringAttribute("caption")); + + // Process children + int childIndex = 0; + + // Open URL:s + while (childIndex < uidl.getChildCount() + && "open".equals(uidl.getChildUIDL(childIndex).getTag())) { + final UIDL open = uidl.getChildUIDL(childIndex); + final String url = open.getStringAttribute("src"); + final String target = open.getStringAttribute("name"); + if (target == null) { + goTo(url); + } else { + // TODO width & height + Window.open(url, target != null ? target : null, ""); + } + childIndex++; + } + + // Draw this application level window + UIDL childUidl = uidl.getChildUIDL(childIndex); + final Paintable lo = client.getPaintable(childUidl); + + if (layout != null) { + if (layout != lo) { + // remove old + client.unregisterPaintable(layout); + // add new + setWidget((Widget) lo); + layout = lo; + } + } else { + setWidget((Widget) lo); + layout = lo; + } + layout.updateFromUIDL(childUidl, client); + + // Update subwindows + final HashSet removedSubWindows = new HashSet(subWindows); + + // Open new windows + while ((childUidl = uidl.getChildUIDL(childIndex++)) != null) { + if ("window".equals(childUidl.getTag())) { + final Paintable w = client.getPaintable(childUidl); + if (subWindows.contains(w)) { + removedSubWindows.remove(w); + } else { + subWindows.add(w); + } + w.updateFromUIDL(childUidl, client); + } else if ("actions".equals(childUidl.getTag())) { + if (actionHandler == null) { + actionHandler = new ShortcutActionHandler(id, client); + } + actionHandler.updateActionMap(childUidl); + } else if (childUidl.getTag().equals("notifications")) { + for (final Iterator it = childUidl.getChildIterator(); it + .hasNext();) { + final UIDL notification = (UIDL) it.next(); + String html = ""; + if (notification.hasAttribute("icon")) { + final String parsedUri = client + .translateToolkitUri(notification + .getStringAttribute("icon")); + html += ""; + } + if (notification.hasAttribute("caption")) { + html += "

" + + notification.getStringAttribute("caption") + + "

"; + } + if (notification.hasAttribute("message")) { + html += "

" + + notification.getStringAttribute("message") + + "

"; + } + + final String style = notification.hasAttribute("style") ? notification + .getStringAttribute("style") + : null; + final int position = notification + .getIntAttribute("position"); + final int delay = notification.getIntAttribute("delay"); + new Notification(delay).show(html, position, style); + } + } + } + + // Close old windows + for (final Iterator rem = removedSubWindows.iterator(); rem.hasNext();) { + final IWindow w = (IWindow) rem.next(); + client.unregisterPaintable(w); + subWindows.remove(w); + w.hide(); + } + + onWindowResized(Window.getClientWidth(), Window.getClientHeight()); + // IE somehow fails some layout on first run, force layout + // functions + // Util.runDescendentsLayout(this); + + } + + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (DOM.eventGetType(event) == Event.ONKEYDOWN && actionHandler != null) { + actionHandler.handleKeyboardEvent(event); + return; + } + } + + public void onWindowResized(int width, int height) { + if (Util.isIE()) { + /* + * IE will give us some false resized events due bugs with + * scrollbars. Postponing layout phase to see if size was really + * changed. + */ + if (resizeTimer == null) { + resizeTimer = new Timer() { + public void run() { + boolean changed = false; + if (IView.this.width != getOffsetWidth()) { + IView.this.width = getOffsetWidth(); + changed = true; + ApplicationConnection.getConsole().log( + "window w" + IView.this.width); + } + if (IView.this.height != getOffsetHeight()) { + IView.this.height = getOffsetHeight(); + changed = true; + ApplicationConnection.getConsole().log( + "window h" + IView.this.height); + } + if (changed) { + ApplicationConnection + .getConsole() + .log( + "Running layout functions due window resize"); + Util.runDescendentsLayout(IView.this); + } + } + }; + } else { + resizeTimer.cancel(); + } + resizeTimer.schedule(200); + } else { + // temporary set overflow hidden, not to let scrollbars disturb + // layout functions + final String overflow = DOM.getStyleAttribute(getElement(), + "overflow"); + DOM.setStyleAttribute(getElement(), "overflow", "hidden"); + ApplicationConnection.getConsole().log( + "Running layout functions due window resize"); + Util.runDescendentsLayout(this); + DOM.setStyleAttribute(getElement(), "overflow", overflow); + } + } + + public native static void goTo(String url) + /*-{ + $wnd.location = url; + }-*/; + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IWindow.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IWindow.java new file mode 100644 index 0000000000..76c7a9ea81 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/IWindow.java @@ -0,0 +1,678 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.Iterator; +import java.util.Vector; + +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.DeferredCommand; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Frame; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.ScrollListener; +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.BrowserInfo; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.Util; + +/** + * "Sub window" component. + * + * TODO update position / scrollposition / size to client + * + * @author IT Mill Ltd + */ +public class IWindow extends PopupPanel implements Paintable, ScrollListener { + + private static final int MIN_HEIGHT = 60; + + private static final int MIN_WIDTH = 80; + + private static Vector windowOrder = new Vector(); + + public static final String CLASSNAME = "i-window"; + + /** pixels used by inner borders and paddings horizontally */ + protected static final int BORDER_WIDTH_HORIZONTAL = 41; + + /** pixels used by headers, footers, inner borders and paddings vertically */ + protected static final int BORDER_WIDTH_VERTICAL = 58; + + private static final int STACKING_OFFSET_PIXELS = 15; + + private static final int Z_INDEX_BASE = 10000; + + private Paintable layout; + + private Element contents; + + private Element header; + + private Element footer; + + private Element resizeBox; + + private final ScrollPanel contentPanel = new ScrollPanel(); + + private boolean dragging; + + private int startX; + + private int startY; + + private int origX; + + private int origY; + + private boolean resizing; + + private int origW; + + private int origH; + + private Element closeBox; + + protected ApplicationConnection client; + + private String id; + + ShortcutActionHandler shortcutHandler; + + /** Last known positionx read from UIDL or updated to application connection */ + private int uidlPositionX = -1; + + /** Last known positiony read from UIDL or updated to application connection */ + private int uidlPositionY = -1; + + private boolean modal = false; + + private Element modalityCurtain; + private Element draggingCurtain; + + private Element headerText; + + public IWindow() { + super(); + final int order = windowOrder.size(); + setWindowOrder(order); + windowOrder.add(this); + constructDOM(); + setPopupPosition(order * STACKING_OFFSET_PIXELS, order + * STACKING_OFFSET_PIXELS); + contentPanel.addScrollListener(this); + } + + private void bringToFront() { + int curIndex = windowOrder.indexOf(this); + if (curIndex + 1 < windowOrder.size()) { + windowOrder.remove(this); + windowOrder.add(this); + for (; curIndex < windowOrder.size(); curIndex++) { + ((IWindow) windowOrder.get(curIndex)).setWindowOrder(curIndex); + } + } + } + + /** + * Returns true if window is the topmost window + * + * @return + */ + private boolean isActive() { + return windowOrder.lastElement().equals(this); + } + + public void setWindowOrder(int order) { + int zIndex = (order + Z_INDEX_BASE); + if (modal) { + zIndex += 1000; + DOM.setStyleAttribute(modalityCurtain, "zIndex", "" + zIndex); + } + DOM.setStyleAttribute(getElement(), "zIndex", "" + zIndex); + } + + protected void constructDOM() { + header = DOM.createDiv(); + DOM.setElementProperty(header, "className", CLASSNAME + "-outerheader"); + headerText = DOM.createDiv(); + DOM.setElementProperty(headerText, "className", CLASSNAME + "-header"); + contents = DOM.createDiv(); + DOM.setElementProperty(contents, "className", CLASSNAME + "-contents"); + footer = DOM.createDiv(); + DOM.setElementProperty(footer, "className", CLASSNAME + "-footer"); + resizeBox = DOM.createDiv(); + DOM + .setElementProperty(resizeBox, "className", CLASSNAME + + "-resizebox"); + closeBox = DOM.createDiv(); + DOM.setElementProperty(closeBox, "className", CLASSNAME + "-closebox"); + DOM.appendChild(footer, resizeBox); + + DOM.sinkEvents(getElement(), Event.ONLOSECAPTURE); + DOM.sinkEvents(closeBox, Event.ONCLICK); + DOM.sinkEvents(contents, Event.ONCLICK); + + final Element wrapper = DOM.createDiv(); + DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap"); + + final Element wrapper2 = DOM.createDiv(); + DOM.setElementProperty(wrapper2, "className", CLASSNAME + "-wrap2"); + + DOM.sinkEvents(wrapper, Event.ONKEYDOWN); + + DOM.appendChild(wrapper2, closeBox); + DOM.appendChild(wrapper2, header); + DOM.appendChild(header, headerText); + DOM.appendChild(wrapper2, contents); + DOM.appendChild(wrapper2, footer); + DOM.appendChild(wrapper, wrapper2); + DOM.appendChild(super.getContainerElement(), wrapper); + DOM.setElementProperty(getElement(), "className", CLASSNAME); + + sinkEvents(Event.MOUSEEVENTS); + + setWidget(contentPanel); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + id = uidl.getId(); + this.client = client; + + // Workaround needed for Testing Tools (GWT generates window DOM + // slightly different in different browsers). + DOM.setElementProperty(closeBox, "id", id + "_window_close"); + + if (uidl.hasAttribute("invisible")) { + this.hide(); + return; + } + + if (client.updateComponent(this, uidl, false)) { + return; + } + + if (uidl.getBooleanAttribute("modal") != modal) { + setModal(!modal); + } + + // Initialize the size from UIDL + // FIXME relational size is for outer size, others are applied for + // content + if (uidl.hasVariable("width")) { + final String width = uidl.getStringVariable("width"); + if (width.indexOf("px") < 0) { + DOM.setStyleAttribute(getElement(), "width", width); + } else { + setWidth(width); + } + } + + // Initialize the position form UIDL + try { + final int positionx = uidl.getIntVariable("positionx"); + final int positiony = uidl.getIntVariable("positiony"); + if (positionx >= 0 && positiony >= 0) { + setPopupPosition(positionx, positiony); + } + } catch (final IllegalArgumentException e) { + // Silently ignored as positionx and positiony are not required + // parameters + } + + if (!isAttached()) { + show(); + } + + // Height set after show so we can detect space used by decorations + if (uidl.hasVariable("height")) { + final String height = uidl.getStringVariable("height"); + if (height.indexOf("%") > 0) { + int winHeight = Window.getClientHeight(); + float percent = Float.parseFloat(height.substring(0, height + .indexOf("%"))) / 100.0f; + int contentPixels = (int) (winHeight * percent); + contentPixels -= (DOM.getElementPropertyInt(getElement(), + "offsetHeight") - DOM.getElementPropertyInt(contents, + "offsetHeight")); + // FIXME hardcoded contents elements border size + contentPixels -= 2; + + setHeight(contentPixels + "px"); + } else { + setHeight(height); + } + } + + if (uidl.hasAttribute("caption")) { + setCaption(uidl.getStringAttribute("caption"), uidl + .getStringAttribute("icon")); + } + + boolean showingUrl = false; + int childIndex = 0; + UIDL childUidl = uidl.getChildUIDL(childIndex++); + while ("open".equals(childUidl.getTag())) { + // TODO multiple opens with the same target will in practice just + // open the last one - should we fix that somehow? + final String parsedUri = client.translateToolkitUri(childUidl + .getStringAttribute("src")); + if (!childUidl.hasAttribute("name")) { + final Frame frame = new Frame(); + DOM.setStyleAttribute(frame.getElement(), "width", "100%"); + DOM.setStyleAttribute(frame.getElement(), "height", "100%"); + DOM.setStyleAttribute(frame.getElement(), "border", "0px"); + frame.setUrl(parsedUri); + contentPanel.setWidget(frame); + showingUrl = true; + } else { + final String target = childUidl.getStringAttribute("name"); + Window.open(parsedUri, target, ""); + } + childUidl = uidl.getChildUIDL(childIndex++); + } + + final Paintable lo = client.getPaintable(childUidl); + if (layout != null) { + if (layout != lo) { + // remove old + client.unregisterPaintable(layout); + contentPanel.remove((Widget) layout); + // add new + if (!showingUrl) { + contentPanel.setWidget((Widget) lo); + } + layout = lo; + } + } else if (!showingUrl) { + contentPanel.setWidget((Widget) lo); + } + lo.updateFromUIDL(childUidl, client); + + // we may have actions and notifications + if (uidl.getChildCount() > 1) { + final int cnt = uidl.getChildCount(); + for (int i = 1; i < cnt; i++) { + childUidl = uidl.getChildUIDL(i); + if (childUidl.getTag().equals("actions")) { + if (shortcutHandler == null) { + shortcutHandler = new ShortcutActionHandler(id, client); + } + shortcutHandler.updateActionMap(childUidl); + } else if (childUidl.getTag().equals("notifications")) { + // TODO needed? move -> + for (final Iterator it = childUidl.getChildIterator(); it + .hasNext();) { + final UIDL notification = (UIDL) it.next(); + String html = ""; + if (notification.hasAttribute("icon")) { + final String parsedUri = client + .translateToolkitUri(notification + .getStringAttribute("icon")); + html += ""; + } + if (notification.hasAttribute("caption")) { + html += "

" + + notification + .getStringAttribute("caption") + + "

"; + } + if (notification.hasAttribute("message")) { + html += "

" + + notification + .getStringAttribute("message") + + "

"; + } + + final String style = notification.hasAttribute("style") ? notification + .getStringAttribute("style") + : null; + final int position = notification + .getIntAttribute("position"); + final int delay = notification.getIntAttribute("delay"); + new Notification(delay).show(html, position, style); + } + } + } + + } + + // setting scrollposition must happen after children is rendered + contentPanel.setScrollPosition(uidl.getIntVariable("scrolltop")); + contentPanel.setHorizontalScrollPosition(uidl + .getIntVariable("scrollleft")); + + } + + public void show() { + if (modal) { + showModalityCurtain(); + } + super.show(); + + setFF2CaretFixEnabled(true); + fixFF3OverflowBug(); + } + + /** Disable overflow auto with FF3 to fix #1837. */ + private void fixFF3OverflowBug() { + if (BrowserInfo.get().isFF3()) { + DeferredCommand.addCommand(new Command() { + public void execute() { + DOM.setStyleAttribute(getElement(), "overflow", ""); + } + }); + } + } + + /** + * Fix "missing cursor" browser bug workaround for FF2 in Windows and Linux. + * + * Calling this method has no effect on other browsers than the ones based + * on Gecko 1.8 + * + * @param enable + */ + private void setFF2CaretFixEnabled(boolean enable) { + if (BrowserInfo.get().isFF2()) { + if (enable) { + DeferredCommand.addCommand(new Command() { + public void execute() { + DOM.setStyleAttribute(getElement(), "overflow", "auto"); + } + }); + } else { + DOM.setStyleAttribute(getElement(), "overflow", ""); + } + } + } + + public void hide() { + if (modal) { + hideModalityCurtain(); + } + super.hide(); + } + + private void setModal(boolean modality) { + modal = modality; + if (modal) { + modalityCurtain = DOM.createDiv(); + DOM.setElementProperty(modalityCurtain, "className", CLASSNAME + + "-modalitycurtain"); + if (isAttached()) { + showModalityCurtain(); + bringToFront(); + } else { + DeferredCommand.addCommand(new Command() { + public void execute() { + // modal window must on top of others + bringToFront(); + } + }); + } + } else { + if (modalityCurtain != null) { + if (isAttached()) { + hideModalityCurtain(); + } + modalityCurtain = null; + } + } + } + + private void showModalityCurtain() { + if (BrowserInfo.get().isFF2()) { + DOM.setStyleAttribute(modalityCurtain, "height", DOM + .getElementPropertyInt(RootPanel.getBodyElement(), + "offsetHeight") + + "px"); + DOM.setStyleAttribute(modalityCurtain, "position", "absolute"); + } + DOM.appendChild(RootPanel.getBodyElement(), modalityCurtain); + } + + private void hideModalityCurtain() { + DOM.removeChild(RootPanel.getBodyElement(), modalityCurtain); + } + + /* + * Shows (or hides) an empty div on top of all other content; used when + * resizing or moving, so that iframes (etc) do not steal event. + */ + private void showDraggingCurtain(boolean show) { + if (show && draggingCurtain == null) { + + setFF2CaretFixEnabled(false); // makes FF2 slow + + draggingCurtain = DOM.createDiv(); + DOM.setStyleAttribute(draggingCurtain, "position", "absolute"); + DOM.setStyleAttribute(draggingCurtain, "top", "0px"); + DOM.setStyleAttribute(draggingCurtain, "left", "0px"); + DOM.setStyleAttribute(draggingCurtain, "width", "100%"); + DOM.setStyleAttribute(draggingCurtain, "height", "100%"); + DOM.setStyleAttribute(draggingCurtain, "zIndex", "" + + ToolkitOverlay.Z_INDEX); + + DOM.appendChild(RootPanel.getBodyElement(), draggingCurtain); + } else if (!show && draggingCurtain != null) { + + setFF2CaretFixEnabled(true); // makes FF2 slow + + DOM.removeChild(RootPanel.getBodyElement(), draggingCurtain); + draggingCurtain = null; + } + + } + + public void setPopupPosition(int left, int top) { + super.setPopupPosition(left, top); + if (left != uidlPositionX && client != null) { + client.updateVariable(id, "positionx", left, false); + uidlPositionX = left; + } + if (top != uidlPositionY && client != null) { + client.updateVariable(id, "positiony", top, false); + uidlPositionY = top; + } + } + + public void setCaption(String c) { + setCaption(c, null); + } + + public void setCaption(String c, String icon) { + String html = Util.escapeHTML(c); + if (icon != null) { + icon = client.translateToolkitUri(icon); + html = "" + html; + } + DOM.setInnerHTML(headerText, html); + } + + protected Element getContainerElement() { + return contents; + } + + public void onBrowserEvent(final Event event) { + final int type = DOM.eventGetType(event); + + if (type == Event.ONKEYDOWN && shortcutHandler != null) { + shortcutHandler.handleKeyboardEvent(event); + return; + } + + final Element target = DOM.eventGetTarget(event); + + // Handle window caption tooltips + if (client != null && DOM.isOrHasChild(header, target)) { + client.handleTooltipEvent(event, this); + } + + if (resizing || DOM.compare(resizeBox, target)) { + onResizeEvent(event); + DOM.eventCancelBubble(event, true); + } else if (DOM.compare(target, closeBox)) { + if (type == Event.ONCLICK) { + onCloseClick(); + DOM.eventCancelBubble(event, true); + } + } else if (dragging || !DOM.isOrHasChild(contents, target)) { + onDragEvent(event); + DOM.eventCancelBubble(event, true); + } else if (type == Event.ONCLICK) { + // clicked inside window, ensure to be on top + if (!isActive()) { + bringToFront(); + } + } + } + + private void onCloseClick() { + client.updateVariable(id, "close", true, true); + } + + private void onResizeEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONMOUSEDOWN: + if (!isActive()) { + bringToFront(); + } + showDraggingCurtain(true); + resizing = true; + startX = DOM.eventGetScreenX(event); + startY = DOM.eventGetScreenY(event); + origW = getWidget().getOffsetWidth(); + origH = getWidget().getOffsetHeight(); + DOM.setCapture(getElement()); + DOM.eventPreventDefault(event); + break; + case Event.ONMOUSEUP: + showDraggingCurtain(false); + resizing = false; + DOM.releaseCapture(getElement()); + setSize(event, true); + break; + case Event.ONLOSECAPTURE: + showDraggingCurtain(false); + resizing = false; + case Event.ONMOUSEMOVE: + if (resizing) { + setSize(event, false); + DOM.eventPreventDefault(event); + } + break; + default: + DOM.eventPreventDefault(event); + break; + } + } + + public void setSize(Event event, boolean updateVariables) { + int w = DOM.eventGetScreenX(event) - startX + origW; + if (w < MIN_WIDTH) { + w = MIN_WIDTH; + } + int h = DOM.eventGetScreenY(event) - startY + origH; + if (h < MIN_HEIGHT) { + h = MIN_HEIGHT; + } + setWidth(w + "px"); + setHeight(h + "px"); + if (updateVariables) { + // sending width back always as pixels, no need for unit + client.updateVariable(id, "width", w, false); + client.updateVariable(id, "height", h, false); + } + // Update child widget dimensions + Util.runDescendentsLayout(this); + } + + public void setWidth(String width) { + if (!"".equals(width)) { + DOM + .setStyleAttribute( + getElement(), + "width", + (Integer.parseInt(width.substring(0, + width.length() - 2)) + BORDER_WIDTH_HORIZONTAL) + + "px"); + } + } + + private void onDragEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONMOUSEDOWN: + if (!isActive()) { + bringToFront(); + } + showDraggingCurtain(true); + dragging = true; + startX = DOM.eventGetScreenX(event); + startY = DOM.eventGetScreenY(event); + origX = DOM.getAbsoluteLeft(getElement()); + origY = DOM.getAbsoluteTop(getElement()); + DOM.setCapture(getElement()); + DOM.eventPreventDefault(event); + break; + case Event.ONMOUSEUP: + dragging = false; + showDraggingCurtain(false); + DOM.releaseCapture(getElement()); + break; + case Event.ONLOSECAPTURE: + showDraggingCurtain(false); + dragging = false; + break; + case Event.ONMOUSEMOVE: + if (dragging) { + final int x = DOM.eventGetScreenX(event) - startX + origX; + final int y = DOM.eventGetScreenY(event) - startY + origY; + setPopupPosition(x, y); + DOM.eventPreventDefault(event); + } + break; + default: + break; + } + } + + public boolean onEventPreview(Event event) { + if (dragging) { + onDragEvent(event); + return false; + } else if (resizing) { + onResizeEvent(event); + return false; + } else if (modal) { + // return false when modal and outside window + final Element target = DOM.eventGetTarget(event); + if (!DOM.isOrHasChild(getElement(), target)) { + return false; + } + } + return true; + } + + public void onScroll(Widget widget, int scrollLeft, int scrollTop) { + client.updateVariable(id, "scrolltop", scrollTop, false); + client.updateVariable(id, "scrollleft", scrollLeft, false); + } + + public void addStyleDependentName(String styleSuffix) { + // IWindow's getStyleElement() does not return the same element as + // getElement(), so we need to override this. + setStyleName(getElement(), getStylePrimaryName() + "-" + styleSuffix, + true); + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Icon.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Icon.java new file mode 100644 index 0000000000..52672d69f4 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Icon.java @@ -0,0 +1,36 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.UIObject; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; + +public class Icon extends UIObject { + private final ApplicationConnection client; + private String myUri; + + public Icon(ApplicationConnection client) { + setElement(DOM.createImg()); + DOM.setElementProperty(getElement(), "alt", "icon"); + setStyleName("i-icon"); + this.client = client; + client.addPngFix(getElement()); + } + + public Icon(ApplicationConnection client, String uidlUri) { + this(client); + setUri(uidlUri); + } + + public void setUri(String uidlUri) { + if (!uidlUri.equals(myUri)) { + String uri = client.translateToolkitUri(uidlUri); + DOM.setElementProperty(getElement(), "src", uri); + myUri = uidlUri; + } + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/MarginInfo.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/MarginInfo.java new file mode 100644 index 0000000000..68d2e2eadf --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/MarginInfo.java @@ -0,0 +1,68 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +public class MarginInfo { + + private static final int TOP = 1; + private static final int RIGHT = 2; + private static final int BOTTOM = 4; + private static final int LEFT = 8; + + private int bitMask; + + public MarginInfo(int bitMask) { + this.bitMask = bitMask; + } + + public MarginInfo(boolean top, boolean right, boolean bottom, boolean left) { + setMargins(top, right, bottom, left); + } + + public void setMargins(boolean top, boolean right, boolean bottom, + boolean left) { + bitMask = top ? TOP : 0; + bitMask += right ? RIGHT : 0; + bitMask += bottom ? BOTTOM : 0; + bitMask += left ? LEFT : 0; + } + + public boolean hasLeft() { + return (bitMask & LEFT) == LEFT; + } + + public boolean hasRight() { + return (bitMask & RIGHT) == RIGHT; + } + + public boolean hasTop() { + return (bitMask & TOP) == TOP; + } + + public boolean hasBottom() { + return (bitMask & BOTTOM) == BOTTOM; + } + + public int getBitMask() { + return bitMask; + } + + public void setMargins(boolean enabled) { + if (enabled) { + bitMask = TOP + RIGHT + BOTTOM + LEFT; + } else { + bitMask = 0; + } + } + + public boolean equals(Object obj) { + if (!(obj instanceof MarginInfo)) { + return false; + } + + return ((MarginInfo) obj).bitMask == bitMask; + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/MenuBar.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/MenuBar.java new file mode 100644 index 0000000000..b446e7d595 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/MenuBar.java @@ -0,0 +1,513 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +/* + * 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.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.DeferredCommand; +import com.google.gwt.user.client.Element; +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; + +/** + * 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 + */ +public class MenuBar extends Widget implements PopupListener { + + private final Element body; + 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(); + + final Element table = DOM.createTable(); + body = DOM.createTBody(); + DOM.appendChild(table, body); + + if (!vertical) { + final Element tr = DOM.createTR(); + DOM.appendChild(body, tr); + } + + this.vertical = vertical; + + final Element 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; + } + + 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; + } + } + } + + 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 + */ + protected 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 + */ + protected MenuItem getSelectedItem() { + return selectedItem; + } + + 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. + */ + 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) { + DeferredCommand.addCommand(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 ToolkitOverlay(true) { + { + setWidget(item.getSubMenu()); + item.getSubMenu().onShow(); + } + + 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); + } + } + } + + void selectItem(MenuItem item) { + if (item == selectedItem) { + return; + } + + if (selectedItem != null) { + selectedItem.setSelectionStyle(false); + } + + if (item != null) { + item.setSelectionStyle(true); + } + + selectedItem = item; + } + + /** + * 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 = (MenuItem) 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((MenuItem) items.get(0)); + } + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/MenuItem.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/MenuItem.java new file mode 100644 index 0000000000..a9651e8e64 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/MenuItem.java @@ -0,0 +1,188 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +/* + * 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 + */ +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; + } + + 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; + } + + 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; + } + + 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; + } + + 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/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Notification.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Notification.java new file mode 100644 index 0000000000..267a07dcca --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Notification.java @@ -0,0 +1,289 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.ArrayList; +import java.util.EventObject; +import java.util.Iterator; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.BrowserInfo; + +public class Notification extends ToolkitOverlay { + + public static final int CENTERED = 1; + public static final int CENTERED_TOP = 2; + public static final int CENTERED_BOTTOM = 3; + public static final int TOP_LEFT = 4; + public static final int TOP_RIGHT = 5; + public static final int BOTTOM_LEFT = 6; + public static final int BOTTOM_RIGHT = 7; + + public static final int DELAY_FOREVER = -1; + public static final int DELAY_NONE = 0; + + private static final String STYLENAME = "i-Notification"; + private static final int mouseMoveThreshold = 7; + private static final int Z_INDEX_BASE = 20000; + + private int startOpacity = 90; + private int fadeMsec = 400; + private int delayMsec = 1000; + + private Timer fader; + private Timer delay; + + private int x = -1; + private int y = -1; + + private String temporaryStyle; + + private ArrayList listeners; + + public Notification() { + setStylePrimaryName(STYLENAME); + sinkEvents(Event.ONCLICK); + DOM.setStyleAttribute(getElement(), "zIndex", "" + Z_INDEX_BASE); + } + + public Notification(int delayMsec) { + this(); + this.delayMsec = delayMsec; + } + + public Notification(int delayMsec, int fadeMsec, int startOpacity) { + this(delayMsec); + this.fadeMsec = fadeMsec; + this.startOpacity = startOpacity; + } + + public void startDelay() { + DOM.removeEventPreview(this); + if (delayMsec > 0) { + delay = new Timer() { + public void run() { + fade(); + } + }; + delay.scheduleRepeating(delayMsec); + } else if (delayMsec == 0) { + fade(); + } + } + + public void show() { + show(CENTERED); + } + + public void show(String style) { + show(CENTERED, style); + } + + public void show(int position) { + show(position, null); + } + + public void show(Widget widget, int position, String style) { + setWidget(widget); + show(position, style); + } + + public void show(String html, int position, String style) { + setWidget(new HTML(html)); + show(position, style); + } + + public void show(int position, String style) { + setOpacity(getElement(), startOpacity); + if (style != null) { + temporaryStyle = style; + addStyleName(style); + } + super.show(); + setPosition(position); + } + + public void hide() { + DOM.removeEventPreview(this); + cancelDelay(); + cancelFade(); + if (temporaryStyle != null) { + removeStyleName(temporaryStyle); + temporaryStyle = null; + } + super.hide(); + fireEvent(new HideEvent(this)); + } + + public void fade() { + DOM.removeEventPreview(this); + cancelDelay(); + fader = new Timer() { + int opacity = startOpacity; + + public void run() { + opacity -= 5; + setOpacity(getElement(), opacity); + if (opacity <= 0) { + cancel(); + hide(); + if (BrowserInfo.get().isOpera()) { + // tray notification on opera needs to explicitly define + // size, reset it + DOM.setStyleAttribute(getElement(), "width", ""); + DOM.setStyleAttribute(getElement(), "height", ""); + } + + } + } + }; + final int msec = fadeMsec / (startOpacity / 5); + fader.scheduleRepeating(msec); + } + + public void setPosition(int position) { + final Element el = getElement(); + DOM.setStyleAttribute(el, "top", ""); + DOM.setStyleAttribute(el, "left", ""); + DOM.setStyleAttribute(el, "bottom", ""); + DOM.setStyleAttribute(el, "right", ""); + switch (position) { + case TOP_LEFT: + DOM.setStyleAttribute(el, "top", "0px"); + DOM.setStyleAttribute(el, "left", "0px"); + break; + case TOP_RIGHT: + DOM.setStyleAttribute(el, "top", "0px"); + DOM.setStyleAttribute(el, "right", "0px"); + break; + case BOTTOM_RIGHT: + DOM.setStyleAttribute(el, "position", "absolute"); + if (BrowserInfo.get().isOpera()) { + // tray notification on opera needs explicitly defined size + DOM.setStyleAttribute(el, "width", getOffsetWidth() + "px"); + DOM.setStyleAttribute(el, "height", getOffsetHeight() + "px"); + } + DOM.setStyleAttribute(el, "bottom", "0px"); + DOM.setStyleAttribute(el, "right", "0px"); + break; + case BOTTOM_LEFT: + DOM.setStyleAttribute(el, "bottom", "0px"); + DOM.setStyleAttribute(el, "left", "0px"); + break; + case CENTERED_TOP: + center(); + DOM.setStyleAttribute(el, "top", "0px"); + break; + case CENTERED_BOTTOM: + center(); + DOM.setStyleAttribute(el, "top", ""); + DOM.setStyleAttribute(el, "bottom", "0px"); + break; + default: + case CENTERED: + center(); + break; + } + } + + private void cancelFade() { + if (fader != null) { + fader.cancel(); + fader = null; + } + } + + private void cancelDelay() { + if (delay != null) { + delay.cancel(); + delay = null; + } + } + + private void setOpacity(Element el, int opacity) { + DOM.setStyleAttribute(el, "opacity", "" + (opacity / 100.0)); + DOM.setStyleAttribute(el, "filter", "Alpha(opacity=" + opacity + ")"); + + } + + public void onBrowserEvent(Event event) { + DOM.removeEventPreview(this); + if (fader == null) { + fade(); + } + } + + public boolean onEventPreview(Event event) { + int type = DOM.eventGetType(event); + // "modal" + if (delayMsec == -1) { + if (type == Event.ONCLICK + && DOM + .isOrHasChild(getElement(), DOM + .eventGetTarget(event))) { + fade(); + } + 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) { + startDelay(); + } + break; + case Event.ONCLICK: + case Event.ONDBLCLICK: + case Event.KEYEVENTS: + case Event.ONSCROLL: + default: + startDelay(); + } + 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 = (EventListener) it.next(); + l.notificationHidden(event); + } + } + } + + public class HideEvent extends EventObject { + public HideEvent(Object source) { + super(source); + } + } + + public interface EventListener extends java.util.EventListener { + public void notificationHidden(HideEvent event); + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ShortcutActionHandler.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ShortcutActionHandler.java new file mode 100644 index 0000000000..244710c6c9 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ShortcutActionHandler.java @@ -0,0 +1,177 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.ArrayList; +import java.util.Iterator; + +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.DeferredCommand; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.KeyboardListener; +import com.google.gwt.user.client.ui.KeyboardListenerCollection; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.UIDL; + +/** + * A helper class to implement keyboard shorcut handling. Keeps a list of owners + * actions and fires actions to server. User class needs to delegate keyboard + * events to handleKeyboardEvents function. + * + * @author IT Mill ltd + */ +public class ShortcutActionHandler { + private final ArrayList actions = new ArrayList(); + private ApplicationConnection client; + private String paintableId; + + /** + * + * @param pid + * Paintable id + * @param c + * reference to application connections + */ + public ShortcutActionHandler(String pid, ApplicationConnection c) { + paintableId = pid; + client = c; + } + + /** + * Updates list of actions this handler listens to. + * + * @param c + * UIDL snippet containing actions + */ + public void updateActionMap(UIDL c) { + actions.clear(); + final Iterator it = c.getChildIterator(); + while (it.hasNext()) { + final UIDL action = (UIDL) it.next(); + + int[] modifiers = null; + if (action.hasAttribute("mk")) { + modifiers = action.getIntArrayAttribute("mk"); + } + + final ShortcutKeyCombination kc = new ShortcutKeyCombination(action + .getIntAttribute("kc"), modifiers); + final String key = action.getStringAttribute("key"); + final String caption = action.getStringAttribute("caption"); + actions.add(new ShortcutAction(key, kc, caption)); + } + } + + public void handleKeyboardEvent(Event event) { + final int modifiers = KeyboardListenerCollection + .getKeyboardModifiers(event); + final char keyCode = (char) DOM.eventGetKeyCode(event); + final ShortcutKeyCombination kc = new ShortcutKeyCombination(keyCode, + modifiers); + final Iterator it = actions.iterator(); + while (it.hasNext()) { + final ShortcutAction a = (ShortcutAction) it.next(); + if (a.getShortcutCombination().equals(kc)) { + shakeTarget(DOM.eventGetTarget(event)); + DeferredCommand.addCommand(new Command() { + public void execute() { + client.updateVariable(paintableId, "action", + a.getKey(), true); + } + }); + break; + } + } + } + + public static native void shakeTarget(Element e) + /*-{ + if(e.blur) { + e.blur(); + e.focus(); + } + }-*/; + +} + +class ShortcutKeyCombination { + + public static final int SHIFT = 16; + public static final int CTRL = 17; + public static final int ALT = 18; + + char keyCode = 0; + private int modifiersMask; + + public ShortcutKeyCombination() { + } + + ShortcutKeyCombination(char kc, int modifierMask) { + keyCode = kc; + modifiersMask = modifierMask; + } + + ShortcutKeyCombination(int kc, int[] modifiers) { + keyCode = (char) kc; + keyCode = Character.toUpperCase(keyCode); + + modifiersMask = 0; + if (modifiers != null) { + for (int i = 0; i < modifiers.length; i++) { + switch (modifiers[i]) { + case ALT: + modifiersMask = modifiersMask + | KeyboardListener.MODIFIER_ALT; + break; + case CTRL: + modifiersMask = modifiersMask + | KeyboardListener.MODIFIER_CTRL; + break; + case SHIFT: + modifiersMask = modifiersMask + | KeyboardListener.MODIFIER_SHIFT; + break; + default: + break; + } + } + } + } + + public boolean equals(ShortcutKeyCombination other) { + if (keyCode == other.keyCode && modifiersMask == other.modifiersMask) { + return true; + } + return false; + } +} + +class ShortcutAction { + + private final ShortcutKeyCombination sc; + private final String caption; + private final String key; + + public ShortcutAction(String key, ShortcutKeyCombination sc, String caption) { + this.sc = sc; + this.key = key; + this.caption = caption; + } + + public ShortcutKeyCombination getShortcutCombination() { + return sc; + } + + public String getCaption() { + return caption; + } + + public String getKey() { + return key; + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Table.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Table.java new file mode 100644 index 0000000000..40cccf00ac --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Table.java @@ -0,0 +1,15 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.ui.HasWidgets; +import com.itmill.toolkit.terminal.gwt.client.Paintable; + +public interface Table extends Paintable, HasWidgets { + final int SELECT_MODE_NONE = 0; + final int SELECT_MODE_SINGLE = 1; + final int SELECT_MODE_MULTI = 2; + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Time.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Time.java new file mode 100644 index 0000000000..ee7b40f2b4 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/Time.java @@ -0,0 +1,317 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import java.util.Date; + +import com.google.gwt.user.client.ui.ChangeListener; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.Widget; + +public class Time extends FlowPanel implements ChangeListener { + + private final IDateField datefield; + + private ListBox hours; + + private ListBox mins; + + private ListBox sec; + + private ListBox msec; + + private ListBox ampm; + + private int resolution = IDateField.RESOLUTION_HOUR; + + private boolean readonly; + + public Time(IDateField parent) { + super(); + datefield = parent; + setStyleName(IDateField.CLASSNAME + "-time"); + } + + private void buildTime(boolean redraw) { + final boolean thc = datefield.getDateTimeService().isTwelveHourClock(); + if (redraw) { + clear(); + final int numHours = thc ? 12 : 24; + hours = new ListBox(); + hours.setStyleName(INativeSelect.CLASSNAME); + for (int i = 0; i < numHours; i++) { + hours.addItem((i < 10) ? "0" + i : "" + i); + } + hours.addChangeListener(this); + if (thc) { + ampm = new ListBox(); + ampm.setStyleName(INativeSelect.CLASSNAME); + final String[] ampmText = datefield.getDateTimeService() + .getAmPmStrings(); + ampm.addItem(ampmText[0]); + ampm.addItem(ampmText[1]); + ampm.addChangeListener(this); + } + + if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_MIN) { + mins = new ListBox(); + mins.setStyleName(INativeSelect.CLASSNAME); + for (int i = 0; i < 60; i++) { + mins.addItem((i < 10) ? "0" + i : "" + i); + } + mins.addChangeListener(this); + } + if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_SEC) { + sec = new ListBox(); + sec.setStyleName(INativeSelect.CLASSNAME); + for (int i = 0; i < 60; i++) { + sec.addItem((i < 10) ? "0" + i : "" + i); + } + sec.addChangeListener(this); + } + if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MSEC) { + msec = new ListBox(); + msec.setStyleName(INativeSelect.CLASSNAME); + for (int i = 0; i < 1000; i++) { + if (i < 10) { + msec.addItem("00" + i); + } else if (i < 100) { + msec.addItem("0" + i); + } else { + msec.addItem("" + i); + } + } + msec.addChangeListener(this); + } + + final String delimiter = datefield.getDateTimeService() + .getClockDelimeter(); + final boolean ro = datefield.isReadonly(); + + if (ro) { + int h = 0; + if (datefield.getCurrentDate() != null) { + h = datefield.getCurrentDate().getHours(); + } + if (thc) { + h -= h < 12 ? 0 : 12; + } + add(new ILabel(h < 10 ? "0" + h : "" + h)); + } else { + add(hours); + } + + if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_MIN) { + add(new ILabel(delimiter)); + if (ro) { + final int m = mins.getSelectedIndex(); + add(new ILabel(m < 10 ? "0" + m : "" + m)); + } else { + add(mins); + } + } + if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_SEC) { + add(new ILabel(delimiter)); + if (ro) { + final int s = sec.getSelectedIndex(); + add(new ILabel(s < 10 ? "0" + s : "" + s)); + } else { + add(sec); + } + } + if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MSEC) { + add(new ILabel(".")); + if (ro) { + final int m = datefield.getMilliseconds(); + final String ms = m < 100 ? "0" + m : "" + m; + add(new ILabel(m < 10 ? "0" + ms : ms)); + } else { + add(msec); + } + } + if (datefield.getCurrentResolution() == IDateField.RESOLUTION_HOUR) { + add(new ILabel(delimiter + "00")); // o'clock + } + if (thc) { + add(new ILabel(" ")); + if (ro) { + add(new ILabel(ampm.getItemText(datefield.getCurrentDate() + .getHours() < 12 ? 0 : 1))); + } else { + add(ampm); + } + } + + if (ro) { + return; + } + } + + // Update times + Date cdate = datefield.getCurrentDate(); + boolean selected = true; + if (cdate == null) { + cdate = new Date(); + selected = false; + } + if (thc) { + int h = cdate.getHours(); + ampm.setSelectedIndex(h < 12 ? 0 : 1); + h -= ampm.getSelectedIndex() * 12; + hours.setSelectedIndex(h); + } else { + hours.setSelectedIndex(cdate.getHours()); + } + if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_MIN) { + mins.setSelectedIndex(cdate.getMinutes()); + } + if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_SEC) { + sec.setSelectedIndex(cdate.getSeconds()); + } + if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MSEC) { + if (selected) { + msec.setSelectedIndex(datefield.getMilliseconds()); + } else { + msec.setSelectedIndex(0); + } + } + if (thc) { + ampm.setSelectedIndex(cdate.getHours() < 12 ? 0 : 1); + } + + if (datefield.isReadonly() && !redraw) { + // Do complete redraw when in read-only status + clear(); + final String delimiter = datefield.getDateTimeService() + .getClockDelimeter(); + + int h = cdate.getHours(); + if (thc) { + h -= h < 12 ? 0 : 12; + } + add(new ILabel(h < 10 ? "0" + h : "" + h)); + + if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_MIN) { + add(new ILabel(delimiter)); + final int m = mins.getSelectedIndex(); + add(new ILabel(m < 10 ? "0" + m : "" + m)); + } + if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_SEC) { + add(new ILabel(delimiter)); + final int s = sec.getSelectedIndex(); + add(new ILabel(s < 10 ? "0" + s : "" + s)); + } + if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MSEC) { + add(new ILabel(".")); + final int m = datefield.getMilliseconds(); + final String ms = m < 100 ? "0" + m : "" + m; + add(new ILabel(m < 10 ? "0" + ms : ms)); + } + if (datefield.getCurrentResolution() == IDateField.RESOLUTION_HOUR) { + add(new ILabel(delimiter + "00")); // o'clock + } + if (thc) { + add(new ILabel(" ")); + add(new ILabel(ampm.getItemText(cdate.getHours() < 12 ? 0 : 1))); + } + } + + final boolean enabled = datefield.isEnabled(); + hours.setEnabled(enabled); + if (mins != null) { + mins.setEnabled(enabled); + } + if (sec != null) { + sec.setEnabled(enabled); + } + if (msec != null) { + msec.setEnabled(enabled); + } + if (ampm != null) { + ampm.setEnabled(enabled); + } + + } + + public void updateTime(boolean redraw) { + buildTime(redraw || resolution != datefield.getCurrentResolution() + || readonly != datefield.isReadonly()); + if (datefield instanceof ITextualDate) { + ((ITextualDate) datefield).buildDate(); + } + resolution = datefield.getCurrentResolution(); + readonly = datefield.isReadonly(); + } + + public void onChange(Widget sender) { + if (datefield.getCurrentDate() == null) { + // was null on server, need to set + Date now = datefield.getShowingDate(); + if (now == null) { + now = new Date(); + datefield.setShowingDate(now); + } + datefield.setCurrentDate(new Date(now.getTime())); + + // Init variables with current time + datefield.getClient().updateVariable(datefield.getId(), "year", + now.getYear() + 1900, false); + datefield.getClient().updateVariable(datefield.getId(), "month", + now.getMonth() + 1, false); + datefield.getClient().updateVariable(datefield.getId(), "day", + now.getDate(), false); + datefield.getClient().updateVariable(datefield.getId(), "hour", + now.getHours(), false); + datefield.getClient().updateVariable(datefield.getId(), "min", + now.getMinutes(), false); + datefield.getClient().updateVariable(datefield.getId(), "sec", + now.getSeconds(), false); + datefield.getClient().updateVariable(datefield.getId(), "msec", + datefield.getMilliseconds(), false); + } + if (sender == hours) { + int h = hours.getSelectedIndex(); + if (datefield.getDateTimeService().isTwelveHourClock()) { + h = h + ampm.getSelectedIndex() * 12; + } + datefield.getCurrentDate().setHours(h); + datefield.getShowingDate().setHours(h); + datefield.getClient().updateVariable(datefield.getId(), "hour", h, + datefield.isImmediate()); + updateTime(false); + } else if (sender == mins) { + final int m = mins.getSelectedIndex(); + datefield.getCurrentDate().setMinutes(m); + datefield.getShowingDate().setMinutes(m); + datefield.getClient().updateVariable(datefield.getId(), "min", m, + datefield.isImmediate()); + updateTime(false); + } else if (sender == sec) { + final int s = sec.getSelectedIndex(); + datefield.getCurrentDate().setSeconds(s); + datefield.getShowingDate().setSeconds(s); + datefield.getClient().updateVariable(datefield.getId(), "sec", s, + datefield.isImmediate()); + updateTime(false); + } else if (sender == msec) { + final int ms = msec.getSelectedIndex(); + datefield.setMilliseconds(ms); + datefield.setShowingMilliseconds(ms); + datefield.getClient().updateVariable(datefield.getId(), "msec", ms, + datefield.isImmediate()); + updateTime(false); + } else if (sender == ampm) { + final int h = hours.getSelectedIndex() + ampm.getSelectedIndex() + * 12; + datefield.getCurrentDate().setHours(h); + datefield.getShowingDate().setHours(h); + datefield.getClient().updateVariable(datefield.getId(), "hour", h, + datefield.isImmediate()); + updateTime(false); + } + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ToolkitOverlay.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ToolkitOverlay.java new file mode 100644 index 0000000000..306c8640b3 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/ToolkitOverlay.java @@ -0,0 +1,147 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.PopupListener; +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.itmill.toolkit.terminal.gwt.client.BrowserInfo; + +/** + * In Toolkit UI this Overlay 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 IWindow objects. + */ +public class ToolkitOverlay extends PopupPanel { + + public static final int Z_INDEX = 20000; + + private Shadow shadow; + + public ToolkitOverlay() { + super(); + adjustZIndex(); + } + + public ToolkitOverlay(boolean autoHide) { + super(autoHide); + adjustZIndex(); + } + + public ToolkitOverlay(boolean autoHide, boolean modal) { + super(autoHide, modal); + adjustZIndex(); + } + + public ToolkitOverlay(boolean autoHide, boolean modal, boolean showShadow) { + super(autoHide, modal); + if (showShadow) { + shadow = new Shadow(this); + } + adjustZIndex(); + } + + private void adjustZIndex() { + DOM.setStyleAttribute(getElement(), "zIndex", "" + Z_INDEX); + } + + public void setPopupPosition(int left, int top) { + super.setPopupPosition(left, top); + if (shadow != null) { + shadow.updateSizeAndPosition(); + } + } + + public void show() { + super.show(); + if (shadow != null) { + DOM.appendChild(RootPanel.get().getElement(), shadow.getElement()); + shadow.updateSizeAndPosition(); + } + } + + public void setShadowOffset(int top, int right, int bottom, int left) { + if(shadow != null) { + shadow.setOffset(top, right, bottom, left); + } + } + + private class Shadow extends HTML { + + private static final String CLASSNAME = "i-shadow"; + + private static final String HTML = "
"; + + private Widget overlay; + + // Amount of shadow on each side. + private int top = 2; + private int right = 5; + private int bottom = 6; + private int left = 5; + + public Shadow(ToolkitOverlay overlay) { + super(HTML); + setStyleName(CLASSNAME); + DOM.setStyleAttribute(getElement(), "position", "absolute"); + + this.overlay = overlay; + overlay.addPopupListener(new PopupListener() { + public void onPopupClosed(PopupPanel sender, boolean autoClosed) { + DOM.removeChild(RootPanel.get().getElement(), shadow.getElement()); + } + }); + } + + public void updateSizeAndPosition() { + // Calculate proper z-index + String zIndex = DOM.getStyleAttribute(overlay.getElement(), + "zIndex"); + if (zIndex == null) { + zIndex = "" + Z_INDEX; + } + + // Calculate position and size + if(BrowserInfo.get().isIE()) { + // Shake IE + overlay.getOffsetHeight(); + overlay.getOffsetWidth(); + } + int x = overlay.getAbsoluteLeft() - left; + int y = overlay.getAbsoluteTop() - top; + int width = overlay.getOffsetWidth() + left + right; + int height = overlay.getOffsetHeight() + top + bottom; + if (width < 0) { + width = 0; + } + if (height < 0) { + height = 0; + } + + // Update correct values + DOM.setStyleAttribute(shadow.getElement(), "zIndex", "" + + (Integer.parseInt(zIndex) - 1)); + DOM.setStyleAttribute(getElement(), "width", width + "px"); + DOM.setStyleAttribute(getElement(), "height", height + "px"); + DOM.setStyleAttribute(getElement(), "top", y + "px"); + DOM.setStyleAttribute(getElement(), "left", x + "px"); + } + + public void setOffset(int top, int right, int bottom, int left) { + this.top = top; + this.right = right; + this.bottom = bottom; + this.left = left; + if(overlay.isAttached()) { + updateSizeAndPosition(); + } + } + + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/TreeAction.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/TreeAction.java new file mode 100644 index 0000000000..4ecc477a3e --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/TreeAction.java @@ -0,0 +1,55 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +/** + * This class is used for "row actions" in ITree and ITable + */ +public class TreeAction extends Action { + + String targetKey = ""; + String actionKey = ""; + + public TreeAction(ActionOwner owner) { + super(owner); + } + + public TreeAction(ActionOwner owner, String target, String action) { + this(owner); + targetKey = target; + actionKey = action; + } + + /** + * Sends message to server that this action has been fired. Messages are + * "standard" Toolkit messages whose value is comma separated pair of + * targetKey (row, treeNod ...) and actions id. + * + * Variablename is always "action". + * + * Actions are always sent immediatedly to server. + */ + public void execute() { + owner.getClient().updateVariable(owner.getPaintableId(), "action", + targetKey + "," + actionKey, true); + owner.getClient().getContextMenu().hide(); + } + + public String getActionKey() { + return actionKey; + } + + public void setActionKey(String actionKey) { + this.actionKey = actionKey; + } + + public String getTargetKey() { + return targetKey; + } + + public void setTargetKey(String targetKey) { + this.targetKey = targetKey; + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/TreeImages.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/TreeImages.java new file mode 100644 index 0000000000..83e0e4c57f --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/TreeImages.java @@ -0,0 +1,27 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui; + +import com.google.gwt.user.client.ui.AbstractImagePrototype; + +public interface TreeImages extends com.google.gwt.user.client.ui.TreeImages { + + /** + * An image indicating an open branch. + * + * @return a prototype of this image + * @gwt.resource com/itmill/toolkit/terminal/gwt/public/default/tree/img/expanded.png + */ + AbstractImagePrototype treeOpen(); + + /** + * An image indicating a closed branch. + * + * @return a prototype of this image + * @gwt.resource com/itmill/toolkit/terminal/gwt/public/default/tree/img/collapsed.png + */ + AbstractImagePrototype treeClosed(); + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/absolutegrid/AbsoluteGrid.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/absolutegrid/AbsoluteGrid.java new file mode 100644 index 0000000000..a2592fdde9 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/absolutegrid/AbsoluteGrid.java @@ -0,0 +1,305 @@ +package com.itmill.toolkit.terminal.gwt.client.ui.absolutegrid; + +import java.util.HashMap; +import java.util.Iterator; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.AbsolutePanel; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.Caption; +import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener; +import com.itmill.toolkit.terminal.gwt.client.Util; +import com.itmill.toolkit.terminal.gwt.client.ui.AlignmentInfo; + +/** + * Prototype helper widget to implement complex sized Toolkit layouts like + * GridLayout and OrderedLayout. Supports size, margins, spacing, but has bit + * expensive layout function. + */ +public class AbsoluteGrid extends Composite implements ContainerResizedListener { + + protected HashMap cells = new HashMap(); + + private int cols = 1; + private int rows = 1; + + private AbsolutePanel ap; + + protected int marginTop; + protected int marginBottom; + protected int marginLeft; + protected int marginRight; + + private int offsetWidth; + + private int offsetHeight; + + public AbsoluteGrid() { + ap = new AbsolutePanel(); + initWidget(ap); + } + + public AbsoluteGridCell getCell(int col, int row) { + AbsoluteGridCell p = (AbsoluteGridCell) cells.get(col + "." + row); + if (p == null) { + p = new AbsoluteGridCell(col, row); + cells.put(col + "." + row, p); + ap.add(p); + } + return p; + } + + public void clear() { + ap.clear(); + cells.clear(); + } + + public Iterator getCellIterator() { + return cells.values().iterator(); + } + + private float getCellWidth(int colspan) { + int total = ap.getOffsetWidth(); + total -= getMarginWidth(); + total -= getSpacingSize() * (cols - colspan); + if (total < 0) { + return 0; + } + return total * colspan / (float) cols; + } + + /** + * + * @return space used by left and right margin + */ + private int getMarginWidth() { + return marginLeft + marginRight; + } + + /** + * @return pixels reserved for space between components + */ + protected int getSpacingSize() { + return 0; + } + + private float getCellHeight(int rowspan) { + int total = ap.getOffsetHeight(); + total -= getMarginHeight(); + total -= getSpacingSize() * (rows - rowspan); + if (total < 0) { + return 0; + } + return total * rowspan / (float) rows; + } + + /** + * + * @return space used by top and bottom margin + */ + private int getMarginHeight() { + return marginBottom + marginTop; + } + + /** + * TODO contains Caption (which is a widget) in a very bad way, cannot be + * simple panel + */ + public class AbsoluteGridCell extends SimplePanel { + + int rowIndex; + int colIndex; + int colSpan = 1; + int rowSpan = 1; + private Element container = DOM.createDiv(); + + private Caption caption; + private AlignmentInfo alignmentInfo = new AlignmentInfo( + AlignmentInfo.ALIGNMENT_TOP + AlignmentInfo.ALIGNMENT_LEFT); + + AbsoluteGridCell(int colIndex, int rowIndex) { + super(); + DOM.appendChild(getElement(), container); + this.rowIndex = rowIndex; + this.colIndex = colIndex; + } + + public void clear() { + super.clear(); + if (caption != null) { + DOM.removeChild(getElement(), caption.getElement()); + caption = null; + } + } + + protected Element getContainerElement() { + return container; + } + + void setColSpan(int s) { + // TODO Should remove possibly collapsing cells + colSpan = s; + } + + void setRowSpan(int s) { + // TODO Should remove possibly collapsing cells + rowSpan = s; + } + + private int getLeft() { + int left = marginLeft; + left += colIndex * getCellWidth(1); + left += getSpacingSize() * colIndex; + return left; + } + + private int getTop() { + int top = marginTop; + top += rowIndex * getCellHeight(1); + top += getSpacingSize() * rowIndex; + return top; + } + + public void render() { + setPixelSize((int) getCellWidth(colSpan), + (int) getCellHeight(rowSpan)); + ap.setWidgetPosition(this, getLeft(), getTop()); + } + + /** + * Does vertical positioning based on DOM values + */ + public void vAling() { + DOM.setStyleAttribute(getElement(), "paddingTop", "0"); + if (!alignmentInfo.isTop()) { + Widget c = getWidget(); + if (c != null) { + + int oh = getOffsetHeight(); + int wt = DOM.getElementPropertyInt(container, "offsetTop"); + int wh = c.getOffsetHeight(); + + int freeSpace = getOffsetHeight() + - (DOM + .getElementPropertyInt(container, + "offsetTop") + c.getOffsetHeight()); + if (Util.isIE()) { + freeSpace -= DOM.getElementPropertyInt(c.getElement(), + "offsetTop"); + } + if (freeSpace < 0) { + freeSpace = 0; // clipping rest of contents when object + // larger than reserved area + } + if (alignmentInfo.isVerticalCenter()) { + DOM.setStyleAttribute(getElement(), "paddingTop", + (freeSpace / 2) + "px"); + } else { + DOM.setStyleAttribute(getElement(), "paddingTop", + (freeSpace) + "px"); + } + } + } + } + + public void setPixelSize(int width, int height) { + super.setPixelSize(width, height); + DOM.setStyleAttribute(container, "width", width + "px"); + int contHeight = height - getCaptionHeight(); + if (contHeight < 0) { + contHeight = 0; + } + DOM.setStyleAttribute(container, "height", contHeight + "px"); + } + + private int getCaptionHeight() { + // remove hard coded caption height + return (caption == null) ? 0 : caption.getOffsetHeight(); + } + + public Caption getCaption() { + return caption; + } + + public void setCaption(Caption newCaption) { + // TODO check for existing, shouldn't happen though + caption = newCaption; + DOM.insertChild(getElement(), caption.getElement(), 0); + } + + public void setAlignment(int bitmask) { + if (alignmentInfo.getBitMask() != bitmask) { + alignmentInfo = new AlignmentInfo(bitmask); + setHorizontalAling(); + // vertical align is set in render() method + } + } + + private void setHorizontalAling() { + DOM.setStyleAttribute(getElement(), "textAlign", alignmentInfo + .getHorizontalAlignment()); + if (getWidget() != null) { + Element el = getWidget().getElement(); + if (alignmentInfo.isHorizontalCenter() + || alignmentInfo.isRight()) { + DOM.setStyleAttribute(el, "marginLeft", "auto"); + } else { + DOM.setStyleAttribute(el, "marginLeft", ""); + } + if (alignmentInfo.isHorizontalCenter() + || alignmentInfo.isLeft()) { + DOM.setStyleAttribute(el, "marginRight", "auto"); + } else { + DOM.setStyleAttribute(el, "marginRight", ""); + } + } + } + } + + public void iLayout() { + boolean sizeChanged = false; + int newWidth = getOffsetWidth(); + if (offsetWidth != newWidth) { + offsetWidth = newWidth; + sizeChanged = true; + } + int newHeight = getOffsetHeight(); + if (offsetHeight != newHeight) { + offsetHeight = newHeight; + sizeChanged = true; + } + if (sizeChanged) { + for (Iterator it = cells.values().iterator(); it.hasNext();) { + AbsoluteGridCell cell = (AbsoluteGridCell) it.next(); + cell.render(); + cell.vAling(); + } + Util.runDescendentsLayout(ap); + } + } + + public int getCols() { + return cols; + } + + public void setCols(int cols) { + this.cols = cols; + // force relayout + offsetHeight = 0; + offsetWidth = 0; + } + + public int getRows() { + return rows; + } + + public void setRows(int rows) { + this.rows = rows; + // force relayout + offsetHeight = 0; + offsetWidth = 0; + } +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/absolutegrid/ISizeableGridLayout.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/absolutegrid/ISizeableGridLayout.java new file mode 100644 index 0000000000..f149fea15d --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/absolutegrid/ISizeableGridLayout.java @@ -0,0 +1,166 @@ +package com.itmill.toolkit.terminal.gwt.client.ui.absolutegrid; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +import com.google.gwt.user.client.ui.Widget; +import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Caption; +import com.itmill.toolkit.terminal.gwt.client.Container; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.ui.MarginInfo; + +/** + * Proto level implementation of GridLayout. + * + * All cell's will be equally sized. + * + */ +public class ISizeableGridLayout extends AbsoluteGrid implements Paintable, + Container { + public static final String CLASSNAME = "i-gridlayout"; + private int spacing; + private HashMap paintableToCellMap = new HashMap(); + private ApplicationConnection client; + + public ISizeableGridLayout() { + super(); + setStyleName(CLASSNAME); + } + + protected int getSpacingSize() { + return spacing; + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + this.client = client; + + if (client.updateComponent(this, uidl, false)) { + return; + } + + if (uidl.hasAttribute("caption")) { + setTitle(uidl.getStringAttribute("caption")); + } + int row = 0, column = 0; + + final ArrayList oldCells = new ArrayList(); + for (final Iterator iterator = getCellIterator(); iterator.hasNext();) { + oldCells.add(iterator.next()); + } + clear(); + + setCols(uidl.getIntAttribute("w")); + setRows(uidl.getIntAttribute("h")); + + handleMargins(uidl); + spacing = uidl.getBooleanAttribute("spacing") ? detectSpacingSize() : 0; + + final int[] alignments = uidl.getIntArrayAttribute("alignments"); + int alignmentIndex = 0; + + for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { + final UIDL r = (UIDL) i.next(); + if ("gr".equals(r.getTag())) { + column = 0; + for (final Iterator j = r.getChildIterator(); j.hasNext();) { + final UIDL c = (UIDL) j.next(); + if ("gc".equals(c.getTag())) { + + // Set cell width + int colSpan; + if (c.hasAttribute("w")) { + colSpan = c.getIntAttribute("w"); + } else { + colSpan = 1; + } + + // Set cell height + int rowSpan; + if (c.hasAttribute("h")) { + rowSpan = c.getIntAttribute("h"); + } else { + rowSpan = 1; + } + + final UIDL u = c.getChildUIDL(0); + if (u != null) { + final Paintable child = client.getPaintable(u); + AbsoluteGridCell cell = getCell(column, row); + paintableToCellMap.put(child, cell); + cell.rowSpan = rowSpan; + cell.colSpan = colSpan; + + oldCells.remove(cell); + + cell.setAlignment(alignments[alignmentIndex++]); + + cell.render(); + + cell.setWidget((Widget) child); + + if (!u.getBooleanAttribute("cached")) { + child.updateFromUIDL(u, client); + } + + cell.vAling(); + } + column += colSpan; + } + } + row++; + } + } + + // loop oldWidgetWrappers that where not re-attached and unregister them + for (final Iterator it = oldCells.iterator(); it.hasNext();) { + final AbsoluteGridCell w = (AbsoluteGridCell) it.next(); + client.unregisterPaintable((Paintable) w.getWidget()); + w.removeFromParent(); + paintableToCellMap.remove(w.getWidget()); + } + + } + + protected void handleMargins(UIDL uidl) { + final MarginInfo margins = new MarginInfo(uidl + .getIntAttribute("margins")); + // TODO build CSS detector to make margins configurable through css + marginTop = margins.hasTop() ? 15 : 0; + marginRight = margins.hasRight() ? 15 : 0; + marginBottom = margins.hasBottom() ? 15 : 0; + marginLeft = margins.hasLeft() ? 15 : 0; + } + + private int detectSpacingSize() { + // TODO Auto-generated method stub + return 15; + } + + public boolean hasChildComponent(Widget component) { + if (paintableToCellMap.containsKey(component)) { + return true; + } else { + return false; + } + } + + public void replaceChildComponent(Widget oldComponent, Widget newComponent) { + // TODO Auto-generated method stub + + } + + public void updateCaption(Paintable component, UIDL uidl) { + AbsoluteGridCell cell = (AbsoluteGridCell) paintableToCellMap + .get(component); + Caption c = cell.getCaption(); + if (c == null) { + c = new Caption(component, client); + cell.setCaption(c); + } + c.updateCaption(uidl); + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/IRichTextArea.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/IRichTextArea.java new file mode 100644 index 0000000000..f0ea0c07d8 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/IRichTextArea.java @@ -0,0 +1,111 @@ +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.itmill.toolkit.terminal.gwt.client.ui.richtextarea; + +import com.google.gwt.user.client.ui.ChangeListener; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusListener; +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.itmill.toolkit.terminal.gwt.client.ApplicationConnection; +import com.itmill.toolkit.terminal.gwt.client.Paintable; +import com.itmill.toolkit.terminal.gwt.client.UIDL; +import com.itmill.toolkit.terminal.gwt.client.ui.Field; + +/** + * This class represents a basic text input field with one row. + * + * @author IT Mill Ltd. + * + */ +public class IRichTextArea extends Composite implements Paintable, Field, + ChangeListener, FocusListener { + + /** + * The input node CSS classname. + */ + public static final String CLASSNAME = "i-richtextarea"; + + protected String id; + + protected ApplicationConnection client; + + private boolean immediate = false; + + private RichTextArea rta = new RichTextArea(); + + private RichTextToolbar formatter = new RichTextToolbar(rta); + + private HTML html = new HTML(); + + private final FlowPanel fp = new FlowPanel(); + + private boolean enabled = true; + + public IRichTextArea() { + fp.add(formatter); + + rta.setWidth("100%"); + rta.addFocusListener(this); + + fp.add(rta); + + initWidget(fp); + setStyleName(CLASSNAME); + + } + + public void setEnabled(boolean enabled) { + if (this.enabled != enabled) { + rta.setEnabled(enabled); + if (enabled) { + fp.remove(html); + fp.add(rta); + } else { + html.setHTML(rta.getHTML()); + fp.remove(rta); + fp.add(html); + } + + this.enabled = enabled; + } + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + this.client = client; + id = uidl.getId(); + + if (uidl.hasVariable("text")) { + rta.setHTML(uidl.getStringVariable("text")); + } + setEnabled(!uidl.getBooleanAttribute("disabled")); + + if (client.updateComponent(this, uidl, true)) { + return; + } + + immediate = uidl.getBooleanAttribute("immediate"); + + } + + public void onChange(Widget sender) { + if (client != null && id != null) { + client.updateVariable(id, "text", rta.getText(), immediate); + } + } + + public void onFocus(Widget sender) { + + } + + public void onLostFocus(Widget sender) { + final String html = rta.getHTML(); + client.updateVariable(id, "text", html, immediate); + + } + +} diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/RichTextToolbar$Strings.properties b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/RichTextToolbar$Strings.properties new file mode 100644 index 0000000000..363b704584 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/RichTextToolbar$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/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/RichTextToolbar.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/RichTextToolbar.java new file mode 100644 index 0000000000..4d3d11d6c9 --- /dev/null +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/RichTextToolbar.java @@ -0,0 +1,509 @@ +/* + * 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.itmill.toolkit.terminal.gwt.client.ui.richtextarea; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.i18n.client.Constants; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.AbstractImagePrototype; +import com.google.gwt.user.client.ui.ChangeListener; +import com.google.gwt.user.client.ui.ClickListener; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.ImageBundle; +import com.google.gwt.user.client.ui.KeyboardListener; +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; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; + +/** + * A 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 RichTextToolbar extends Composite { + + /** + * This {@link ImageBundle} is used for all the button icons. Using an image + * 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 ImageBundle { + + /** + * @gwt.resource bold.gif + */ + AbstractImagePrototype bold(); + + /** + * @gwt.resource createLink.gif + */ + AbstractImagePrototype createLink(); + + /** + * @gwt.resource hr.gif + */ + AbstractImagePrototype hr(); + + /** + * @gwt.resource indent.gif + */ + AbstractImagePrototype indent(); + + /** + * @gwt.resource insertImage.gif + */ + AbstractImagePrototype insertImage(); + + /** + * @gwt.resource italic.gif + */ + AbstractImagePrototype italic(); + + /** + * @gwt.resource justifyCenter.gif + */ + AbstractImagePrototype justifyCenter(); + + /** + * @gwt.resource justifyLeft.gif + */ + AbstractImagePrototype justifyLeft(); + + /** + * @gwt.resource justifyRight.gif + */ + AbstractImagePrototype justifyRight(); + + /** + * @gwt.resource ol.gif + */ + AbstractImagePrototype ol(); + + /** + * @gwt.resource outdent.gif + */ + AbstractImagePrototype outdent(); + + /** + * @gwt.resource removeFormat.gif + */ + AbstractImagePrototype removeFormat(); + + /** + * @gwt.resource removeLink.gif + */ + AbstractImagePrototype removeLink(); + + /** + * @gwt.resource strikeThrough.gif + */ + AbstractImagePrototype strikeThrough(); + + /** + * @gwt.resource subscript.gif + */ + AbstractImagePrototype subscript(); + + /** + * @gwt.resource superscript.gif + */ + AbstractImagePrototype superscript(); + + /** + * @gwt.resource ul.gif + */ + AbstractImagePrototype ul(); + + /** + * @gwt.resource underline.gif + */ + AbstractImagePrototype 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 EventListener class to avoid exposing event methods on + * the RichTextToolbar itself. + */ + private class EventListener implements ClickListener, ChangeListener, + KeyboardListener { + + public void onChange(Widget sender) { + 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); + } + } + + public void onClick(Widget sender) { + 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(); + } + } + + public void onKeyDown(Widget sender, char keyCode, int modifiers) { + } + + public void onKeyPress(Widget sender, char keyCode, int modifiers) { + } + + public void onKeyUp(Widget sender, char keyCode, int modifiers) { + 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(); + } + } + } + + 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 EventListener listener = new EventListener(); + + private final RichTextArea richText; + private final RichTextArea.BasicFormatter basic; + private final RichTextArea.ExtendedFormatter extended; + + private final VerticalPanel outer = new VerticalPanel(); + private final HorizontalPanel topPanel = new HorizontalPanel(); + private final HorizontalPanel bottomPanel = new HorizontalPanel(); + 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 + */ + public RichTextToolbar(RichTextArea richText) { + this.richText = richText; + basic = richText.getBasicFormatter(); + extended = richText.getExtendedFormatter(); + + outer.add(topPanel); + outer.add(bottomPanel); + topPanel.setWidth("100%"); + bottomPanel.setWidth("100%"); + + 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 listeners for updating status, so don't hook + // them up + // unless at least basic editing is supported. + richText.addKeyboardListener(listener); + richText.addClickListener(listener); + } + } + + private ListBox createColorList(String caption) { + final ListBox lb = new ListBox(); + lb.addChangeListener(listener); + 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"); + return lb; + } + + private ListBox createFontList() { + final ListBox lb = new ListBox(); + lb.addChangeListener(listener); + lb.setVisibleItemCount(1); + + lb.addItem(strings.font(), ""); + lb.addItem(strings.normal(), ""); + 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"); + return lb; + } + + private ListBox createFontSizes() { + final ListBox lb = new ListBox(); + lb.addChangeListener(listener); + 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()); + return lb; + } + + private PushButton createPushButton(AbstractImagePrototype img, String tip) { + final PushButton pb = new PushButton(img.createImage()); + pb.addClickListener(listener); + pb.setTitle(tip); + return pb; + } + + private ToggleButton createToggleButton(AbstractImagePrototype img, + String tip) { + final ToggleButton tb = new ToggleButton(img.createImage()); + tb.addClickListener(listener); + tb.setTitle(tip); + return tb; + } + + /** + * Updates the status of all the stateful buttons. + */ + 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/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/backColors.gif b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/backColors.gif new file mode 100644 index 0000000000000000000000000000000000000000..ddfc1cea2c7c12f2319b63b8046a919fa77f49d3 GIT binary patch literal 104 zcmZ?wbhEHb6k!lyn8*ME|NsC0`ua5{5L|quPwsyt%ghOjPfSqupZ3S&(!$fz4U$jI@ib6i Gum%9=@*aBt literal 0 HcmV?d00001 diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/createLink.gif b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/createLink.gif new file mode 100644 index 0000000000000000000000000000000000000000..3ab9e599f822b6fd9d2886ee4f06491b49ecda8f GIT binary patch literal 118 zcmZ?wbhEHb6k!lySi}GXXA*Os*4ID%j|vojvM@3*urTO=lz`MSFk5@Yu-R&bl)z_uT!D z1QdU=FfuUkGw6VHg3MrGarki3bM;<}*SQV<8x~Y{Gf7-(Ok!0iJvGg5UJR>3qWv?k m`Weg$i7OL18jkjvMRnT7#h)yU3=I4XIv|}O zGZ=us literal 0 HcmV?d00001 diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/gwtLogo.png b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/gwtLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..80728186d8cadc88b774cdbd1278a6ba02035ba0 GIT binary patch literal 11454 zcmV;vEJ4$WP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=00004XF*Lt006O$eEU(80000WV@Og>004R=004l4008;_004mL004C` z008P>0026e000+nl3&F}001G`Nkl=0}x4t9*=w1@WU#I!pJo%V8fhE8H99j8}1O0rgl<#>7Ow&R4h4Tc14uw!h@ z3<6nL0}>jmRI2%ncRc6xANRhht5;Q;J05b^I;#p%_3nM&+xNGBd+&2g7-RTJIIi4w zZ2`jwLaetec^N`pDulH`!3JX(5SUqE^s~a~&%E&TlMntdu2K9r!Mt0qW6*pBXl?|1y%DQ5yyt-WO>UG?l<%cm%FQDZIG(8{R5AcG3 zFbE0bh$xB)!-z1BP)ZTUG5JD)fvy6?&5!MR^6}sMHw*jnYqs_e4XyZ#D>iO?RexXq zU{|G5$`?v#1hqz!6H_xxP0unjSEE|1(P*~tgMcWCh~k(ij)|2*DUCK71V~FNAw{fW zokJUpG4%EHP$^m`t~z||9cw3RD`QJ;1&#p^ZuiFb|HQ+-{F<%S##dZ+|GVDxOK+|= zTI_!5Wey)b#_V#we`QDJ9M;U|=46f*7>)5DXKRzo%DG)*+AOM~Q?gk#%?v20n6A63E4X=Fn zt8ae&7oL1!4~LE(#q)i%F&G2J7}{4UC0Le)@G;Lqpa0)U9sBv$gUj| zcdXvvl>O+Uf*);hV)aT}>239LZu3`$270T3Mi@=8tBdt6ylT%uibB%Tnoubu0$U1z z5O@N(2Y3M3yWJZ%KM}BReBB$r*fp^Fj$M0SVrFg*Ap}wiY)cx)v8EhH?sYA>D=&Gz zsP%JQQT*Z~-`~*!_~5|0?t&j~bz^m-)aC|Ys}^FV5eNk+W3Y|k@l}I7C0+8`U`av$ zU>_fD=*`E*jx#Yc zL(aAs?#cbHBK&n%5FUHv`#VB_e?PF!E(FmPZlrFsw7vn@qJ_BB2#Oj|fHnpJ*iuq* zZ2Dc7m99&xtH9sb7W>1PAX0So_Hs*J@cT6%OC(Yf7{gQ;aHQpPwAEtT4~R|%9{|q) z_W@tq?v3yMv4ef}t8e*4zBKUBmgjS5bQCECmzU&kY(045?+QWm2C2=>z&0bq64jn7@PXx(05GY(__Kr4rWc9CKO|A~4#Jix7x?+A$C^e+94VZP9FWS15ul|w`wQ0^ltyTSuWaO^G@I`3`u4p63NvPyp z1`2srPTX3IdEb_DNxMDnvaikV@aDg9iQQjnE+#wXf@&-FI2aq zRwu<$K8NV(!t{3$^_B6;HnpHheQuh@^bG#YEOE7#NQp>n<0wdzymT zYK$cq12%8>#%C^6WN*b#iEY8F*B7|h>ZhO_Tt5KYLP?1RVj#k#?!e$Eow@@}K9z*F z9P|U9s!ie=$QkhDIY3q#Vg<$^9S2z{!9X{9phD1{rZj1*wZ2=Mx}&vHRv|CBKX#}Qn1NtxB}|1eX>W(c0! zgL&pfM7@FSxa1?9x`QN%*>NU`2{4G0m)_Ni;i$4g&-RiG`3BwsmeOj1ahfgcWPFE$Fcgt}9W#LI~b@Az^P( zM$qHL^jv%?gKvE=<%z@0ZP|-`%XK)Pe-Qg$p2u<=L~6OtB#G%$Wi+KSQEMZ6Du|H* z%t#+$Pl>tw)p8Q7=<^cOTeS6v4{p?8?V)jhC!E&AfML@Vz> zeeofTV}m8nK8eLDk%iPD$dv<#i-yo60|ebUnmVL5H$(l{5gI#>5>8B`f);7|#9$Ce z3Vp*A*RLnHY6QDd0V&a*M>IW+fAkpsu~DMd943u+DRXBIUO>JT;+1m%^yOgEmJsVc z1h)d8I^W3NfMJahg25h_(#9=d6a*edYfSebyrP8t+XFDrjryxECl@c}vc49jA&Qg4 zEXCfa!<7$>e?iMDyTYz{0X}XCx&~5MxIQ5joV4xM&!2@i0+;8IPFi^%{ocQurs6F!>(c~1y7@WRd%2#e;@U~m&zTtXACJ0ok#pKuS z=g^;Sr}pfQMdw(Oi8j>xiZr|P*v7EmmrTq=Sc0+b-uS9>itM)b0$a2(6fDKcjT@n7 z5FL1+VlXLar9eecEFiAEB7qx5h(ZoI(1TnvgjhF>S~Eyc$Pi}lW=m1>a$NGOgppZh<&_@O(Ay*jqYV_U^#muV?*7 z|0|dL!S5nnmx+76#@^rj2m<$vKm#ay;qD0aJnhK~;YK!S0lg)Q(a|csDuTET+a$o# zB`iVX!~_$;6pbUts6TiBe{`HUXaYI`D{L&wNsZrG+G^FJ@0B-k&E5Zi-Cbeqe}9qh zzwdp?{jeP5g$Tm3iWn8M{}X@6z^!k>E|vNId*6qSLgXp#r1l{JNz(7}1BGi#A~k;( zOeHpDn za)T?`{JBqKcUNfa+{Nw>e;5#F1Zu$sYATmqNptsJ4t)IM#I-7Nnd=iNT(luDh87a9 zSU~mn(KA@4bz+8C#ckNfvmeSijnjZB4AcX->8c9*KJzz>?KzBeMWWO5D@M1P^j`f6 z)_>?%(n+`tQk*yFq$rKq@P{9xxZzR&_WjQ95Y}eT3Uo3^cJYoJ<;6RHm0)xX*&$yN zu#5qvXeq^UY>xKzP~UtprCTm%-Sz8Ox4KL@@<1yx@;!D&ku8?iDv`d=X#w~VM1IJ1 z12uMj_A~VSmtUtSW3)=-#AuCC8Z$S;>R_{w@G)VC6fQ{8>e67+=xrit74n$D3SGn9jCAKI1p(G{onY4_tvQeJ z!4kL@fw0ipEQ)OVYyfAz7r>MR!WjHWk!#l3z;hgY_}grF!%a9UKq;N*w^kS(lkYCF z=2t(!?vH%roNuYlf^zkTeg$DCZ{zqs-iS_%jvgEEPM@18lq z;PYQ&V&g^z`-|Abn6}?G8a+F~iZ|ZG;nf!t96N?M2Pj#sF0Q_q6>q&Q0UyWAe)~Jf zv-lVVV5QOP8$%6AE1!oI-IRtZtnA6rtrc>jMzHG;-b{7y-k06Q-kp|b1L;;kZe*!i=%;fcNV}%`JNiZt0gC-8i z4ciRd`ZFB)ZR9pQ=O#$^*3?slb=i^Mx1-1?7iit`R{|V zdDcg70yh9VDB+n(+wu##nF2@J=2z)pReWnm-*MX^NDqsC1;eYrTV>5MD^?*^S6|But z*|?sX3gQsAAn3aKYFdZ(r7w4uiP326N`>6W$bv*f z3JdU+qKc%J%cJ`%R7NVS9W2qK74CGMaNjYS6Llt|C`oNXA}kB*d@{8Fm3B0B2khP6IOolZE;8uEXegi=Z9@~5aexr)S zK}w0R>@y#{7=zI-$&zxoy!J=ks^ugbeDiyC}>7IVLPdWlv3c8 z6t=qrW|J+=)ej-#G@jNI$qz$L3n?A`BUn>xi;?g#V1*62nHp-Y$rZn~9d~#L05^)rty@dDYbWw7Cqhd3 z2w{;QT1j!kC6uq+MAy|<(Y5s&ikEF*_np5^ZO2oKO89mwRY`%7DeTk-q_B^4!j|dR zTnf3poD68uVG)z8i}76``G#X%QT{x!w9M}kLmBCcTErL z2TKe{L!sUzIyO%Ic$MQ#9}S6fkP=(wP^v|1{{hNZZkj*Uz?pFRA*r$ zPIcKnrYQsjQDS#o3zU#cwbbC*r{IcB#Bs#x5>&@qe6Kdes++d3vA;lf5F#e#XzduM zKG|Se#mOFyRC}$?rV%jr#1pLexwmFu|1`yQm*O2dc-lsn(Tc)F>xi38p8TnsX&%^1 zjH0}LeKKKcU%KO*wxCfNj~!~!E@@=5OzK-(G-oW(N9-{TU&v-pkSV)O=8fzwix$i!9@@cxe8Tohb;iZrL z{%NH|ST_FABQ*bhUovu&F1C&lfk8(RvXIDzEEX09(#u5mN!8;*a-7K)H0_+!-n7(S zZQ!Sc-ZHv@fLIxVbTXlYT-YC-nT8O@u=9mvMj1zle34qc!0|)lv>E|e7M5ivCGwf{ zpUG2u_BrZ1zkkxdul?|Uq`2-9wDQluuuCkfm{hSsAP^`&KnLN%TrpojoaUp`X+kzY zs7@+qf=<}U?yzJq8K&Q>4De;4*HI0DK&7zTCA&!;b36HP95}EaW~Y+RGY+sz#YC&l zVUQp!NfgvM{>3ky^zZKA0I&GOCot%w<9N2!X%wM-Zvjv)cgp4#0+gNTnmUEY&cY4> zKNa<$1NgE>)94l*CA&#W_9b<@UWWq*l9!DFto->65G)+VKKpt6V<*{JhJW#PF8Qth zPHgGM=r@-zLf3D!VXf{t@LYnCX&)J+SdYQ zbkg}-DB0zFN?s%<`}ZeP`1t%`3(LVfag2k1@;?_(qyOj+*zn(e7j690ONvSQ<1qm} z*WEyVcx9pk|MUR6KlE!D6`!=PL}-(=X&sA3PXXmf1g5i~Id&@8EufhJe%k168trRL zgfV#0!iIFcl#H|!<@gDhnT9w-=3LJAutc6CfAu#^+TkLJ(Qnb*vyW%rcq`A`b{n%#eJ`m*jX`LG5#X0y z<_6154_9dP7Un&+BHfp?jEvan&xC-N!fs^Bbvt7L7y0ZKI*g6+r4e~AS{PNSzY5D4zCA z5@aUr>m;W-4E)-5Z+uA^F&W@~26k}?Ia#W(X>=h8W70XZq2}i44gwsd<{yPSNlF?G zI@>>1*19kh0k*QAODe_ZP!w{{#x>@S0P*%RjuJ2JV@w;yGW>?|6~|EP=1#A&vQ5WXywq8%#F0FO{<9 z=ckFBL>X7oUb&2%EQ;S}Y;nS(u*ee|DTrJPzvL1WT$Gc@gkT|xBLmYxA``X1 z5bK2~R>HLAjDGy64)1v?Zhog>RC~^N+N#^`*z?jE-EB$9XZqVR>rD14e{_{}zkZcf zxV2V|d3c}Tt(zPi40E|WITh3Ic_()RZ3pwxi-=bSr>@Q}M4+ecAhsnz(Zw&i#IBX} zpH1@6loW}HXv8q(8D@P=5R*Q;2~wavqxX-g=%E)>>ye{6JOHqu7!JMF+3s5*D0f0^X7Ad<}DfVJb~_DwFIY%*>RIpV*`PufL#LKhO)(>SeyR zVV@M)LWo6Gr@d(J%hJ(ZJXLf9+!xM0ct@^(phsF4?R+_;zm(^iA@E~3Ug~14s^Y3R zkrXL0)mg;cBr=yr$I*Ft_saZq%K9cxjtQ!D_U^9^A}CWIiY8};H56+ZlI z+s0^z#QFy|e`+Li<*7r*2Z=Gvb4gc<>l=sa&%6~b9nq5JrQP&2nI$9cLVK!k-so6_Kx7VTFZD%4g z&SW0=n2zQiiyHU7#_4*u@|E??-4So!;?iXsT6U5!M;i6yAyQ+Kp{#A6yG+RB`SGCS z5)_U)I(=j> zZpq9$lPIIzjm*}D?3T4ohaz53t(OO+{m^Es@aAeY=Hb19w_K4V%xW%=s}uuX6Qec4 zYQr{Xm+Tyipy;H@&c>uN(N1yhIT-z!O8 zyWT>_hN(h{oQkP51EeKR8`<+RVF`kA;wHlULb4OaBqtY1<9^Q9OnHWSl$6tgL?&WP zbyBOxU()_V&&93pD@-e+!C9H;9Hg9a6rb+NJ3IS>%V@Ws0osPzmYPClr&M+l&;3#L zfjiv3!HTrjyzp{Je<{b+LjpfGjF-B|c}*NZ zilrcK8og&!g%3RwH@`b+;^}3yIO>Sv&In!-^4UJtXm|c{26)zYw=%C;cNjr*_BMAy_M+0af*^%~Q zX8lAe8ZqhgJ&EEFbK-=Kzw-m-ePfsM_Ais@I+L9~6PY-lzhYsZx}`o3whh{J=y`j! zt>H<~M|C*;c+|M(W~b}janMef4rSZmSvHeeV>vd0#Crw#B!Yv2gI+4xX+t$INq-`M zFq7;A_*J8J9MQqUPsOdr8%9-^Np|rPZsLN-#QDN*C(BOwVP?AHW%dZRqkJCv4%ON> zAnk`XS%o*x&Bc6kui&;#4z_?=qeiWqqgBWg_H_}t3k{%b6t@D+jAxh*prsZnJ4=9X z^r3Mbf9r*~^{s{dxgDX z#CEnGIv(*@NpQ<5P>m{He;4)MBGOH!Au>&NQEZs;HPaqc<8%{{Nc$6*+O$!RzpR3X zpN^Z)M<*w{AX9jp%C22-nK+-wW{FaMIpAK#lb14U9{T;!+`aE|`v$wDwdVPkBl;ZJ z(ws)xHqvNp4B8l)sSeKs1~2`qE>Z}jK(&nCJEp^L>`<-mjOl1%3An*BMaNRJ)PFU| z8E0jdnP7<}D`HN5QlY&I*3AqYvaqvTkn?M$JNy>6=T1wDF3W|Vxqg6FO)xh)!CWAi z4h;1;*%BZ`a)JmmHKpT6_9*`wFR0d@jtE|=s*8VEGI7qZmjS*MbSncr$HJ!i?4Iv7 z^!#;p>FOKp@&~m>mh;eM6f=<|NcRfL*%q>9Tn;6ijE&U$mb3F z9Gy#ciH??THhQzZ{>Mfp{#9U~g>%U+v4nrme&r_x@&$)|mhvK&D4b3S_{SM?`}ltY Y0R3Q}1uh($07*qoM6N<$f@Yup5C8xG literal 0 HcmV?d00001 diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/hr.gif b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/hr.gif new file mode 100644 index 0000000000000000000000000000000000000000..3fb1607e67a5e8f308167630d8ce6aa07b4c437a GIT binary patch literal 67 zcmZ?wbhEHb6k!lyXkY+=|Ns9h{$ycfU|?j>0r5dH3`|lz{VPwuR!2?&mzW?^W@8I8w%lGX&eqsOpziUrE`1145-~WF;e0u-t z{CS{(V1q$~;!hSv1_l`h9S{d(Cj;wz1@*p^%y}7ALM;pWj+k*o`>ZiMo7LO8QLV9I z!9)*%R?d{B3pqY5Y=?{j!&Xo0S?yvFv%k~(8iQ($u9#Cx;lV3DZEj4mJ9RX;IbMee zC8%+i^E7e^2uek(FiDjQaI^CW@V9oPmI(+{Oy=U}^;b1zojr+bDWAB5iVoY-WplX2 lgjO1`aIR(NQUt$*tD86`yqYXDTQdhGxJ literal 0 HcmV?d00001 diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/italic.gif b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/italic.gif new file mode 100644 index 0000000000000000000000000000000000000000..2b0a5a0a062ac0b66ea119a18243415205e88bf2 GIT binary patch literal 79 zcmZ?wbhEHb6k!lyn8*ME)z#H4EiM25|5yCU!pOkD%%B4j1<5loDa`3#dHOB?VwVNi eELR0r5dH3{0{;{VPwu0r5dH3`}x8{VPwu literal 0 HcmV?d00001 diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/justifyRight.gif b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/justifyRight.gif new file mode 100644 index 0000000000000000000000000000000000000000..99ee25836f15352e94799df649b6594e0bd284f2 GIT binary patch literal 855 zcmZ?wbhEHb6k!ly_|C{M3PwX0r5dH3{1*B{VPwuuqdo{+k&8 z4+{EkZ2VtP@V|=ke`n|aCMNs$?>}?q3}!Kl`L*G~#Ze2%I%J8H`KTrF9Y zq;-OyHCVLHnLn7h*(p{)uiP4$CLo%3i0^>vmSq@hw&d}+qLUIhebDepVSFc&C myljQDROl8Jd1*ccL6=qQ)-WiFs0z!7HS;-(cepz;SOWkbh-TXW literal 0 HcmV?d00001 diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/removeLink.gif b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/removeLink.gif new file mode 100644 index 0000000000000000000000000000000000000000..522ab4b29eef84c2952fc7920ae530f75d6b21b0 GIT binary patch literal 895 zcmZ?wbhEHb6k!ly_|5*%Sk1L5s7kfmD#h%)-GPs42k--`O Ds0r5dH3{2`h{VPwu0r5dH3{2`h{VPwu-QUo{A>o~UEjLcP evP<1bcw6i3?QDu(b2m7j^NHN{>zq9+gEatULm7zx literal 0 HcmV?d00001 diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/ul.gif b/src/com/itmill/toolkit/terminal/gwt/client/ui/ui/richtextarea/ul.gif new file mode 100644 index 0000000000000000000000000000000000000000..01380dbb833be5fd96a1bad898d72fbdeadc1455 GIT binary patch literal 863 zcmZ?wbhEHb6k!ly_|C{M3PwX|Nl;ximB{CakA kLiC)*d5?R2IVUAmF*ORz&=tLUs`d7-bKmy`FfdpH03G5Xz5oCK literal 0 HcmV?d00001 -- 2.39.5