--- /dev/null
+/*
+@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("<div>");
+ if (getIconUrl() != null) {
+ sb.append("<img src=\"" + getIconUrl() + "\" alt=\"icon\" />");
+ }
+ sb.append(getCaption());
+ sb.append("</div>");
+ 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;
+ }
+}
--- /dev/null
+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();
+
+}
--- /dev/null
+/*
+@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";
+ }
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.Date;\r
+\r
+import com.itmill.toolkit.terminal.gwt.client.DateTimeService;\r
+\r
+public class CalendarEntry {\r
+ private final String styleName;\r
+ private Date start;\r
+ private Date end;\r
+ private String title;\r
+ private String description;\r
+ private boolean notime;\r
+\r
+ public CalendarEntry(String styleName, Date start, Date end, String title,\r
+ String description, boolean notime) {\r
+ this.styleName = styleName;\r
+ if (notime) {\r
+ Date d = new Date(start.getTime());\r
+ d.setSeconds(0);\r
+ d.setMinutes(0);\r
+ this.start = d;\r
+ if (end != null) {\r
+ d = new Date(end.getTime());\r
+ d.setSeconds(0);\r
+ d.setMinutes(0);\r
+ this.end = d;\r
+ } else {\r
+ end = start;\r
+ }\r
+ } else {\r
+ this.start = start;\r
+ this.end = end;\r
+ }\r
+ this.title = title;\r
+ this.description = description;\r
+ this.notime = notime;\r
+ }\r
+\r
+ public CalendarEntry(String styleName, Date start, Date end, String title,\r
+ String description) {\r
+ this(styleName, start, end, title, description, false);\r
+ }\r
+\r
+ public String getStyleName() {\r
+ return styleName;\r
+ }\r
+\r
+ public Date getStart() {\r
+ return start;\r
+ }\r
+\r
+ public void setStart(Date start) {\r
+ this.start = start;\r
+ }\r
+\r
+ public Date getEnd() {\r
+ return end;\r
+ }\r
+\r
+ public void setEnd(Date end) {\r
+ this.end = end;\r
+ }\r
+\r
+ public String getTitle() {\r
+ return title;\r
+ }\r
+\r
+ public void setTitle(String title) {\r
+ this.title = title;\r
+ }\r
+\r
+ public String getDescription() {\r
+ return description;\r
+ }\r
+\r
+ public void setDescription(String description) {\r
+ this.description = description;\r
+ }\r
+\r
+ public boolean isNotime() {\r
+ return notime;\r
+ }\r
+\r
+ public void setNotime(boolean notime) {\r
+ this.notime = notime;\r
+ }\r
+\r
+ public String getStringForDate(Date d) {\r
+ // TODO format from DateTimeService\r
+ String s = "";\r
+ if (!notime) {\r
+ if (!DateTimeService.isSameDay(d, start)) {\r
+ s += (start.getYear() + 1900) + "." + (start.getMonth() + 1)\r
+ + "." + start.getDate() + " ";\r
+ }\r
+ int i = start.getHours();\r
+ s += (i < 10 ? "0" : "") + i;\r
+ s += ":";\r
+ i = start.getMinutes();\r
+ s += (i < 10 ? "0" : "") + i;\r
+ if (!start.equals(end)) {\r
+ s += " - ";\r
+ if (!DateTimeService.isSameDay(start, end)) {\r
+ s += (end.getYear() + 1900) + "." + (end.getMonth() + 1)\r
+ + "." + end.getDate() + " ";\r
+ }\r
+ i = end.getHours();\r
+ s += (i < 10 ? "0" : "") + i;\r
+ s += ":";\r
+ i = end.getMinutes();\r
+ s += (i < 10 ? "0" : "") + i;\r
+ }\r
+ s += " ";\r
+ }\r
+ if (title != null) {\r
+ s += title;\r
+ }\r
+ return s;\r
+ }\r
+\r
+}
\ No newline at end of file
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.Date;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.Event;\r
+import com.google.gwt.user.client.Timer;\r
+import com.google.gwt.user.client.ui.ClickListener;\r
+import com.google.gwt.user.client.ui.FlexTable;\r
+import com.google.gwt.user.client.ui.MouseListener;\r
+import com.google.gwt.user.client.ui.MouseListenerCollection;\r
+import com.google.gwt.user.client.ui.SourcesMouseEvents;\r
+import com.google.gwt.user.client.ui.SourcesTableEvents;\r
+import com.google.gwt.user.client.ui.TableListener;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.DateTimeService;\r
+import com.itmill.toolkit.terminal.gwt.client.LocaleService;\r
+\r
+public class CalendarPanel extends FlexTable implements MouseListener,\r
+ ClickListener {\r
+\r
+ private final IDateField datefield;\r
+\r
+ private IEventButton prevYear;\r
+\r
+ private IEventButton nextYear;\r
+\r
+ private IEventButton prevMonth;\r
+\r
+ private IEventButton nextMonth;\r
+\r
+ private Time time;\r
+\r
+ private Date minDate = null;\r
+\r
+ private Date maxDate = null;\r
+\r
+ private CalendarEntrySource entrySource;\r
+\r
+ /* Needed to identify resolution changes */\r
+ private int resolution = IDateField.RESOLUTION_YEAR;\r
+\r
+ /* Needed to identify locale changes */\r
+ private String locale = LocaleService.getDefaultLocale();\r
+\r
+ public CalendarPanel(IDateField parent) {\r
+ datefield = parent;\r
+ setStyleName(IDateField.CLASSNAME + "-calendarpanel");\r
+ // buildCalendar(true);\r
+ addTableListener(new DateClickListener(this));\r
+ }\r
+\r
+ public CalendarPanel(IDateField parent, Date min, Date max) {\r
+ datefield = parent;\r
+ setStyleName(IDateField.CLASSNAME + "-calendarpanel");\r
+ // buildCalendar(true);\r
+ addTableListener(new DateClickListener(this));\r
+\r
+ }\r
+\r
+ private void buildCalendar(boolean forceRedraw) {\r
+ final boolean needsMonth = datefield.getCurrentResolution() > IDateField.RESOLUTION_YEAR;\r
+ boolean needsBody = datefield.getCurrentResolution() >= IDateField.RESOLUTION_DAY;\r
+ final boolean needsTime = datefield.getCurrentResolution() >= IDateField.RESOLUTION_HOUR;\r
+ buildCalendarHeader(forceRedraw, needsMonth);\r
+ clearCalendarBody(!needsBody);\r
+ if (needsBody) {\r
+ buildCalendarBody();\r
+ }\r
+ if (needsTime) {\r
+ buildTime(forceRedraw);\r
+ } else if (time != null) {\r
+ remove(time);\r
+ time = null;\r
+ }\r
+ }\r
+\r
+ private void clearCalendarBody(boolean remove) {\r
+ if (!remove) {\r
+ for (int row = 2; row < 8; row++) {\r
+ for (int col = 0; col < 7; col++) {\r
+ setHTML(row, col, " ");\r
+ }\r
+ }\r
+ } else if (getRowCount() > 2) {\r
+ while (getRowCount() > 2) {\r
+ removeRow(2);\r
+ }\r
+ }\r
+ }\r
+\r
+ private void buildCalendarHeader(boolean forceRedraw, boolean needsMonth) {\r
+ if (forceRedraw) {\r
+ if (prevMonth == null) { // Only do once\r
+ prevYear = new IEventButton();\r
+ prevYear.setHTML("«");\r
+ prevYear.setStyleName("i-button-prevyear");\r
+ nextYear = new IEventButton();\r
+ nextYear.setHTML("»");\r
+ nextYear.setStyleName("i-button-nextyear");\r
+ prevYear.addMouseListener(this);\r
+ nextYear.addMouseListener(this);\r
+ prevYear.addClickListener(this);\r
+ nextYear.addClickListener(this);\r
+ setWidget(0, 0, prevYear);\r
+ setWidget(0, 4, nextYear);\r
+\r
+ if (needsMonth) {\r
+ prevMonth = new IEventButton();\r
+ prevMonth.setHTML("‹");\r
+ prevMonth.setStyleName("i-button-prevmonth");\r
+ nextMonth = new IEventButton();\r
+ nextMonth.setHTML("›");\r
+ nextMonth.setStyleName("i-button-nextmonth");\r
+ prevMonth.addMouseListener(this);\r
+ nextMonth.addMouseListener(this);\r
+ prevMonth.addClickListener(this);\r
+ nextMonth.addClickListener(this);\r
+ setWidget(0, 3, nextMonth);\r
+ setWidget(0, 1, prevMonth);\r
+ }\r
+\r
+ getFlexCellFormatter().setColSpan(0, 2, 3);\r
+ getRowFormatter().addStyleName(0,\r
+ IDateField.CLASSNAME + "-calendarpanel-header");\r
+ } else if (!needsMonth) {\r
+ // Remove month traverse buttons\r
+ prevMonth.removeClickListener(this);\r
+ prevMonth.removeMouseListener(this);\r
+ nextMonth.removeClickListener(this);\r
+ nextMonth.removeMouseListener(this);\r
+ remove(prevMonth);\r
+ remove(nextMonth);\r
+ prevMonth = null;\r
+ nextMonth = null;\r
+ }\r
+\r
+ // Print weekday names\r
+ final int firstDay = datefield.getDateTimeService()\r
+ .getFirstDayOfWeek();\r
+ for (int i = 0; i < 7; i++) {\r
+ int day = i + firstDay;\r
+ if (day > 6) {\r
+ day = 0;\r
+ }\r
+ if (datefield.getCurrentResolution() > IDateField.RESOLUTION_MONTH) {\r
+ setHTML(1, i, "<strong>"\r
+ + datefield.getDateTimeService().getShortDay(day)\r
+ + "</strong>");\r
+ } else {\r
+ setHTML(1, i, "");\r
+ }\r
+ }\r
+ }\r
+\r
+ final String monthName = needsMonth ? datefield.getDateTimeService()\r
+ .getMonth(datefield.getShowingDate().getMonth()) : "";\r
+ final int year = datefield.getShowingDate().getYear() + 1900;\r
+ setHTML(0, 2, "<span class=\"" + IDateField.CLASSNAME\r
+ + "-calendarpanel-month\">" + monthName + " " + year\r
+ + "</span>");\r
+ }\r
+\r
+ private void buildCalendarBody() {\r
+ // date actually selected?\r
+ Date currentDate = datefield.getCurrentDate();\r
+ Date showing = datefield.getShowingDate();\r
+ boolean selected = (currentDate != null\r
+ && currentDate.getMonth() == showing.getMonth() && currentDate\r
+ .getYear() == showing.getYear());\r
+\r
+ final int startWeekDay = datefield.getDateTimeService()\r
+ .getStartWeekDay(datefield.getShowingDate());\r
+ final int numDays = DateTimeService.getNumberOfDaysInMonth(datefield\r
+ .getShowingDate());\r
+ int dayCount = 0;\r
+ final Date today = new Date();\r
+ final Date curr = new Date(datefield.getShowingDate().getTime());\r
+ for (int row = 2; row < 8; row++) {\r
+ for (int col = 0; col < 7; col++) {\r
+ if (!(row == 2 && col < startWeekDay)) {\r
+ if (dayCount < numDays) {\r
+ final int selectedDate = ++dayCount;\r
+ String title = "";\r
+ if (entrySource != null) {\r
+ curr.setDate(dayCount);\r
+ final List entries = entrySource.getEntries(curr,\r
+ IDateField.RESOLUTION_DAY);\r
+ if (entries != null) {\r
+ for (final Iterator it = entries.iterator(); it\r
+ .hasNext();) {\r
+ final CalendarEntry entry = (CalendarEntry) it\r
+ .next();\r
+ title += (title.length() > 0 ? ", " : "")\r
+ + entry.getStringForDate(curr);\r
+ }\r
+ }\r
+ }\r
+ final String baseclass = IDateField.CLASSNAME\r
+ + "-calendarpanel-day";\r
+ String cssClass = baseclass;\r
+ if (!isEnabledDate(curr)) {\r
+ cssClass += " " + baseclass + "-disabled";\r
+ }\r
+ if (selected\r
+ && datefield.getShowingDate().getDate() == dayCount) {\r
+ cssClass += " " + baseclass + "-selected";\r
+ }\r
+ if (today.getDate() == dayCount\r
+ && today.getMonth() == datefield\r
+ .getShowingDate().getMonth()\r
+ && today.getYear() == datefield\r
+ .getShowingDate().getYear()) {\r
+ cssClass += " " + baseclass + "-today";\r
+ }\r
+ if (title.length() > 0) {\r
+ cssClass += " " + baseclass + "-entry";\r
+ }\r
+ setHTML(row, col, "<span title=\"" + title\r
+ + "\" class=\"" + cssClass + "\">"\r
+ + selectedDate + "</span>");\r
+ } else {\r
+ break;\r
+ }\r
+\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ private void buildTime(boolean forceRedraw) {\r
+ if (time == null) {\r
+ time = new Time(datefield);\r
+ setText(8, 0, ""); // Add new row\r
+ getFlexCellFormatter().setColSpan(8, 0, 7);\r
+ setWidget(8, 0, time);\r
+ }\r
+ time.updateTime(forceRedraw);\r
+ }\r
+\r
+ /**\r
+ * \r
+ * @param forceRedraw\r
+ * Build all from scratch, in case of e.g. locale changes\r
+ */\r
+ public void updateCalendar() {\r
+ // Locale and resolution changes force a complete redraw\r
+ buildCalendar(locale != datefield.getCurrentLocale()\r
+ || resolution != datefield.getCurrentResolution());\r
+ if (datefield instanceof ITextualDate) {\r
+ ((ITextualDate) datefield).buildDate();\r
+ }\r
+ locale = datefield.getCurrentLocale();\r
+ resolution = datefield.getCurrentResolution();\r
+ }\r
+\r
+ public void onClick(Widget sender) {\r
+ // processClickEvent(sender, true);\r
+ }\r
+\r
+ private boolean isEnabledDate(Date date) {\r
+ if ((minDate != null && date.before(minDate))\r
+ || (maxDate != null && date.after(maxDate))) {\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+\r
+ private void processClickEvent(Widget sender, boolean updateVariable) {\r
+ if (!datefield.isEnabled() || datefield.isReadonly()) {\r
+ return;\r
+ }\r
+ Date showingDate = datefield.getShowingDate();\r
+ if (!updateVariable) {\r
+ if (sender == prevYear) {\r
+ showingDate.setYear(showingDate.getYear() - 1);\r
+ updateCalendar();\r
+ } else if (sender == nextYear) {\r
+ showingDate.setYear(showingDate.getYear() + 1);\r
+ updateCalendar();\r
+ } else if (sender == prevMonth) {\r
+ showingDate.setMonth(showingDate.getMonth() - 1);\r
+ updateCalendar();\r
+ } else if (sender == nextMonth) {\r
+ showingDate.setMonth(showingDate.getMonth() + 1);\r
+ updateCalendar();\r
+ }\r
+ } else {\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_YEAR\r
+ || datefield.getCurrentResolution() == IDateField.RESOLUTION_MONTH) {\r
+ // Due to current UI, update variable if res=year/month\r
+ datefield.setCurrentDate(new Date(showingDate.getTime()));\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MONTH) {\r
+ datefield.getClient().updateVariable(datefield.getId(),\r
+ "month", datefield.getCurrentDate().getMonth() + 1,\r
+ false);\r
+ }\r
+ datefield.getClient().updateVariable(datefield.getId(), "year",\r
+ datefield.getCurrentDate().getYear() + 1900,\r
+ datefield.isImmediate());\r
+ }\r
+ }\r
+ }\r
+\r
+ private Timer timer;\r
+\r
+ public void onMouseDown(final Widget sender, int x, int y) {\r
+ if (sender instanceof IEventButton) {\r
+ processClickEvent(sender, false);\r
+ timer = new Timer() {\r
+ public void run() {\r
+ processClickEvent(sender, false);\r
+ }\r
+ };\r
+ timer.scheduleRepeating(100);\r
+ }\r
+ }\r
+\r
+ public void onMouseEnter(Widget sender) {\r
+ }\r
+\r
+ public void onMouseLeave(Widget sender) {\r
+ if (timer != null) {\r
+ timer.cancel();\r
+ }\r
+ }\r
+\r
+ public void onMouseMove(Widget sender, int x, int y) {\r
+ }\r
+\r
+ public void onMouseUp(Widget sender, int x, int y) {\r
+ if (timer != null) {\r
+ timer.cancel();\r
+ }\r
+ processClickEvent(sender, true);\r
+ }\r
+\r
+ private class IEventButton extends IButton implements SourcesMouseEvents {\r
+\r
+ private MouseListenerCollection mouseListeners;\r
+\r
+ public IEventButton() {\r
+ super();\r
+ sinkEvents(Event.FOCUSEVENTS | Event.KEYEVENTS | Event.ONCLICK\r
+ | Event.MOUSEEVENTS);\r
+ }\r
+\r
+ public void addMouseListener(MouseListener listener) {\r
+ if (mouseListeners == null) {\r
+ mouseListeners = new MouseListenerCollection();\r
+ }\r
+ mouseListeners.add(listener);\r
+ }\r
+\r
+ public void removeMouseListener(MouseListener listener) {\r
+ if (mouseListeners != null) {\r
+ mouseListeners.remove(listener);\r
+ }\r
+ }\r
+\r
+ public void onBrowserEvent(Event event) {\r
+ super.onBrowserEvent(event);\r
+ switch (DOM.eventGetType(event)) {\r
+ case Event.ONMOUSEDOWN:\r
+ case Event.ONMOUSEUP:\r
+ case Event.ONMOUSEMOVE:\r
+ case Event.ONMOUSEOVER:\r
+ case Event.ONMOUSEOUT:\r
+ if (mouseListeners != null) {\r
+ mouseListeners.fireMouseEvent(this, event);\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ private class DateClickListener implements TableListener {\r
+\r
+ private final CalendarPanel cal;\r
+\r
+ public DateClickListener(CalendarPanel panel) {\r
+ cal = panel;\r
+ }\r
+\r
+ public void onCellClicked(SourcesTableEvents sender, int row, int col) {\r
+ if (sender != cal || row < 2 || row > 7\r
+ || !cal.datefield.isEnabled() || cal.datefield.isReadonly()) {\r
+ return;\r
+ }\r
+\r
+ final String text = cal.getText(row, col);\r
+ if (text.equals(" ")) {\r
+ return;\r
+ }\r
+\r
+ try {\r
+ final Integer day = new Integer(text);\r
+ final Date newDate = cal.datefield.getShowingDate();\r
+ newDate.setDate(day.intValue());\r
+ if (!isEnabledDate(newDate)) {\r
+ return;\r
+ }\r
+ if (cal.datefield.getCurrentDate() == null) {\r
+ cal.datefield.setCurrentDate(new Date(newDate.getTime()));\r
+\r
+ // Init variables with current time\r
+ datefield.getClient().updateVariable(cal.datefield.getId(),\r
+ "hour", newDate.getHours(), false);\r
+ datefield.getClient().updateVariable(cal.datefield.getId(),\r
+ "min", newDate.getMinutes(), false);\r
+ datefield.getClient().updateVariable(cal.datefield.getId(),\r
+ "sec", newDate.getSeconds(), false);\r
+ datefield.getClient().updateVariable(cal.datefield.getId(),\r
+ "msec", datefield.getMilliseconds(), false);\r
+ }\r
+\r
+ cal.datefield.getCurrentDate().setTime(newDate.getTime());\r
+ cal.datefield.getClient().updateVariable(cal.datefield.getId(),\r
+ "day", cal.datefield.getCurrentDate().getDate(), false);\r
+ cal.datefield.getClient().updateVariable(cal.datefield.getId(),\r
+ "month", cal.datefield.getCurrentDate().getMonth() + 1,\r
+ false);\r
+ cal.datefield.getClient().updateVariable(cal.datefield.getId(),\r
+ "year",\r
+ cal.datefield.getCurrentDate().getYear() + 1900,\r
+ cal.datefield.isImmediate());\r
+\r
+ if (datefield instanceof ITextualDate\r
+ && resolution < IDateField.RESOLUTION_HOUR) {\r
+ ((ToolkitOverlay) getParent()).hide();\r
+ } else {\r
+ updateCalendar();\r
+ }\r
+\r
+ } catch (final NumberFormatException e) {\r
+ // Not a number, ignore and stop here\r
+ return;\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ public void setLimits(Date min, Date max) {\r
+ if (min != null) {\r
+ final Date d = new Date(min.getTime());\r
+ d.setHours(0);\r
+ d.setMinutes(0);\r
+ d.setSeconds(1);\r
+ minDate = d;\r
+ } else {\r
+ minDate = null;\r
+ }\r
+ if (max != null) {\r
+ final Date d = new Date(max.getTime());\r
+ d.setHours(24);\r
+ d.setMinutes(59);\r
+ d.setSeconds(59);\r
+ maxDate = d;\r
+ } else {\r
+ maxDate = null;\r
+ }\r
+ }\r
+\r
+ public void setCalendarEntrySource(CalendarEntrySource entrySource) {\r
+ this.entrySource = entrySource;\r
+ }\r
+\r
+ public CalendarEntrySource getCalendarEntrySource() {\r
+ return entrySource;\r
+ }\r
+\r
+ public interface CalendarEntrySource {\r
+ public List getEntries(Date date, int resolution);\r
+ }\r
+\r
+ /**\r
+ * Sets focus to Calendar panel.\r
+ * \r
+ * @param focus\r
+ */\r
+ public void setFocus(boolean focus) {\r
+ nextYear.setFocus(focus);\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@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);
+ }*/
+ }
+}
--- /dev/null
+/**\r
+ * \r
+ */\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+/**\r
+ * This interface indicates that the component is a Field (serverside), and\r
+ * wants (for instance) to automatically get the i-modified classname.\r
+ * \r
+ */\r
+public interface Field {\r
+\r
+}\r
--- /dev/null
+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();
+ }
+
+}
--- /dev/null
+/*
+@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);
+ }
+ }
+
+}
--- /dev/null
+/*
+@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;
+ }
+ }
+}
--- /dev/null
+/*
+@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
+ }
+
+}
--- /dev/null
+/*
+@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 = "<em>Layout file layouts/"
+ + newTemplate
+ + ".html is missing. Components will be drawn for debug purposes.</em>";
+ } 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("<script", nextPosToCheck);
+ while (scriptStart > 0) {
+ res += html.substring(endOfPrevScript, scriptStart);
+ scriptStart = lc.indexOf(">", scriptStart);
+ final int j = lc.indexOf("</script>", scriptStart);
+ scripts += html.substring(scriptStart + 1, j) + ";";
+ nextPosToCheck = endOfPrevScript = j + "</script>".length();
+ scriptStart = lc.indexOf("<script", nextPosToCheck);
+ }
+ res += html.substring(endOfPrevScript);
+
+ // Extract body
+ html = res;
+ lc = html.toLowerCase();
+ int startOfBody = lc.indexOf("<body");
+ if (startOfBody < 0) {
+ res = html;
+ } else {
+ res = "";
+ startOfBody = lc.indexOf(">", startOfBody) + 1;
+ final int endOfBody = lc.indexOf("</body>", 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;
+ }
+ }-*/;
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.Date;\r
+\r
+import com.google.gwt.user.client.Event;\r
+import com.google.gwt.user.client.ui.FlowPanel;\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.DateTimeService;\r
+import com.itmill.toolkit.terminal.gwt.client.LocaleNotLoadedException;\r
+import com.itmill.toolkit.terminal.gwt.client.Paintable;\r
+import com.itmill.toolkit.terminal.gwt.client.Tooltip;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+public class IDateField extends FlowPanel implements Paintable, Field {\r
+\r
+ public static final String CLASSNAME = "i-datefield";\r
+\r
+ protected String id;\r
+\r
+ protected ApplicationConnection client;\r
+\r
+ protected boolean immediate;\r
+\r
+ public static final int RESOLUTION_YEAR = 0;\r
+ public static final int RESOLUTION_MONTH = 1;\r
+ public static final int RESOLUTION_DAY = 2;\r
+ public static final int RESOLUTION_HOUR = 3;\r
+ public static final int RESOLUTION_MIN = 4;\r
+ public static final int RESOLUTION_SEC = 5;\r
+ public static final int RESOLUTION_MSEC = 6;\r
+\r
+ protected int currentResolution = RESOLUTION_YEAR;\r
+\r
+ protected String currentLocale;\r
+\r
+ protected boolean readonly;\r
+\r
+ protected boolean enabled;\r
+\r
+ protected Date date = null;\r
+ // e.g when paging a calendar, before actually selecting\r
+ protected Date showingDate = new Date();\r
+\r
+ protected DateTimeService dts;\r
+\r
+ public IDateField() {\r
+ setStyleName(CLASSNAME);\r
+ dts = new DateTimeService();\r
+ sinkEvents(Tooltip.TOOLTIP_EVENTS);\r
+ }\r
+\r
+ public void onBrowserEvent(Event event) {\r
+ super.onBrowserEvent(event);\r
+ if (client != null) {\r
+ client.handleTooltipEvent(event, this);\r
+ }\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+ // Ensure correct implementation and let layout manage caption\r
+ if (client.updateComponent(this, uidl, true)) {\r
+ return;\r
+ }\r
+\r
+ // Save details\r
+ this.client = client;\r
+ id = uidl.getId();\r
+ immediate = uidl.getBooleanAttribute("immediate");\r
+\r
+ readonly = uidl.getBooleanAttribute("readonly");\r
+ enabled = !uidl.getBooleanAttribute("disabled");\r
+\r
+ if (uidl.hasAttribute("locale")) {\r
+ final String locale = uidl.getStringAttribute("locale");\r
+ try {\r
+ dts.setLocale(locale);\r
+ currentLocale = locale;\r
+ } catch (final LocaleNotLoadedException e) {\r
+ currentLocale = dts.getLocale();\r
+ // TODO redirect this to console\r
+ System.out.println("Tried to use an unloaded locale \""\r
+ + locale + "\". Using default locale (" + currentLocale\r
+ + ").");\r
+ }\r
+ }\r
+\r
+ int newResolution;\r
+ if (uidl.hasVariable("msec")) {\r
+ newResolution = RESOLUTION_MSEC;\r
+ } else if (uidl.hasVariable("sec")) {\r
+ newResolution = RESOLUTION_SEC;\r
+ } else if (uidl.hasVariable("min")) {\r
+ newResolution = RESOLUTION_MIN;\r
+ } else if (uidl.hasVariable("hour")) {\r
+ newResolution = RESOLUTION_HOUR;\r
+ } else if (uidl.hasVariable("day")) {\r
+ newResolution = RESOLUTION_DAY;\r
+ } else if (uidl.hasVariable("month")) {\r
+ newResolution = RESOLUTION_MONTH;\r
+ } else {\r
+ newResolution = RESOLUTION_YEAR;\r
+ }\r
+\r
+ currentResolution = newResolution;\r
+\r
+ final int year = uidl.getIntVariable("year");\r
+ final int month = (currentResolution >= RESOLUTION_MONTH) ? uidl\r
+ .getIntVariable("month") : -1;\r
+ final int day = (currentResolution >= RESOLUTION_DAY) ? uidl\r
+ .getIntVariable("day") : -1;\r
+ final int hour = (currentResolution >= RESOLUTION_HOUR) ? uidl\r
+ .getIntVariable("hour") : 0;\r
+ final int min = (currentResolution >= RESOLUTION_MIN) ? uidl\r
+ .getIntVariable("min") : 0;\r
+ final int sec = (currentResolution >= RESOLUTION_SEC) ? uidl\r
+ .getIntVariable("sec") : 0;\r
+ final int msec = (currentResolution >= RESOLUTION_MSEC) ? uidl\r
+ .getIntVariable("msec") : 0;\r
+\r
+ // Construct new date for this datefield (only if not null)\r
+ if (year > -1) {\r
+ date = new Date((long) getTime(year, month, day, hour, min, sec,\r
+ msec));\r
+ showingDate.setTime(date.getTime());\r
+ } else {\r
+ date = null;\r
+ showingDate = new Date();\r
+ }\r
+\r
+ }\r
+\r
+ /*\r
+ * We need this redundant native function because Java's Date object doesn't\r
+ * have a setMilliseconds method.\r
+ */\r
+ private static native double getTime(int y, int m, int d, int h, int mi,\r
+ int s, int ms)\r
+ /*-{\r
+ try {\r
+ var date = new Date(2000,1,1,1); // don't use current date here\r
+ if(y && y >= 0) date.setFullYear(y);\r
+ if(m && m >= 1) date.setMonth(m-1);\r
+ if(d && d >= 0) date.setDate(d);\r
+ if(h >= 0) date.setHours(h);\r
+ if(mi >= 0) date.setMinutes(mi);\r
+ if(s >= 0) date.setSeconds(s);\r
+ if(ms >= 0) date.setMilliseconds(ms);\r
+ return date.getTime();\r
+ } catch (e) {\r
+ // TODO print some error message on the console\r
+ //console.log(e);\r
+ return (new Date()).getTime();\r
+ }\r
+ }-*/;\r
+\r
+ public int getMilliseconds() {\r
+ return (int) (date.getTime() - date.getTime() / 1000 * 1000);\r
+ }\r
+\r
+ public void setMilliseconds(int ms) {\r
+ date.setTime(date.getTime() / 1000 * 1000 + ms);\r
+ }\r
+\r
+ public int getShowingMilliseconds() {\r
+ return (int) (showingDate.getTime() - showingDate.getTime() / 1000 * 1000);\r
+ }\r
+\r
+ public void setShowingMilliseconds(int ms) {\r
+ showingDate.setTime(showingDate.getTime() / 1000 * 1000 + ms);\r
+ }\r
+\r
+ public int getCurrentResolution() {\r
+ return currentResolution;\r
+ }\r
+\r
+ public void setCurrentResolution(int currentResolution) {\r
+ this.currentResolution = currentResolution;\r
+ }\r
+\r
+ public String getCurrentLocale() {\r
+ return currentLocale;\r
+ }\r
+\r
+ public void setCurrentLocale(String currentLocale) {\r
+ this.currentLocale = currentLocale;\r
+ }\r
+\r
+ public Date getCurrentDate() {\r
+ return date;\r
+ }\r
+\r
+ public void setCurrentDate(Date date) {\r
+ this.date = date;\r
+ }\r
+\r
+ public Date getShowingDate() {\r
+ return showingDate;\r
+ }\r
+\r
+ public void setShowingDate(Date date) {\r
+ showingDate = date;\r
+ }\r
+\r
+ public boolean isImmediate() {\r
+ return immediate;\r
+ }\r
+\r
+ public boolean isReadonly() {\r
+ return readonly;\r
+ }\r
+\r
+ public boolean isEnabled() {\r
+ return enabled;\r
+ }\r
+\r
+ public DateTimeService getDateTimeService() {\r
+ return dts;\r
+ }\r
+\r
+ public String getId() {\r
+ return id;\r
+ }\r
+\r
+ public ApplicationConnection getClient() {\r
+ return client;\r
+ }\r
+}\r
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+public class IDateFieldCalendar extends IDateField {\r
+\r
+ private final CalendarPanel date;\r
+\r
+ public IDateFieldCalendar() {\r
+ super();\r
+ date = new CalendarPanel(this);\r
+ add(date);\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+ super.updateFromUIDL(uidl, client);\r
+ date.updateCalendar();\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@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("<img src=\"" + getSrc(uidl, client) + "\"" + w + h
+ + "/>");
+ client.addPngFix(DOM.getFirstChild(getElement()));
+
+ } else if (type.equals("browser")) {
+ if (browserElement == null) {
+ setHTML("<iframe width=\"100%\" height=\"100%\" frameborder=\"0\" src=\""
+ + getSrc(uidl, client) + "\"></iframe>");
+ 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("<object width=\"" + width + "\" height=\"" + heigth
+ + "\"><param name=\"movie\" value=\""
+ + getSrc(uidl, client) + "\"><embed src=\""
+ + getSrc(uidl, client) + "\" width=\"" + width
+ + "\" height=\"" + heigth + "\"></embed></object>");
+ } 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();
+ }
+}
--- /dev/null
+/*
+@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();
+ }
+ });
+ }
+ }
+}
--- /dev/null
+/*
+@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("<img src=\"");
+ sb.append(iconUri);
+ sb.append("\" alt=\"icon\" class=\"i-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, "<span>Prev</span>");
+ DOM.sinkEvents(up, Event.ONCLICK);
+ DOM.setInnerHTML(down, "<span>Next</span>");
+ 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 += "<div>" + captions[i] + "</div>";
+ // 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);
+ }
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.Element;\r
+import com.google.gwt.user.client.ui.ComplexPanel;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.Container;\r
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;\r
+import com.itmill.toolkit.terminal.gwt.client.ErrorMessage;\r
+import com.itmill.toolkit.terminal.gwt.client.Paintable;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+import com.itmill.toolkit.terminal.gwt.client.Util;\r
+\r
+public class IForm extends ComplexPanel implements Paintable,\r
+ ContainerResizedListener {\r
+\r
+ public static final String CLASSNAME = "i-form";\r
+\r
+ private Container lo;\r
+ private Element legend = DOM.createLegend();\r
+ private Element caption = DOM.createSpan();\r
+ private Element errorIndicatorElement = DOM.createDiv();\r
+ private Element desc = DOM.createDiv();\r
+ private Icon icon;\r
+ private ErrorMessage errorMessage = new ErrorMessage();\r
+\r
+ private Element fieldContainer = DOM.createDiv();\r
+\r
+ private Element footerContainer = DOM.createDiv();\r
+\r
+ private Container footer;\r
+\r
+ public IForm() {\r
+ setElement(DOM.createFieldSet());\r
+ setStyleName(CLASSNAME);\r
+ DOM.appendChild(getElement(), legend);\r
+ DOM.appendChild(legend, caption);\r
+ DOM.setElementProperty(errorIndicatorElement, "className",\r
+ "i-errorindicator");\r
+ DOM.setStyleAttribute(errorIndicatorElement, "display", "none");\r
+ DOM.setInnerText(errorIndicatorElement, " "); // needed for IE\r
+ DOM.setElementProperty(desc, "className", "i-form-description");\r
+ DOM.appendChild(getElement(), desc);\r
+ DOM.appendChild(getElement(), fieldContainer);\r
+ errorMessage.setVisible(false);\r
+ errorMessage.setStyleName(CLASSNAME + "-errormessage");\r
+ DOM.appendChild(getElement(), errorMessage.getElement());\r
+ DOM.appendChild(getElement(), footerContainer);\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+ if (client.updateComponent(this, uidl, false)) {\r
+ return;\r
+ }\r
+\r
+ boolean legendEmpty = true;\r
+ if (uidl.hasAttribute("caption")) {\r
+ DOM.setInnerText(caption, uidl.getStringAttribute("caption"));\r
+ legendEmpty = false;\r
+ } else {\r
+ DOM.setInnerText(caption, "");\r
+ }\r
+ if (uidl.hasAttribute("icon")) {\r
+ if (icon == null) {\r
+ icon = new Icon(client);\r
+ DOM.insertChild(legend, icon.getElement(), 0);\r
+ }\r
+ icon.setUri(uidl.getStringAttribute("icon"));\r
+ legendEmpty = false;\r
+ } else {\r
+ if (icon != null) {\r
+ DOM.removeChild(legend, icon.getElement());\r
+ }\r
+ }\r
+ if (legendEmpty) {\r
+ DOM.setStyleAttribute(legend, "display", "none");\r
+ } else {\r
+ DOM.setStyleAttribute(legend, "display", "");\r
+ }\r
+\r
+ if (uidl.hasAttribute("error")) {\r
+ final UIDL errorUidl = uidl.getErrors();\r
+ errorMessage.updateFromUIDL(errorUidl);\r
+ errorMessage.setVisible(true);\r
+\r
+ } else {\r
+ errorMessage.setVisible(false);\r
+ }\r
+\r
+ if (uidl.hasAttribute("description")) {\r
+ DOM.setInnerHTML(desc, uidl.getStringAttribute("description"));\r
+ } else {\r
+ DOM.setInnerHTML(desc, "");\r
+ }\r
+\r
+ iLayout();\r
+\r
+ final UIDL layoutUidl = uidl.getChildUIDL(0);\r
+ Container newLo = (Container) client.getPaintable(layoutUidl);\r
+ if (lo == null) {\r
+ lo = newLo;\r
+ add((Widget) lo, fieldContainer);\r
+ } else if (lo != newLo) {\r
+ client.unregisterPaintable(lo);\r
+ remove((Widget) lo);\r
+ lo = newLo;\r
+ add((Widget) lo, fieldContainer);\r
+ }\r
+ lo.updateFromUIDL(layoutUidl, client);\r
+\r
+ if (uidl.getChildCount() > 1) {\r
+ // render footer\r
+ Container newFooter = (Container) client.getPaintable(uidl\r
+ .getChildUIDL(1));\r
+ if (footer == null) {\r
+ add((Widget) newFooter, footerContainer);\r
+ footer = newFooter;\r
+ } else if (newFooter != footer) {\r
+ remove((Widget) footer);\r
+ client.unregisterPaintable(footer);\r
+ add((Widget) newFooter, footerContainer);\r
+ }\r
+ footer = newFooter;\r
+ footer.updateFromUIDL(uidl.getChildUIDL(1), client);\r
+ } else {\r
+ if (footer != null) {\r
+ remove((Widget) footer);\r
+ client.unregisterPaintable(footer);\r
+ }\r
+ }\r
+ }\r
+\r
+ public void iLayout() {\r
+ Util.runDescendentsLayout(this);\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@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);
+ }
+ }
+
+ }
+}
--- /dev/null
+/*
+@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);
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+public class IHorizontalExpandLayout extends IExpandLayout {
+
+ public IHorizontalExpandLayout() {
+ super(IExpandLayout.ORIENTATION_HORIZONTAL);
+ }
+
+}
--- /dev/null
+/*
+@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("");
+ }
+ }
+}
--- /dev/null
+/*
+@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);
+ }
+ }
+
+}
--- /dev/null
+/*
+@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
--- /dev/null
+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("<p>");
+ if (moreItemUIDL.hasAttribute("icon")) {
+ itemHTML.append("<img src=\""
+ + client.translateToolkitUri(moreItemUIDL
+ .getStringAttribute("icon"))
+ + "\" align=\"left\" />");
+ }
+ itemHTML.append(moreItemUIDL.getStringAttribute("text"));
+ itemHTML.append("</p>");
+ 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("<p>");
+
+ if (item.hasAttribute("icon")) {
+ itemHTML.append("<img src=\""
+ + client.translateToolkitUri(item
+ .getStringAttribute("icon"))
+ + "\" align=\"left\" />");
+ }
+
+ itemHTML.append(itemText);
+
+ if (currentMenu != this && item.getChildCount() > 0
+ && submenuIcon != null) {
+ itemHTML.append("<img src=\"" + submenuIcon
+ + "\" align=\"right\" />");
+ }
+
+ itemHTML.append("</p>");
+
+ 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("<img src=\"" + submenuIcon
+ + "\" align=\"right\" /></p>");
+ 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
--- /dev/null
+/*
+@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);
+ }
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.HashMap;\r
+import java.util.Iterator;\r
+import java.util.Map;\r
+\r
+import com.google.gwt.user.client.ui.CheckBox;\r
+import com.google.gwt.user.client.ui.Panel;\r
+import com.google.gwt.user.client.ui.RadioButton;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+public class IOptionGroup extends IOptionGroupBase {\r
+\r
+ public static final String CLASSNAME = "i-select-optiongroup";\r
+\r
+ private final Panel panel;\r
+\r
+ private final Map optionsToKeys;\r
+\r
+ public IOptionGroup() {\r
+ super(CLASSNAME);\r
+ panel = (Panel) optionsContainer;\r
+ optionsToKeys = new HashMap();\r
+ }\r
+\r
+ /*\r
+ * Return true if no elements were changed, false otherwise.\r
+ */\r
+ protected void buildOptions(UIDL uidl) {\r
+ panel.clear();\r
+ for (final Iterator it = uidl.getChildIterator(); it.hasNext();) {\r
+ final UIDL opUidl = (UIDL) it.next();\r
+ CheckBox op;\r
+ if (isMultiselect()) {\r
+ op = new ICheckBox();\r
+ op.setText(opUidl.getStringAttribute("caption"));\r
+ } else {\r
+ op = new RadioButton(id, opUidl.getStringAttribute("caption"));\r
+ op.setStyleName("i-radiobutton");\r
+ }\r
+ op.addStyleName(CLASSNAME_OPTION);\r
+ op.setChecked(opUidl.getBooleanAttribute("selected"));\r
+ op.setEnabled(!opUidl.getBooleanAttribute("disabled")\r
+ && !isReadonly() && !isDisabled());\r
+ op.addClickListener(this);\r
+ optionsToKeys.put(op, opUidl.getStringAttribute("key"));\r
+ panel.add(op);\r
+ }\r
+ }\r
+\r
+ protected Object[] getSelectedItems() {\r
+ return selectedKeys.toArray();\r
+ }\r
+\r
+ public void onClick(Widget sender) {\r
+ super.onClick(sender);\r
+ if (sender instanceof CheckBox) {\r
+ final boolean selected = ((CheckBox) sender).isChecked();\r
+ final String key = (String) optionsToKeys.get(sender);\r
+ if (!isMultiselect()) {\r
+ selectedKeys.clear();\r
+ }\r
+ if (selected) {\r
+ selectedKeys.add(key);\r
+ } else {\r
+ selectedKeys.remove(key);\r
+ }\r
+ client.updateVariable(id, "selected", getSelectedItems(),\r
+ isImmediate());\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.Set;\r
+\r
+import com.google.gwt.user.client.ui.ChangeListener;\r
+import com.google.gwt.user.client.ui.ClickListener;\r
+import com.google.gwt.user.client.ui.Composite;\r
+import com.google.gwt.user.client.ui.FlowPanel;\r
+import com.google.gwt.user.client.ui.KeyboardListener;\r
+import com.google.gwt.user.client.ui.Panel;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.Paintable;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+abstract class IOptionGroupBase extends Composite implements Paintable, Field,\r
+ ClickListener, ChangeListener, KeyboardListener {\r
+\r
+ public static final String CLASSNAME_OPTION = "i-select-option";\r
+\r
+ protected ApplicationConnection client;\r
+\r
+ protected String id;\r
+\r
+ protected Set selectedKeys;\r
+\r
+ private boolean immediate;\r
+\r
+ private boolean multiselect;\r
+\r
+ private boolean disabled;\r
+\r
+ private boolean readonly;\r
+\r
+ private int cols = 0;\r
+\r
+ private int rows = 0;\r
+\r
+ private boolean nullSelectionAllowed = true;\r
+\r
+ private boolean nullSelectionItemAvailable = false;\r
+\r
+ /**\r
+ * Widget holding the different options (e.g. ListBox or Panel for radio\r
+ * buttons) (optional, fallbacks to container Panel)\r
+ */\r
+ protected Widget optionsContainer;\r
+\r
+ /**\r
+ * Panel containing the component\r
+ */\r
+ private final Panel container;\r
+\r
+ private ITextField newItemField;\r
+\r
+ private IButton newItemButton;\r
+\r
+ public IOptionGroupBase(String classname) {\r
+ container = new FlowPanel();\r
+ initWidget(container);\r
+ optionsContainer = container;\r
+ container.setStyleName(classname);\r
+ immediate = false;\r
+ multiselect = false;\r
+ }\r
+\r
+ /*\r
+ * Call this if you wish to specify your own container for the option\r
+ * elements (e.g. SELECT)\r
+ */\r
+ public IOptionGroupBase(Widget w, String classname) {\r
+ this(classname);\r
+ optionsContainer = w;\r
+ container.add(optionsContainer);\r
+ }\r
+\r
+ protected boolean isImmediate() {\r
+ return immediate;\r
+ }\r
+\r
+ protected boolean isMultiselect() {\r
+ return multiselect;\r
+ }\r
+\r
+ protected boolean isDisabled() {\r
+ return disabled;\r
+ }\r
+\r
+ protected boolean isReadonly() {\r
+ return readonly;\r
+ }\r
+\r
+ protected boolean isNullSelectionAllowed() {\r
+ return nullSelectionAllowed;\r
+ }\r
+\r
+ protected boolean isNullSelectionItemAvailable() {\r
+ return nullSelectionItemAvailable;\r
+ }\r
+\r
+ /**\r
+ * @return "cols" specified in uidl, 0 if not specified\r
+ */\r
+ protected int getColumns() {\r
+ return cols;\r
+ }\r
+\r
+ /**\r
+ * @return "rows" specified in uidl, 0 if not specified\r
+ */\r
+\r
+ protected int getRows() {\r
+ return rows;\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+ this.client = client;\r
+ id = uidl.getId();\r
+\r
+ if (client.updateComponent(this, uidl, true)) {\r
+ return;\r
+ }\r
+\r
+ selectedKeys = uidl.getStringArrayVariableAsSet("selected");\r
+\r
+ readonly = uidl.getBooleanAttribute("readonly");\r
+ disabled = uidl.getBooleanAttribute("disabled");\r
+ multiselect = "multi".equals(uidl.getStringAttribute("selectmode"));\r
+ immediate = uidl.getBooleanAttribute("immediate");\r
+ nullSelectionAllowed = uidl.getBooleanAttribute("nullselect");\r
+ nullSelectionItemAvailable = uidl.getBooleanAttribute("nullselectitem");\r
+\r
+ cols = uidl.getIntAttribute("cols");\r
+ rows = uidl.getIntAttribute("rows");\r
+\r
+ final UIDL ops = uidl.getChildUIDL(0);\r
+\r
+ if (getColumns() > 0) {\r
+ container.setWidth(getColumns() + "em");\r
+ if (container != optionsContainer) {\r
+ optionsContainer.setWidth("100%");\r
+ }\r
+ }\r
+\r
+ buildOptions(ops);\r
+\r
+ if (uidl.getBooleanAttribute("allownewitem")) {\r
+ if (newItemField == null) {\r
+ newItemButton = new IButton();\r
+ newItemButton.setText("+");\r
+ newItemButton.setWidth("1.5em");\r
+ newItemButton.addClickListener(this);\r
+ newItemField = new ITextField();\r
+ newItemField.addKeyboardListener(this);\r
+ // newItemField.setColumns(16);\r
+ if (getColumns() > 0) {\r
+ newItemField.setWidth((getColumns() - 2) + "em");\r
+ }\r
+ }\r
+ newItemField.setEnabled(!disabled && !readonly);\r
+ newItemButton.setEnabled(!disabled && !readonly);\r
+\r
+ if (newItemField == null || newItemField.getParent() != container) {\r
+ container.add(newItemField);\r
+ container.add(newItemButton);\r
+ }\r
+ } else if (newItemField != null) {\r
+ container.remove(newItemField);\r
+ container.remove(newItemButton);\r
+ }\r
+\r
+ }\r
+\r
+ public void onClick(Widget sender) {\r
+ if (sender == newItemButton && !newItemField.getText().equals("")) {\r
+ client.updateVariable(id, "newitem", newItemField.getText(), true);\r
+ newItemField.setText("");\r
+ }\r
+ }\r
+\r
+ public void onChange(Widget sender) {\r
+ if (multiselect) {\r
+ client\r
+ .updateVariable(id, "selected", getSelectedItems(),\r
+ immediate);\r
+ } else {\r
+ client.updateVariable(id, "selected", new String[] { ""\r
+ + getSelectedItem() }, immediate);\r
+ }\r
+ }\r
+\r
+ public void onKeyPress(Widget sender, char keyCode, int modifiers) {\r
+ if (sender == newItemField && keyCode == KeyboardListener.KEY_ENTER) {\r
+ newItemButton.click();\r
+ }\r
+ }\r
+\r
+ public void onKeyUp(Widget sender, char keyCode, int modifiers) {\r
+ // Ignore, subclasses may override\r
+ }\r
+\r
+ public void onKeyDown(Widget sender, char keyCode, int modifiers) {\r
+ // Ignore, subclasses may override\r
+ }\r
+\r
+ protected abstract void buildOptions(UIDL uidl);\r
+\r
+ protected abstract Object[] getSelectedItems();\r
+\r
+ protected Object getSelectedItem() {\r
+ final Object[] sel = getSelectedItems();\r
+ if (sel.length > 0) {\r
+ return sel[0];\r
+ } else {\r
+ return null;\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@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.
+ *
+ * <p>
+ * There are two modes - vertical and horizontal.
+ * <ul>
+ * <li>Vertical mode uses structure: div-root ( div-wrap ( child ) div-wrap (
+ * child ))).</li>
+ * <li>Horizontal mode uses structure: table ( tbody ( tr-childcontainer (
+ * td-wrap ( child ) td-wrap ( child) )) )</li>
+ * </ul>
+ * where root and childcontainer refer to the root element and the element
+ * that contain WidgetWrappers.
+ * </p>
+ *
+ */
+ 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 = "<table cellspacing=\"0\" cellpadding=\"0\"><tbody>"
+ + (orientationMode == ORIENTATION_HORIZONTAL ? "<tr></tr>"
+ : "") + "</tbody></table>";
+ 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:
+ * <ul>
+ * <li>(a) Root DIV of the WrapperElement when not in tableMode</li>
+ * <li>(b) TD in just below the root TR of the WrapperElement when in
+ * tableMode</li>
+ * <li>(c) clipperDiv inside the (a) or (b)</li>
+ * <li>(d) The innermost TD within alignment structures located in (a),
+ * (b) or (c)</li>
+ * </ul>
+ *
+ * @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:
+ * <ul>
+ * <li>(a) Root DIV of the WrapperElement when not in tableMode</li>
+ * <li>(b) TD in just below the root TR of the WrapperElement when in
+ * tableMode</li>
+ * <li>(c) clipperDiv inside the (a) or (b)</li>
+ * </ul>
+ *
+ * @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:
+ * <ul>
+ * <li>(a) Root DIV of the WrapperElement when not in tableMode</li>
+ * <li>(b) TD in just below the root TR of the WrapperElement when in
+ * tableMode</li>
+ * </ul>
+ *
+ * @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 = "<table cellpadding='0' cellspacing='0' width='100%' height='100%'><tbody><tr><td>"
+ + "<table cellpadding='0' cellspacing='0' ><tbody><tr><td align='left'>"
+ + "</td></tr></tbody></table></td></tr></tbody></table>";
+ 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) {
+ /*
+ * <b>Validate:</b> 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;
+ }
+
+ /*
+ * <b>Adjust for Reinsertion:</b> 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;
+ }
+
+ /*
+ * <b>Detach Child:</b> Remove the Widget from its existing parent, if
+ * any. Most Panels will simply call {@link Widget#removeFromParent()}
+ * on the Widget.
+ */
+ child.removeFromParent();
+
+ /*
+ * <b>Logical Attach:</b> 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);
+
+ /*
+ * <b>Physical Attach:</b> 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());
+
+ /*
+ * <b>Adopt:</b> Call {@link #adopt(Widget)} to finalize the add as the
+ * very last step.
+ */
+ adopt(child);
+ }
+
+ /* documented at super */
+ public boolean remove(Widget child) {
+
+ /*
+ * <b>Validate:</b> Make sure this Panel is actually the parent of the
+ * child Widget; return <code>false</code> if it is not.
+ */
+ if (!childWidgets.contains(child)) {
+ return false;
+ }
+
+ /*
+ * <b>Orphan:</b> Call {@link #orphan(Widget)} first while the child
+ * Widget is still attached.
+ */
+ orphan(child);
+
+ /*
+ * <b>Physical Detach:</b> 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);
+
+ /*
+ * <b>Logical Detach:</b> 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;
+ }
+}
--- /dev/null
+/*
+@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);
+ }
+ }
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+\r
+/**\r
+ * This class represents a password field.\r
+ * \r
+ * @author IT Mill Ltd.\r
+ * \r
+ */\r
+public class IPasswordField extends ITextField {\r
+\r
+ public IPasswordField() {\r
+ super(DOM.createInputPassword());\r
+ }\r
+\r
+}\r
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.Timer;\r
+import com.google.gwt.user.client.Window;\r
+import com.google.gwt.user.client.ui.Button;\r
+import com.google.gwt.user.client.ui.ClickListener;\r
+import com.google.gwt.user.client.ui.PopupListener;\r
+import com.google.gwt.user.client.ui.PopupPanel;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.Paintable;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+public class IPopupCalendar extends ITextualDate implements Paintable, Field,\r
+ ClickListener, PopupListener {\r
+\r
+ private final Button calendarToggle;\r
+\r
+ private final CalendarPanel calendar;\r
+\r
+ private final ToolkitOverlay popup;\r
+ private boolean open = false;\r
+\r
+ public IPopupCalendar() {\r
+ super();\r
+\r
+ calendarToggle = new Button();\r
+ calendarToggle.setStyleName(CLASSNAME + "-button");\r
+ calendarToggle.setText("...");\r
+ calendarToggle.addClickListener(this);\r
+ add(calendarToggle);\r
+\r
+ calendar = new CalendarPanel(this);\r
+ popup = new ToolkitOverlay(true, true, true);\r
+ popup.setStyleName(IDateField.CLASSNAME + "-popup");\r
+ popup.setWidget(calendar);\r
+ popup.addPopupListener(this);\r
+\r
+ DOM.setElementProperty(calendar.getElement(), "id",\r
+ "PID_TOOLKIT_POPUPCAL");\r
+\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+ super.updateFromUIDL(uidl, client);\r
+ if (date != null) {\r
+ calendar.updateCalendar();\r
+ }\r
+ calendarToggle.setEnabled(enabled);\r
+ }\r
+\r
+ public void onClick(Widget sender) {\r
+ if (sender == calendarToggle && !open) {\r
+ open = true;\r
+ calendar.updateCalendar();\r
+ // clear previous values\r
+ popup.setWidth("");\r
+ popup.setHeight("");\r
+ popup.setPopupPositionAndShow(new PositionCallback() {\r
+ public void setPosition(int offsetWidth, int offsetHeight) {\r
+ final int w = offsetWidth;\r
+ final int h = offsetHeight;\r
+ int t = calendarToggle.getAbsoluteTop();\r
+ int l = calendarToggle.getAbsoluteLeft();\r
+ if (l + w > Window.getClientWidth()\r
+ + Window.getScrollLeft()) {\r
+ l = Window.getClientWidth() + Window.getScrollLeft()\r
+ - w;\r
+ }\r
+ if (t + h > Window.getClientHeight()\r
+ + Window.getScrollTop()) {\r
+ t = Window.getClientHeight() + Window.getScrollTop()\r
+ - h - calendarToggle.getOffsetHeight() - 30;\r
+ l += calendarToggle.getOffsetWidth();\r
+ }\r
+ \r
+ // fix size\r
+ popup.setWidth(w + "px");\r
+ popup.setHeight(h + "px");\r
+ \r
+ popup.setPopupPosition(l, t\r
+ + calendarToggle.getOffsetHeight() + 2);\r
+\r
+ setFocus(true);\r
+ }\r
+ });\r
+ }\r
+ }\r
+\r
+ public void onPopupClosed(PopupPanel sender, boolean autoClosed) {\r
+ if (sender == popup) {\r
+ buildDate();\r
+ // Sigh.\r
+ Timer t = new Timer() {\r
+ public void run() {\r
+ open = false;\r
+ }\r
+ };\r
+ t.schedule(100);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Sets focus to Calendar panel.\r
+ * \r
+ * @param focus\r
+ */\r
+ public void setFocus(boolean focus) {\r
+ calendar.setFocus(focus);\r
+ }\r
+\r
+ protected int getFieldExtraWidth() {\r
+ if (fieldExtraWidth < 0) {\r
+ fieldExtraWidth = super.getFieldExtraWidth();\r
+ fieldExtraWidth += calendarToggle.getOffsetWidth();\r
+ }\r
+ return fieldExtraWidth;\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@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();
+ }
+
+ }
+
+}
--- /dev/null
+/*
+@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, "<span>"
+ + (firstRowInViewPort + 1) + " – " + last + "..."
+ + "</span>");
+ 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("<span class=\"i-off\">");
+ } else {
+ buf.append("<span class=\"i-on\">");
+ }
+ buf.append(super.getHTML());
+ buf.append("</span>");
+
+ 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());
+ }
+ });
+ }
+ }
+ }
+ }
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+// \r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.Command;\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.DeferredCommand;\r
+import com.google.gwt.user.client.Element;\r
+import com.google.gwt.user.client.Event;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;\r
+import com.itmill.toolkit.terminal.gwt.client.Paintable;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+import com.itmill.toolkit.terminal.gwt.client.Util;\r
+\r
+public class ISlider extends Widget implements Paintable, Field,\r
+ ContainerResizedListener {\r
+\r
+ public static final String CLASSNAME = "i-slider";\r
+\r
+ /**\r
+ * Minimum size (width or height, depending on orientation) of the slider\r
+ * base.\r
+ */\r
+ private static final int MIN_SIZE = 50;\r
+\r
+ ApplicationConnection client;\r
+\r
+ String id;\r
+\r
+ private boolean immediate;\r
+ private boolean disabled;\r
+ private boolean readonly;\r
+ private boolean scrollbarStyle;\r
+\r
+ private int handleSize;\r
+ private double min;\r
+ private double max;\r
+ private int resolution;\r
+ private Double value;\r
+ private boolean vertical;\r
+ private int size = -1;\r
+ private boolean arrows;\r
+\r
+ /* DOM element for slider's base */\r
+ private final Element base;\r
+\r
+ /* DOM element for slider's handle */\r
+ private final Element handle;\r
+\r
+ /* DOM element for decrement arrow */\r
+ private final Element smaller;\r
+\r
+ /* DOM element for increment arrow */\r
+ private final Element bigger;\r
+\r
+ /* Temporary dragging/animation variables */\r
+ private boolean dragging = false;\r
+\r
+ public ISlider() {\r
+ super();\r
+\r
+ setElement(DOM.createDiv());\r
+ base = DOM.createDiv();\r
+ handle = DOM.createDiv();\r
+ smaller = DOM.createDiv();\r
+ bigger = DOM.createDiv();\r
+\r
+ setStyleName(CLASSNAME);\r
+ DOM.setElementProperty(base, "className", CLASSNAME + "-base");\r
+ DOM.setElementProperty(handle, "className", CLASSNAME + "-handle");\r
+ DOM.setElementProperty(smaller, "className", CLASSNAME + "-smaller");\r
+ DOM.setElementProperty(bigger, "className", CLASSNAME + "-bigger");\r
+\r
+ DOM.appendChild(getElement(), bigger);\r
+ DOM.appendChild(getElement(), smaller);\r
+ DOM.appendChild(getElement(), base);\r
+ DOM.appendChild(base, handle);\r
+\r
+ // Hide initially\r
+ DOM.setStyleAttribute(smaller, "display", "none");\r
+ DOM.setStyleAttribute(bigger, "display", "none");\r
+ DOM.setStyleAttribute(handle, "visibility", "hidden");\r
+\r
+ DOM.sinkEvents(getElement(), Event.MOUSEEVENTS | Event.ONMOUSEWHEEL);\r
+ DOM.sinkEvents(base, Event.ONCLICK);\r
+ DOM.sinkEvents(handle, Event.MOUSEEVENTS);\r
+ DOM.sinkEvents(smaller, Event.ONMOUSEDOWN | Event.ONMOUSEUP\r
+ | Event.ONMOUSEOUT);\r
+ DOM.sinkEvents(bigger, Event.ONMOUSEDOWN | Event.ONMOUSEUP\r
+ | Event.ONMOUSEOUT);\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+\r
+ this.client = client;\r
+ id = uidl.getId();\r
+\r
+ // Ensure correct implementation\r
+ if (client.updateComponent(this, uidl, true)) {\r
+ return;\r
+ }\r
+\r
+ immediate = uidl.getBooleanAttribute("immediate");\r
+ disabled = uidl.getBooleanAttribute("disabled");\r
+ readonly = uidl.getBooleanAttribute("readonly");\r
+\r
+ vertical = uidl.hasAttribute("vertical");\r
+ arrows = uidl.hasAttribute("arrows");\r
+\r
+ String style = "";\r
+ if (uidl.hasAttribute("style")) {\r
+ style = uidl.getStringAttribute("style");\r
+ }\r
+\r
+ scrollbarStyle = style.indexOf("scrollbar") > -1;\r
+\r
+ if (arrows) {\r
+ DOM.setStyleAttribute(smaller, "display", "block");\r
+ DOM.setStyleAttribute(bigger, "display", "block");\r
+ }\r
+\r
+ if (vertical) {\r
+ addStyleName(CLASSNAME + "-vertical");\r
+ } else {\r
+ removeStyleName(CLASSNAME + "-vertical");\r
+ }\r
+\r
+ min = uidl.getDoubleAttribute("min");\r
+ max = uidl.getDoubleAttribute("max");\r
+ resolution = uidl.getIntAttribute("resolution");\r
+ value = new Double(uidl.getDoubleVariable("value"));\r
+\r
+ handleSize = uidl.getIntAttribute("hsize");\r
+\r
+ buildBase();\r
+\r
+ if (!vertical) {\r
+ // Draw handle with a delay to allow base to gain maximum width\r
+ DeferredCommand.addCommand(new Command() {\r
+ public void execute() {\r
+ buildHandle();\r
+ setValue(value, false, false);\r
+ }\r
+ });\r
+ } else {\r
+ buildHandle();\r
+ setValue(value, false, false);\r
+ }\r
+ }\r
+\r
+ private void buildBase() {\r
+ final String styleAttribute = vertical ? "height" : "width";\r
+ final String domProperty = vertical ? "offsetHeight" : "offsetWidth";\r
+\r
+ if (size == -1) {\r
+ final Element p = DOM.getParent(getElement());\r
+ if (DOM.getElementPropertyInt(p, domProperty) > 50) {\r
+ if (vertical) {\r
+ setHeight();\r
+ } else {\r
+ DOM.setStyleAttribute(base, styleAttribute, "");\r
+ }\r
+ } else {\r
+ // Set minimum size and adjust after all components have\r
+ // (supposedly) been drawn completely.\r
+ DOM.setStyleAttribute(base, styleAttribute, MIN_SIZE + "px");\r
+ DeferredCommand.addCommand(new Command() {\r
+ public void execute() {\r
+ final Element p = DOM.getParent(getElement());\r
+ if (DOM.getElementPropertyInt(p, domProperty) > (MIN_SIZE + 5)) {\r
+ if (vertical) {\r
+ setHeight();\r
+ } else {\r
+ DOM.setStyleAttribute(base, styleAttribute, "");\r
+ }\r
+ // Ensure correct position\r
+ setValue(value, false, false);\r
+ }\r
+ }\r
+ });\r
+ }\r
+ } else {\r
+ DOM.setStyleAttribute(base, styleAttribute, size + "px");\r
+ }\r
+\r
+ // TODO attach listeners for focusing and arrow keys\r
+ }\r
+\r
+ private void buildHandle() {\r
+ final String styleAttribute = vertical ? "height" : "width";\r
+ final String handleAttribute = vertical ? "marginTop" : "marginLeft";\r
+ final String domProperty = vertical ? "offsetHeight" : "offsetWidth";\r
+\r
+ DOM.setStyleAttribute(handle, handleAttribute, "0");\r
+\r
+ if (scrollbarStyle) {\r
+ // Only stretch the handle if scrollbar style is set.\r
+ int s = (int) (Double.parseDouble(DOM.getElementProperty(base,\r
+ domProperty)) / 100 * handleSize);\r
+ if (handleSize == -1) {\r
+ final int baseS = Integer.parseInt(DOM.getElementProperty(base,\r
+ domProperty));\r
+ final double range = (max - min) * (resolution + 1) * 3;\r
+ s = (int) (baseS - range);\r
+ }\r
+ if (s < 3) {\r
+ s = 3;\r
+ }\r
+ DOM.setStyleAttribute(handle, styleAttribute, s + "px");\r
+ } else {\r
+ DOM.setStyleAttribute(handle, styleAttribute, "");\r
+ }\r
+\r
+ // Restore visibility\r
+ DOM.setStyleAttribute(handle, "visibility", "visible");\r
+\r
+ }\r
+\r
+ private void setValue(Double value, boolean animate, boolean updateToServer) {\r
+ if (value == null) {\r
+ return;\r
+ }\r
+\r
+ if (value.doubleValue() < min) {\r
+ value = new Double(min);\r
+ } else if (value.doubleValue() > max) {\r
+ value = new Double(max);\r
+ }\r
+\r
+ // Update handle position\r
+ final String styleAttribute = vertical ? "marginTop" : "marginLeft";\r
+ final String domProperty = vertical ? "offsetHeight" : "offsetWidth";\r
+ final int handleSize = Integer.parseInt(DOM.getElementProperty(handle,\r
+ domProperty));\r
+ final int baseSize = Integer.parseInt(DOM.getElementProperty(base,\r
+ domProperty));\r
+ final int range = baseSize - handleSize;\r
+ double v = value.doubleValue();\r
+ // Round value to resolution\r
+ if (resolution > 0) {\r
+ v = Math.round(v * Math.pow(10, resolution));\r
+ v = v / Math.pow(10, resolution);\r
+ } else {\r
+ v = Math.round(v);\r
+ }\r
+ final double valueRange = max - min;\r
+ double p = 0;\r
+ if (valueRange > 0) {\r
+ p = range * ((v - min) / valueRange);\r
+ }\r
+ if (p < 0) {\r
+ p = 0;\r
+ }\r
+ if (vertical) {\r
+ // IE6 rounding behaves a little unstable, reduce one pixel so the\r
+ // containing element (base) won't expand without limits\r
+ p = range - p - (Util.isIE6() ? 1 : 0);\r
+ }\r
+ final double pos = p;\r
+\r
+ final int current = DOM.getIntStyleAttribute(handle, styleAttribute);\r
+\r
+ DOM.setStyleAttribute(handle, styleAttribute, (Math.round(pos)) + "px");\r
+\r
+ // TODO give more detailed info when dragging and do roundup\r
+ DOM.setElementAttribute(handle, "title", "" + v);\r
+\r
+ // Update value\r
+ this.value = new Double(v);\r
+\r
+ if (updateToServer) {\r
+ client.updateVariable(id, "value", this.value.doubleValue(),\r
+ immediate);\r
+ }\r
+ }\r
+\r
+ public void onBrowserEvent(Event event) {\r
+ if (disabled || readonly) {\r
+ return;\r
+ }\r
+ final Element targ = DOM.eventGetTarget(event);\r
+\r
+ if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) {\r
+ processMouseWheelEvent(event);\r
+ } else if (dragging || DOM.compare(targ, handle)) {\r
+ processHandleEvent(event);\r
+ } else if (DOM.compare(targ, smaller)) {\r
+ decreaseValue(event);\r
+ } else if (DOM.compare(targ, bigger)) {\r
+ increaseValue(event);\r
+ } else {\r
+ processBaseEvent(event);\r
+ }\r
+ }\r
+\r
+ private void processMouseWheelEvent(Event event) {\r
+ final int dir = DOM.eventGetMouseWheelVelocityY(event);\r
+ if (dir < 0) {\r
+ increaseValue(event);\r
+ } else {\r
+ decreaseValue(event);\r
+ }\r
+ DOM.eventPreventDefault(event);\r
+ DOM.eventCancelBubble(event, true);\r
+ }\r
+\r
+ private void processHandleEvent(Event event) {\r
+ switch (DOM.eventGetType(event)) {\r
+ case Event.ONMOUSEDOWN:\r
+ if (!disabled && !readonly) {\r
+ dragging = true;\r
+ DOM.setCapture(getElement());\r
+ DOM.eventPreventDefault(event); // prevent selecting text\r
+ DOM.eventCancelBubble(event, true);\r
+ }\r
+ break;\r
+ case Event.ONMOUSEMOVE:\r
+ if (dragging) {\r
+ // DOM.setCapture(getElement());\r
+ setValueByEvent(event, false, false);\r
+ }\r
+ break;\r
+ case Event.ONMOUSEUP:\r
+ dragging = false;\r
+ DOM.releaseCapture(getElement());\r
+ setValueByEvent(event, true, true);\r
+ break;\r
+ default:\r
+ break;\r
+ }\r
+ }\r
+\r
+ private void processBaseEvent(Event event) {\r
+ if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {\r
+ if (!disabled && !readonly && !dragging) {\r
+ setValueByEvent(event, true, true);\r
+ DOM.eventCancelBubble(event, true);\r
+ }\r
+ } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN && dragging) {\r
+ dragging = false;\r
+ DOM.releaseCapture(getElement());\r
+ setValueByEvent(event, true, true);\r
+ }\r
+ }\r
+\r
+ private void decreaseValue(Event event) {\r
+ setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)),\r
+ false, true);\r
+ }\r
+\r
+ private void increaseValue(Event event) {\r
+ setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)),\r
+ false, true);\r
+ }\r
+\r
+ private void setValueByEvent(Event event, boolean animate, boolean roundup) {\r
+ double v = min; // Fallback to min\r
+\r
+ final int coord = vertical ? DOM.eventGetClientY(event) : DOM\r
+ .eventGetClientX(event);\r
+ final String domProperty = vertical ? "offsetHeight" : "offsetWidth";\r
+\r
+ final double handleSize = Integer.parseInt(DOM.getElementProperty(\r
+ handle, domProperty));\r
+ final double baseSize = Integer.parseInt(DOM.getElementProperty(base,\r
+ domProperty));\r
+ final double baseOffset = vertical ? DOM.getAbsoluteTop(base)\r
+ - handleSize / 2 : DOM.getAbsoluteLeft(base) + handleSize / 2;\r
+\r
+ if (vertical) {\r
+ v = ((baseSize - (coord - baseOffset)) / (baseSize - handleSize))\r
+ * (max - min) + min;\r
+ } else {\r
+ v = ((coord - baseOffset) / (baseSize - handleSize)) * (max - min)\r
+ + min;\r
+ }\r
+\r
+ if (v < min) {\r
+ v = min;\r
+ } else if (v > max) {\r
+ v = max;\r
+ }\r
+\r
+ setValue(new Double(v), animate, roundup);\r
+ }\r
+\r
+ public void iLayout() {\r
+ if (vertical) {\r
+ setHeight();\r
+ }\r
+ // Update handle position\r
+ setValue(value, false, false);\r
+ }\r
+\r
+ private void setHeight() {\r
+ if (size == -1) {\r
+ // Calculate decoration size\r
+ DOM.setStyleAttribute(base, "height", "0");\r
+ DOM.setStyleAttribute(base, "overflow", "hidden");\r
+ int h = DOM.getElementPropertyInt(getElement(), "offsetHeight");\r
+ if (h < MIN_SIZE) {\r
+ h = MIN_SIZE;\r
+ }\r
+ DOM.setStyleAttribute(base, "height", h + "px");\r
+ } else {\r
+ DOM.setStyleAttribute(base, "height", size + "px");\r
+ }\r
+ DOM.setStyleAttribute(base, "overflow", "");\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@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;
+ }
+
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+public class ISplitPanelHorizontal extends ISplitPanel {
+
+ public ISplitPanelHorizontal() {
+ super(ISplitPanel.ORIENTATION_HORIZONTAL);
+ }
+}
--- /dev/null
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+public class ISplitPanelVertical extends ISplitPanel {
+
+ public ISplitPanelVertical() {
+ super(ISplitPanel.ORIENTATION_VERTICAL);
+ }
+}
--- /dev/null
+/*
+@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;
+ }
+}
--- /dev/null
+/*
+@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();
+ }
+}
--- /dev/null
+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);
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.ui.ComplexPanel;\r
+import com.google.gwt.user.client.ui.Widget;\r
+\r
+/**\r
+ * A panel that displays all of its child widgets in a 'deck', where only one\r
+ * can be visible at a time. It is used by\r
+ * {@link com.itmill.toolkit.terminal.gwt.client.ui.ITabsheetPanel}.\r
+ * \r
+ * This class has the same basic functionality as the GWT DeckPanel\r
+ * {@link com.google.gwt.user.client.ui.DeckPanel}, with the exception that it\r
+ * doesn't manipulate the child widgets' width and height attributes.\r
+ */\r
+public class ITabsheetPanel extends ComplexPanel {\r
+\r
+ private Widget visibleWidget;\r
+\r
+ /**\r
+ * Creates an empty tabsheet panel.\r
+ */\r
+ public ITabsheetPanel() {\r
+ setElement(DOM.createDiv());\r
+ }\r
+\r
+ /**\r
+ * Adds the specified widget to the deck.\r
+ * \r
+ * @param w\r
+ * the widget to be added\r
+ */\r
+ public void add(Widget w) {\r
+ super.add(w, getElement());\r
+ initChildWidget(w);\r
+ }\r
+\r
+ /**\r
+ * Gets the index of the currently-visible widget.\r
+ * \r
+ * @return the visible widget's index\r
+ */\r
+ public int getVisibleWidget() {\r
+ return getWidgetIndex(visibleWidget);\r
+ }\r
+\r
+ /**\r
+ * Inserts a widget before the specified index.\r
+ * \r
+ * @param w\r
+ * the widget to be inserted\r
+ * @param beforeIndex\r
+ * the index before which it will be inserted\r
+ * @throws IndexOutOfBoundsException\r
+ * if <code>beforeIndex</code> is out of range\r
+ */\r
+ public void insert(Widget w, int beforeIndex) {\r
+ super.insert(w, getElement(), beforeIndex, true);\r
+ initChildWidget(w);\r
+ }\r
+\r
+ public boolean remove(Widget w) {\r
+ final boolean removed = super.remove(w);\r
+ if (removed) {\r
+ resetChildWidget(w);\r
+\r
+ if (visibleWidget == w) {\r
+ visibleWidget = null;\r
+ }\r
+ }\r
+ return removed;\r
+ }\r
+\r
+ /**\r
+ * Shows the widget at the specified index. This causes the currently-\r
+ * visible widget to be hidden.\r
+ * \r
+ * @param index\r
+ * the index of the widget to be shown\r
+ */\r
+ public void showWidget(int index) {\r
+ checkIndexBoundsForAccess(index);\r
+ Widget newVisible = getWidget(index);\r
+ if (visibleWidget != newVisible) {\r
+ if (visibleWidget != null) {\r
+ visibleWidget.setVisible(false);\r
+ }\r
+ visibleWidget = newVisible;\r
+ visibleWidget.setVisible(true);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Make the widget invisible, and set its width and height to full.\r
+ */\r
+ private void initChildWidget(Widget w) {\r
+ w.setVisible(false);\r
+ }\r
+\r
+ /**\r
+ * Make the widget visible, and clear the widget's width and height\r
+ * attributes. This is done so that any changes to the visibility, height,\r
+ * or width of the widget that were done by the panel are undone.\r
+ */\r
+ private void resetChildWidget(Widget w) {\r
+ w.setVisible(true);\r
+ }\r
+}\r
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.Element;\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+/**\r
+ * This class represents a multiline textfield (textarea).\r
+ * \r
+ * TODO consider replacing this with a RichTextArea based implementation. IE\r
+ * does not support CSS height for textareas in Strict mode :-(\r
+ * \r
+ * @author IT Mill Ltd.\r
+ * \r
+ */\r
+public class ITextArea extends ITextField {\r
+ public static final String CLASSNAME = "i-textarea";\r
+\r
+ public ITextArea() {\r
+ super(DOM.createTextArea());\r
+ setStyleName(CLASSNAME);\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+ // Call parent renderer explicitly\r
+ super.updateFromUIDL(uidl, client);\r
+\r
+ if (uidl.hasAttribute("rows")) {\r
+ setRows(new Integer(uidl.getStringAttribute("rows")).intValue());\r
+ }\r
+ }\r
+\r
+ public void setRows(int rows) {\r
+ setRows(getElement(), rows);\r
+ }\r
+\r
+ private native void setRows(Element e, int r)\r
+ /*-{\r
+ try {\r
+ if(e.tagName.toLowerCase() == "textarea")\r
+ e.rows = r;\r
+ } catch (e) {}\r
+ }-*/;\r
+\r
+}\r
--- /dev/null
+/*
+@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);
+ }
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.Date;\r
+\r
+import com.google.gwt.i18n.client.DateTimeFormat;\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.ui.ChangeListener;\r
+import com.google.gwt.user.client.ui.TextBox;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;\r
+import com.itmill.toolkit.terminal.gwt.client.BrowserInfo;\r
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;\r
+import com.itmill.toolkit.terminal.gwt.client.Focusable;\r
+import com.itmill.toolkit.terminal.gwt.client.LocaleNotLoadedException;\r
+import com.itmill.toolkit.terminal.gwt.client.LocaleService;\r
+import com.itmill.toolkit.terminal.gwt.client.Paintable;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+public class ITextualDate extends IDateField implements Paintable, Field,\r
+ ChangeListener, ContainerResizedListener, Focusable {\r
+\r
+ private static final String PARSE_ERROR_CLASSNAME = CLASSNAME\r
+ + "-parseerror";\r
+\r
+ private final TextBox text;\r
+\r
+ private String formatStr;\r
+\r
+ private String width;\r
+\r
+ private boolean needLayout;\r
+\r
+ protected int fieldExtraWidth = -1;\r
+\r
+ public ITextualDate() {\r
+ super();\r
+ text = new TextBox();\r
+ // use normal textfield styles as a basis\r
+ text.setStyleName(ITextField.CLASSNAME);\r
+ // add datefield spesific style name also\r
+ text.addStyleName(CLASSNAME + "-textfield");\r
+ text.addChangeListener(this);\r
+ add(text);\r
+ }\r
+\r
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {\r
+\r
+ int origRes = currentResolution;\r
+ super.updateFromUIDL(uidl, client);\r
+ if (origRes != currentResolution) {\r
+ // force recreating format string\r
+ formatStr = null;\r
+ }\r
+ buildDate();\r
+ }\r
+\r
+ protected String getFormatString() {\r
+ if (formatStr == null) {\r
+ if (currentResolution == RESOLUTION_YEAR) {\r
+ formatStr = "yyyy"; // force full year\r
+ } else {\r
+\r
+ try {\r
+ String frmString = LocaleService\r
+ .getDateFormat(currentLocale);\r
+ frmString = cleanFormat(frmString);\r
+ String delim = LocaleService\r
+ .getClockDelimiter(currentLocale);\r
+\r
+ if (currentResolution >= RESOLUTION_HOUR) {\r
+ if (dts.isTwelveHourClock()) {\r
+ frmString += " hh";\r
+ } else {\r
+ frmString += " HH";\r
+ }\r
+ if (currentResolution >= RESOLUTION_MIN) {\r
+ frmString += ":mm";\r
+ if (currentResolution >= RESOLUTION_SEC) {\r
+ frmString += ":ss";\r
+ if (currentResolution >= RESOLUTION_MSEC) {\r
+ frmString += ".SSS";\r
+ }\r
+ }\r
+ }\r
+ if (dts.isTwelveHourClock()) {\r
+ frmString += " aaa";\r
+ }\r
+\r
+ }\r
+\r
+ formatStr = frmString;\r
+ } catch (LocaleNotLoadedException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+ }\r
+ return formatStr;\r
+ }\r
+\r
+ /**\r
+ * \r
+ */\r
+ protected void buildDate() {\r
+ removeStyleName(PARSE_ERROR_CLASSNAME);\r
+ // Create the initial text for the textfield\r
+ String dateText;\r
+ if (date != null) {\r
+ dateText = DateTimeFormat.getFormat(getFormatString()).format(date);\r
+ } else {\r
+ dateText = "";\r
+ }\r
+\r
+ text.setText(dateText);\r
+ text.setEnabled(enabled && !readonly);\r
+\r
+ if (readonly) {\r
+ text.addStyleName("i-readonly");\r
+ } else {\r
+ text.removeStyleName("i-readonly");\r
+ }\r
+\r
+ }\r
+\r
+ public void onChange(Widget sender) {\r
+ if (sender == text) {\r
+ if (!text.getText().equals("")) {\r
+ try {\r
+ date = DateTimeFormat.getFormat(getFormatString()).parse(\r
+ text.getText());\r
+ // remove possibly added invalid value indication\r
+ removeStyleName(PARSE_ERROR_CLASSNAME);\r
+ } catch (final Exception e) {\r
+ ApplicationConnection.getConsole().log(e.getMessage());\r
+ addStyleName(PARSE_ERROR_CLASSNAME);\r
+ client.updateVariable(id, "lastInvalidDateString", text\r
+ .getText(), false);\r
+ date = null;\r
+ }\r
+ } else {\r
+ date = null;\r
+ // remove possibly added invalid value indication\r
+ removeStyleName(PARSE_ERROR_CLASSNAME);\r
+ }\r
+\r
+ if (date != null) {\r
+ showingDate = new Date(date.getTime());\r
+ }\r
+\r
+ // Update variables\r
+ // (only the smallest defining resolution needs to be\r
+ // immediate)\r
+ client.updateVariable(id, "year",\r
+ date != null ? date.getYear() + 1900 : -1,\r
+ currentResolution == IDateField.RESOLUTION_YEAR\r
+ && immediate);\r
+ if (currentResolution >= IDateField.RESOLUTION_MONTH) {\r
+ client.updateVariable(id, "month", date != null ? date\r
+ .getMonth() + 1 : -1,\r
+ currentResolution == IDateField.RESOLUTION_MONTH\r
+ && immediate);\r
+ }\r
+ if (currentResolution >= IDateField.RESOLUTION_DAY) {\r
+ client.updateVariable(id, "day", date != null ? date.getDate()\r
+ : -1, currentResolution == IDateField.RESOLUTION_DAY\r
+ && immediate);\r
+ }\r
+ if (currentResolution >= IDateField.RESOLUTION_HOUR) {\r
+ client.updateVariable(id, "hour", date != null ? date\r
+ .getHours() : -1,\r
+ currentResolution == IDateField.RESOLUTION_HOUR\r
+ && immediate);\r
+ }\r
+ if (currentResolution >= IDateField.RESOLUTION_MIN) {\r
+ client.updateVariable(id, "min", date != null ? date\r
+ .getMinutes() : -1,\r
+ currentResolution == IDateField.RESOLUTION_MIN\r
+ && immediate);\r
+ }\r
+ if (currentResolution >= IDateField.RESOLUTION_SEC) {\r
+ client.updateVariable(id, "sec", date != null ? date\r
+ .getSeconds() : -1,\r
+ currentResolution == IDateField.RESOLUTION_SEC\r
+ && immediate);\r
+ }\r
+ if (currentResolution == IDateField.RESOLUTION_MSEC) {\r
+ client.updateVariable(id, "msec",\r
+ date != null ? getMilliseconds() : -1, immediate);\r
+ }\r
+\r
+ }\r
+ }\r
+\r
+ private String cleanFormat(String format) {\r
+ // Remove unnecessary d & M if resolution is too low\r
+ if (currentResolution < IDateField.RESOLUTION_DAY) {\r
+ format = format.replaceAll("d", "");\r
+ }\r
+ if (currentResolution < IDateField.RESOLUTION_MONTH) {\r
+ format = format.replaceAll("M", "");\r
+ }\r
+\r
+ // Remove unsupported patterns\r
+ // TODO support for 'G', era designator (used at least in Japan)\r
+ format = format.replaceAll("[GzZwWkK]", "");\r
+\r
+ // Remove extra delimiters ('/' and '.')\r
+ while (format.startsWith("/") || format.startsWith(".")\r
+ || format.startsWith("-")) {\r
+ format = format.substring(1);\r
+ }\r
+ while (format.endsWith("/") || format.endsWith(".")\r
+ || format.endsWith("-")) {\r
+ format = format.substring(0, format.length() - 1);\r
+ }\r
+\r
+ // Remove duplicate delimiters\r
+ format = format.replaceAll("//", "/");\r
+ format = format.replaceAll("\\.\\.", ".");\r
+ format = format.replaceAll("--", "-");\r
+\r
+ return format.trim();\r
+ }\r
+\r
+ public void setWidth(String newWidth) {\r
+ if (!"".equals(newWidth) && (width == null || !newWidth.equals(width))) {\r
+ if (BrowserInfo.get().isIE6()) {\r
+ // in IE6 cols ~ min-width\r
+ DOM.setElementProperty(text.getElement(), "size", "1");\r
+ }\r
+ needLayout = true;\r
+ width = newWidth;\r
+ super.setWidth(width);\r
+ iLayout();\r
+ if (newWidth.indexOf("%") < 0) {\r
+ needLayout = false;\r
+ }\r
+ } else {\r
+ if ("".equals(newWidth) && width != null && !"".equals(width)) {\r
+ if (BrowserInfo.get().isIE6()) {\r
+ // revert IE6 hack\r
+ DOM.setElementProperty(text.getElement(), "size", "");\r
+ }\r
+ super.setWidth("");\r
+ needLayout = true;\r
+ iLayout();\r
+ needLayout = false;\r
+ width = null;\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Returns pixels in x-axis reserved for other than textfield content.\r
+ * \r
+ * @return extra width in pixels\r
+ */\r
+ protected int getFieldExtraWidth() {\r
+ if (fieldExtraWidth < 0) {\r
+ text.setWidth("0px");\r
+ fieldExtraWidth = text.getOffsetWidth();\r
+ }\r
+ return fieldExtraWidth;\r
+ }\r
+\r
+ public void iLayout() {\r
+ if (needLayout) {\r
+ text.setWidth((getOffsetWidth() - getFieldExtraWidth()) + "px");\r
+ }\r
+ }\r
+\r
+ public void focus() {\r
+ text.setFocus(true);\r
+ }\r
+}\r
--- /dev/null
+/*
+@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;
+ };
+ }-*/;
+
+ }
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.Iterator;\r
+import java.util.Vector;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.ui.FlowPanel;\r
+import com.google.gwt.user.client.ui.HTML;\r
+import com.google.gwt.user.client.ui.ListBox;\r
+import com.google.gwt.user.client.ui.Panel;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.UIDL;\r
+\r
+public class ITwinColSelect extends IOptionGroupBase {\r
+\r
+ private static final String CLASSNAME = "i-select-twincol";\r
+\r
+ private static final int VISIBLE_COUNT = 10;\r
+\r
+ private static final int DEFAULT_COLUMN_COUNT = 10;\r
+\r
+ private final ListBox options;\r
+\r
+ private final ListBox selections;\r
+\r
+ private final IButton add;\r
+\r
+ private final IButton remove;\r
+\r
+ private FlowPanel buttons;\r
+\r
+ private Panel panel;\r
+\r
+ private boolean widthSet = false;\r
+\r
+ public ITwinColSelect() {\r
+ super(CLASSNAME);\r
+ options = new ListBox();\r
+ options.addClickListener(this);\r
+ selections = new ListBox();\r
+ selections.addClickListener(this);\r
+ options.setVisibleItemCount(VISIBLE_COUNT);\r
+ selections.setVisibleItemCount(VISIBLE_COUNT);\r
+ options.setStyleName(CLASSNAME + "-options");\r
+ selections.setStyleName(CLASSNAME + "-selections");\r
+ buttons = new FlowPanel();\r
+ buttons.setStyleName(CLASSNAME + "-buttons");\r
+ add = new IButton();\r
+ add.setText(">>");\r
+ add.addClickListener(this);\r
+ remove = new IButton();\r
+ remove.setText("<<");\r
+ remove.addClickListener(this);\r
+ panel = ((Panel) optionsContainer);\r
+ panel.add(options);\r
+ buttons.add(add);\r
+ final HTML br = new HTML("<span/>");\r
+ br.setStyleName(CLASSNAME + "-deco");\r
+ buttons.add(br);\r
+ buttons.add(remove);\r
+ panel.add(buttons);\r
+ panel.add(selections);\r
+ }\r
+\r
+ protected void buildOptions(UIDL uidl) {\r
+ final boolean enabled = !isDisabled() && !isReadonly();\r
+ options.setMultipleSelect(isMultiselect());\r
+ selections.setMultipleSelect(isMultiselect());\r
+ options.setEnabled(enabled);\r
+ selections.setEnabled(enabled);\r
+ add.setEnabled(enabled);\r
+ remove.setEnabled(enabled);\r
+ options.clear();\r
+ selections.clear();\r
+ for (final Iterator i = uidl.getChildIterator(); i.hasNext();) {\r
+ final UIDL optionUidl = (UIDL) i.next();\r
+ if (optionUidl.hasAttribute("selected")) {\r
+ selections.addItem(optionUidl.getStringAttribute("caption"),\r
+ optionUidl.getStringAttribute("key"));\r
+ } else {\r
+ options.addItem(optionUidl.getStringAttribute("caption"),\r
+ optionUidl.getStringAttribute("key"));\r
+ }\r
+ }\r
+ if (getColumns() > 0) {\r
+ options.setWidth(getColumns() + "em");\r
+ selections.setWidth(getColumns() + "em");\r
+ optionsContainer.setWidth((getColumns() * 2 + 3) + "em");\r
+ } else if (!widthSet) {\r
+ options.setWidth(DEFAULT_COLUMN_COUNT + "em");\r
+ selections.setWidth(DEFAULT_COLUMN_COUNT + "em");\r
+ optionsContainer.setWidth((DEFAULT_COLUMN_COUNT * 2 + 2) + "em");\r
+ }\r
+ if (getRows() > 0) {\r
+ options.setVisibleItemCount(getRows());\r
+ selections.setVisibleItemCount(getRows());\r
+ }\r
+ }\r
+\r
+ protected Object[] getSelectedItems() {\r
+ final Vector selectedItemKeys = new Vector();\r
+ for (int i = 0; i < selections.getItemCount(); i++) {\r
+ selectedItemKeys.add(selections.getValue(i));\r
+ }\r
+ return selectedItemKeys.toArray();\r
+ }\r
+\r
+ private boolean[] getItemsToAdd() {\r
+ final boolean[] selectedIndexes = new boolean[options.getItemCount()];\r
+ for (int i = 0; i < options.getItemCount(); i++) {\r
+ if (options.isItemSelected(i)) {\r
+ selectedIndexes[i] = true;\r
+ } else {\r
+ selectedIndexes[i] = false;\r
+ }\r
+ }\r
+ return selectedIndexes;\r
+ }\r
+\r
+ private boolean[] getItemsToRemove() {\r
+ final boolean[] selectedIndexes = new boolean[selections.getItemCount()];\r
+ for (int i = 0; i < selections.getItemCount(); i++) {\r
+ if (selections.isItemSelected(i)) {\r
+ selectedIndexes[i] = true;\r
+ } else {\r
+ selectedIndexes[i] = false;\r
+ }\r
+ }\r
+ return selectedIndexes;\r
+ }\r
+\r
+ public void onClick(Widget sender) {\r
+ super.onClick(sender);\r
+ if (sender == add) {\r
+ final boolean[] sel = getItemsToAdd();\r
+ for (int i = 0; i < sel.length; i++) {\r
+ if (sel[i]) {\r
+ final int optionIndex = i\r
+ - (sel.length - options.getItemCount());\r
+ selectedKeys.add(options.getValue(optionIndex));\r
+\r
+ // Move selection to another column\r
+ final String text = options.getItemText(optionIndex);\r
+ final String value = options.getValue(optionIndex);\r
+ selections.addItem(text, value);\r
+ selections.setItemSelected(selections.getItemCount() - 1,\r
+ true);\r
+ options.removeItem(optionIndex);\r
+ }\r
+ }\r
+ client.updateVariable(id, "selected", selectedKeys.toArray(),\r
+ isImmediate());\r
+\r
+ } else if (sender == remove) {\r
+ final boolean[] sel = getItemsToRemove();\r
+ for (int i = 0; i < sel.length; i++) {\r
+ if (sel[i]) {\r
+ final int selectionIndex = i\r
+ - (sel.length - selections.getItemCount());\r
+ selectedKeys.remove(selections.getValue(selectionIndex));\r
+\r
+ // Move selection to another column\r
+ final String text = selections.getItemText(selectionIndex);\r
+ final String value = selections.getValue(selectionIndex);\r
+ options.addItem(text, value);\r
+ options.setItemSelected(options.getItemCount() - 1, true);\r
+ selections.removeItem(selectionIndex);\r
+ }\r
+ }\r
+ client.updateVariable(id, "selected", selectedKeys.toArray(),\r
+ isImmediate());\r
+ } else if (sender == options) {\r
+ // unselect all in other list, to avoid mistakes (i.e wrong button)\r
+ final int c = selections.getItemCount();\r
+ for (int i = 0; i < c; i++) {\r
+ selections.setItemSelected(i, false);\r
+ }\r
+ } else if (sender == selections) {\r
+ // unselect all in other list, to avoid mistakes (i.e wrong button)\r
+ final int c = options.getItemCount();\r
+ for (int i = 0; i < c; i++) {\r
+ options.setItemSelected(i, false);\r
+ }\r
+ }\r
+ }\r
+\r
+ public void setHeight(String height) {\r
+ super.setHeight(height);\r
+ if ("".equals(height)) {\r
+ options.setHeight("");\r
+ selections.setHeight("");\r
+ } else {\r
+ setFullHeightInternals();\r
+ }\r
+ }\r
+\r
+ private void setFullHeightInternals() {\r
+ options.setHeight("100%");\r
+ selections.setHeight("100%");\r
+ }\r
+\r
+ public void setWidth(String width) {\r
+ super.setWidth(width);\r
+ if (!"".equals(width) && width != null) {\r
+ setRelativeInternalWidths();\r
+ }\r
+ }\r
+\r
+ private void setRelativeInternalWidths() {\r
+ DOM.setStyleAttribute(getElement(), "position", "relative");\r
+ buttons.setWidth("16%");\r
+ options.setWidth("42%");\r
+ selections.setWidth("42%");\r
+ widthSet = true;\r
+ }\r
+}\r
--- /dev/null
+/*
+@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);
+ }
+}
--- /dev/null
+/*
+@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();
+ }
+
+}
--- /dev/null
+/*
+@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 += "<IMG src=\"" + parsedUri + "\" />";
+ }
+ if (notification.hasAttribute("caption")) {
+ html += "<H1>"
+ + notification.getStringAttribute("caption")
+ + "</H1>";
+ }
+ if (notification.hasAttribute("message")) {
+ html += "<p>"
+ + notification.getStringAttribute("message")
+ + "</p>";
+ }
+
+ 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;
+ }-*/;
+
+}
--- /dev/null
+/*
+@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 += "<img src=\"" + parsedUri + "\" />";
+ }
+ if (notification.hasAttribute("caption")) {
+ html += "<h1>"
+ + notification
+ .getStringAttribute("caption")
+ + "</h1>";
+ }
+ if (notification.hasAttribute("message")) {
+ html += "<p>"
+ + notification
+ .getStringAttribute("message")
+ + "</p>";
+ }
+
+ 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 = "<img src=\"" + icon + "\" class=\"i-icon\" />" + 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);
+ }
+
+}
--- /dev/null
+/*
+@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;
+ }
+ }
+
+}
--- /dev/null
+/*
+@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;
+ }
+
+}
--- /dev/null
+/*
+@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.
+ *
+ * <p>
+ * <img class='gallery' src='MenuBar.png'/>
+ * </p>
+ *
+ * <h3>CSS Style Rules</h3>
+ * <ul class='css'>
+ * <li>.gwt-MenuBar { the menu bar itself }</li>
+ * <li>.gwt-MenuBar .gwt-MenuItem { menu items }</li>
+ * <li>.gwt-MenuBar .gwt-MenuItem-selected { selected menu items }</li>
+ * </ul>
+ *
+ * <p>
+ * <h3>Example</h3>
+ * {@example com.google.gwt.examples.MenuBarExample}
+ * </p>
+ *
+ * @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
+ * <code>true</code> 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
+ * <code>true</code> 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
+ * <code>true</code> 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 <code>true</code> 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
+ * <code>true</code> to cause child menus to auto-open
+ */
+ public void setAutoOpen(boolean autoOpen) {
+ this.autoOpen = autoOpen;
+ }
+
+ /**
+ * Returns a list containing the <code>MenuItem</code> objects in the menu
+ * bar. If there are no items in the menu bar, then an empty
+ * <code>List</code> object will be returned.
+ *
+ * @return a list containing the <code>MenuItem</code> objects in the menu
+ * bar
+ */
+ protected List getItems() {
+ return items;
+ }
+
+ /**
+ * Returns the <code>MenuItem</code> that is currently selected
+ * (highlighted) by the user. If none of the items in the menu are currently
+ * selected, then <code>null</code> will be returned.
+ *
+ * @return the <code>MenuItem</code> that is currently selected, or
+ * <code>null</code> 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
+ * <code>true</code> if the item's command should be fired, <code>false</code>
+ * 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));
+ }
+ }
+}
--- /dev/null
+/*
+@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
+ * <code>true</code> 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
+ * <code>true</code> 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 <code>null</code> 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 <code>null</code> if none exists.
+ */
+ public MenuBar getParentMenu() {
+ return parentMenu;
+ }
+
+ /**
+ * Gets the sub-menu associated with this item.
+ *
+ * @return this item's sub-menu, or <code>null</code> 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);
+ }
+ }
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.ArrayList;\r
+import java.util.EventObject;\r
+import java.util.Iterator;\r
+\r
+import com.google.gwt.user.client.DOM;\r
+import com.google.gwt.user.client.Element;\r
+import com.google.gwt.user.client.Event;\r
+import com.google.gwt.user.client.Timer;\r
+import com.google.gwt.user.client.ui.HTML;\r
+import com.google.gwt.user.client.ui.Widget;\r
+import com.itmill.toolkit.terminal.gwt.client.BrowserInfo;\r
+\r
+public class Notification extends ToolkitOverlay {\r
+\r
+ public static final int CENTERED = 1;\r
+ public static final int CENTERED_TOP = 2;\r
+ public static final int CENTERED_BOTTOM = 3;\r
+ public static final int TOP_LEFT = 4;\r
+ public static final int TOP_RIGHT = 5;\r
+ public static final int BOTTOM_LEFT = 6;\r
+ public static final int BOTTOM_RIGHT = 7;\r
+\r
+ public static final int DELAY_FOREVER = -1;\r
+ public static final int DELAY_NONE = 0;\r
+\r
+ private static final String STYLENAME = "i-Notification";\r
+ private static final int mouseMoveThreshold = 7;\r
+ private static final int Z_INDEX_BASE = 20000;\r
+\r
+ private int startOpacity = 90;\r
+ private int fadeMsec = 400;\r
+ private int delayMsec = 1000;\r
+\r
+ private Timer fader;\r
+ private Timer delay;\r
+\r
+ private int x = -1;\r
+ private int y = -1;\r
+\r
+ private String temporaryStyle;\r
+\r
+ private ArrayList listeners;\r
+\r
+ public Notification() {\r
+ setStylePrimaryName(STYLENAME);\r
+ sinkEvents(Event.ONCLICK);\r
+ DOM.setStyleAttribute(getElement(), "zIndex", "" + Z_INDEX_BASE);\r
+ }\r
+\r
+ public Notification(int delayMsec) {\r
+ this();\r
+ this.delayMsec = delayMsec;\r
+ }\r
+\r
+ public Notification(int delayMsec, int fadeMsec, int startOpacity) {\r
+ this(delayMsec);\r
+ this.fadeMsec = fadeMsec;\r
+ this.startOpacity = startOpacity;\r
+ }\r
+\r
+ public void startDelay() {\r
+ DOM.removeEventPreview(this);\r
+ if (delayMsec > 0) {\r
+ delay = new Timer() {\r
+ public void run() {\r
+ fade();\r
+ }\r
+ };\r
+ delay.scheduleRepeating(delayMsec);\r
+ } else if (delayMsec == 0) {\r
+ fade();\r
+ }\r
+ }\r
+\r
+ public void show() {\r
+ show(CENTERED);\r
+ }\r
+\r
+ public void show(String style) {\r
+ show(CENTERED, style);\r
+ }\r
+\r
+ public void show(int position) {\r
+ show(position, null);\r
+ }\r
+\r
+ public void show(Widget widget, int position, String style) {\r
+ setWidget(widget);\r
+ show(position, style);\r
+ }\r
+\r
+ public void show(String html, int position, String style) {\r
+ setWidget(new HTML(html));\r
+ show(position, style);\r
+ }\r
+\r
+ public void show(int position, String style) {\r
+ setOpacity(getElement(), startOpacity);\r
+ if (style != null) {\r
+ temporaryStyle = style;\r
+ addStyleName(style);\r
+ }\r
+ super.show();\r
+ setPosition(position);\r
+ }\r
+\r
+ public void hide() {\r
+ DOM.removeEventPreview(this);\r
+ cancelDelay();\r
+ cancelFade();\r
+ if (temporaryStyle != null) {\r
+ removeStyleName(temporaryStyle);\r
+ temporaryStyle = null;\r
+ }\r
+ super.hide();\r
+ fireEvent(new HideEvent(this));\r
+ }\r
+\r
+ public void fade() {\r
+ DOM.removeEventPreview(this);\r
+ cancelDelay();\r
+ fader = new Timer() {\r
+ int opacity = startOpacity;\r
+\r
+ public void run() {\r
+ opacity -= 5;\r
+ setOpacity(getElement(), opacity);\r
+ if (opacity <= 0) {\r
+ cancel();\r
+ hide();\r
+ if (BrowserInfo.get().isOpera()) {\r
+ // tray notification on opera needs to explicitly define\r
+ // size, reset it\r
+ DOM.setStyleAttribute(getElement(), "width", "");\r
+ DOM.setStyleAttribute(getElement(), "height", "");\r
+ }\r
+\r
+ }\r
+ }\r
+ };\r
+ final int msec = fadeMsec / (startOpacity / 5);\r
+ fader.scheduleRepeating(msec);\r
+ }\r
+\r
+ public void setPosition(int position) {\r
+ final Element el = getElement();\r
+ DOM.setStyleAttribute(el, "top", "");\r
+ DOM.setStyleAttribute(el, "left", "");\r
+ DOM.setStyleAttribute(el, "bottom", "");\r
+ DOM.setStyleAttribute(el, "right", "");\r
+ switch (position) {\r
+ case TOP_LEFT:\r
+ DOM.setStyleAttribute(el, "top", "0px");\r
+ DOM.setStyleAttribute(el, "left", "0px");\r
+ break;\r
+ case TOP_RIGHT:\r
+ DOM.setStyleAttribute(el, "top", "0px");\r
+ DOM.setStyleAttribute(el, "right", "0px");\r
+ break;\r
+ case BOTTOM_RIGHT:\r
+ DOM.setStyleAttribute(el, "position", "absolute");\r
+ if (BrowserInfo.get().isOpera()) {\r
+ // tray notification on opera needs explicitly defined size\r
+ DOM.setStyleAttribute(el, "width", getOffsetWidth() + "px");\r
+ DOM.setStyleAttribute(el, "height", getOffsetHeight() + "px");\r
+ }\r
+ DOM.setStyleAttribute(el, "bottom", "0px");\r
+ DOM.setStyleAttribute(el, "right", "0px");\r
+ break;\r
+ case BOTTOM_LEFT:\r
+ DOM.setStyleAttribute(el, "bottom", "0px");\r
+ DOM.setStyleAttribute(el, "left", "0px");\r
+ break;\r
+ case CENTERED_TOP:\r
+ center();\r
+ DOM.setStyleAttribute(el, "top", "0px");\r
+ break;\r
+ case CENTERED_BOTTOM:\r
+ center();\r
+ DOM.setStyleAttribute(el, "top", "");\r
+ DOM.setStyleAttribute(el, "bottom", "0px");\r
+ break;\r
+ default:\r
+ case CENTERED:\r
+ center();\r
+ break;\r
+ }\r
+ }\r
+\r
+ private void cancelFade() {\r
+ if (fader != null) {\r
+ fader.cancel();\r
+ fader = null;\r
+ }\r
+ }\r
+\r
+ private void cancelDelay() {\r
+ if (delay != null) {\r
+ delay.cancel();\r
+ delay = null;\r
+ }\r
+ }\r
+\r
+ private void setOpacity(Element el, int opacity) {\r
+ DOM.setStyleAttribute(el, "opacity", "" + (opacity / 100.0));\r
+ DOM.setStyleAttribute(el, "filter", "Alpha(opacity=" + opacity + ")");\r
+\r
+ }\r
+\r
+ public void onBrowserEvent(Event event) {\r
+ DOM.removeEventPreview(this);\r
+ if (fader == null) {\r
+ fade();\r
+ }\r
+ }\r
+\r
+ public boolean onEventPreview(Event event) {\r
+ int type = DOM.eventGetType(event);\r
+ // "modal"\r
+ if (delayMsec == -1) {\r
+ if (type == Event.ONCLICK\r
+ && DOM\r
+ .isOrHasChild(getElement(), DOM\r
+ .eventGetTarget(event))) {\r
+ fade();\r
+ }\r
+ return false;\r
+ }\r
+ // default\r
+ switch (type) {\r
+ case Event.ONMOUSEMOVE:\r
+\r
+ if (x < 0) {\r
+ x = DOM.eventGetClientX(event);\r
+ y = DOM.eventGetClientY(event);\r
+ } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold\r
+ || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) {\r
+ startDelay();\r
+ }\r
+ break;\r
+ case Event.ONCLICK:\r
+ case Event.ONDBLCLICK:\r
+ case Event.KEYEVENTS:\r
+ case Event.ONSCROLL:\r
+ default:\r
+ startDelay();\r
+ }\r
+ return true;\r
+ }\r
+\r
+ public void addEventListener(EventListener listener) {\r
+ if (listeners == null) {\r
+ listeners = new ArrayList();\r
+ }\r
+ listeners.add(listener);\r
+ }\r
+\r
+ public void removeEventListener(EventListener listener) {\r
+ if (listeners == null) {\r
+ return;\r
+ }\r
+ listeners.remove(listener);\r
+ }\r
+\r
+ private void fireEvent(HideEvent event) {\r
+ if (listeners != null) {\r
+ for (Iterator it = listeners.iterator(); it.hasNext();) {\r
+ EventListener l = (EventListener) it.next();\r
+ l.notificationHidden(event);\r
+ }\r
+ }\r
+ }\r
+\r
+ public class HideEvent extends EventObject {\r
+ public HideEvent(Object source) {\r
+ super(source);\r
+ }\r
+ }\r
+\r
+ public interface EventListener extends java.util.EventListener {\r
+ public void notificationHidden(HideEvent event);\r
+ }\r
+}\r
--- /dev/null
+/*
+@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;
+ }
+
+}
--- /dev/null
+/*
+@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;
+
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import java.util.Date;\r
+\r
+import com.google.gwt.user.client.ui.ChangeListener;\r
+import com.google.gwt.user.client.ui.FlowPanel;\r
+import com.google.gwt.user.client.ui.ListBox;\r
+import com.google.gwt.user.client.ui.Widget;\r
+\r
+public class Time extends FlowPanel implements ChangeListener {\r
+\r
+ private final IDateField datefield;\r
+\r
+ private ListBox hours;\r
+\r
+ private ListBox mins;\r
+\r
+ private ListBox sec;\r
+\r
+ private ListBox msec;\r
+\r
+ private ListBox ampm;\r
+\r
+ private int resolution = IDateField.RESOLUTION_HOUR;\r
+\r
+ private boolean readonly;\r
+\r
+ public Time(IDateField parent) {\r
+ super();\r
+ datefield = parent;\r
+ setStyleName(IDateField.CLASSNAME + "-time");\r
+ }\r
+\r
+ private void buildTime(boolean redraw) {\r
+ final boolean thc = datefield.getDateTimeService().isTwelveHourClock();\r
+ if (redraw) {\r
+ clear();\r
+ final int numHours = thc ? 12 : 24;\r
+ hours = new ListBox();\r
+ hours.setStyleName(INativeSelect.CLASSNAME);\r
+ for (int i = 0; i < numHours; i++) {\r
+ hours.addItem((i < 10) ? "0" + i : "" + i);\r
+ }\r
+ hours.addChangeListener(this);\r
+ if (thc) {\r
+ ampm = new ListBox();\r
+ ampm.setStyleName(INativeSelect.CLASSNAME);\r
+ final String[] ampmText = datefield.getDateTimeService()\r
+ .getAmPmStrings();\r
+ ampm.addItem(ampmText[0]);\r
+ ampm.addItem(ampmText[1]);\r
+ ampm.addChangeListener(this);\r
+ }\r
+\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_MIN) {\r
+ mins = new ListBox();\r
+ mins.setStyleName(INativeSelect.CLASSNAME);\r
+ for (int i = 0; i < 60; i++) {\r
+ mins.addItem((i < 10) ? "0" + i : "" + i);\r
+ }\r
+ mins.addChangeListener(this);\r
+ }\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_SEC) {\r
+ sec = new ListBox();\r
+ sec.setStyleName(INativeSelect.CLASSNAME);\r
+ for (int i = 0; i < 60; i++) {\r
+ sec.addItem((i < 10) ? "0" + i : "" + i);\r
+ }\r
+ sec.addChangeListener(this);\r
+ }\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MSEC) {\r
+ msec = new ListBox();\r
+ msec.setStyleName(INativeSelect.CLASSNAME);\r
+ for (int i = 0; i < 1000; i++) {\r
+ if (i < 10) {\r
+ msec.addItem("00" + i);\r
+ } else if (i < 100) {\r
+ msec.addItem("0" + i);\r
+ } else {\r
+ msec.addItem("" + i);\r
+ }\r
+ }\r
+ msec.addChangeListener(this);\r
+ }\r
+\r
+ final String delimiter = datefield.getDateTimeService()\r
+ .getClockDelimeter();\r
+ final boolean ro = datefield.isReadonly();\r
+\r
+ if (ro) {\r
+ int h = 0;\r
+ if (datefield.getCurrentDate() != null) {\r
+ h = datefield.getCurrentDate().getHours();\r
+ }\r
+ if (thc) {\r
+ h -= h < 12 ? 0 : 12;\r
+ }\r
+ add(new ILabel(h < 10 ? "0" + h : "" + h));\r
+ } else {\r
+ add(hours);\r
+ }\r
+\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_MIN) {\r
+ add(new ILabel(delimiter));\r
+ if (ro) {\r
+ final int m = mins.getSelectedIndex();\r
+ add(new ILabel(m < 10 ? "0" + m : "" + m));\r
+ } else {\r
+ add(mins);\r
+ }\r
+ }\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_SEC) {\r
+ add(new ILabel(delimiter));\r
+ if (ro) {\r
+ final int s = sec.getSelectedIndex();\r
+ add(new ILabel(s < 10 ? "0" + s : "" + s));\r
+ } else {\r
+ add(sec);\r
+ }\r
+ }\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MSEC) {\r
+ add(new ILabel("."));\r
+ if (ro) {\r
+ final int m = datefield.getMilliseconds();\r
+ final String ms = m < 100 ? "0" + m : "" + m;\r
+ add(new ILabel(m < 10 ? "0" + ms : ms));\r
+ } else {\r
+ add(msec);\r
+ }\r
+ }\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_HOUR) {\r
+ add(new ILabel(delimiter + "00")); // o'clock\r
+ }\r
+ if (thc) {\r
+ add(new ILabel(" "));\r
+ if (ro) {\r
+ add(new ILabel(ampm.getItemText(datefield.getCurrentDate()\r
+ .getHours() < 12 ? 0 : 1)));\r
+ } else {\r
+ add(ampm);\r
+ }\r
+ }\r
+\r
+ if (ro) {\r
+ return;\r
+ }\r
+ }\r
+\r
+ // Update times\r
+ Date cdate = datefield.getCurrentDate();\r
+ boolean selected = true;\r
+ if (cdate == null) {\r
+ cdate = new Date();\r
+ selected = false;\r
+ }\r
+ if (thc) {\r
+ int h = cdate.getHours();\r
+ ampm.setSelectedIndex(h < 12 ? 0 : 1);\r
+ h -= ampm.getSelectedIndex() * 12;\r
+ hours.setSelectedIndex(h);\r
+ } else {\r
+ hours.setSelectedIndex(cdate.getHours());\r
+ }\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_MIN) {\r
+ mins.setSelectedIndex(cdate.getMinutes());\r
+ }\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_SEC) {\r
+ sec.setSelectedIndex(cdate.getSeconds());\r
+ }\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MSEC) {\r
+ if (selected) {\r
+ msec.setSelectedIndex(datefield.getMilliseconds());\r
+ } else {\r
+ msec.setSelectedIndex(0);\r
+ }\r
+ }\r
+ if (thc) {\r
+ ampm.setSelectedIndex(cdate.getHours() < 12 ? 0 : 1);\r
+ }\r
+\r
+ if (datefield.isReadonly() && !redraw) {\r
+ // Do complete redraw when in read-only status\r
+ clear();\r
+ final String delimiter = datefield.getDateTimeService()\r
+ .getClockDelimeter();\r
+\r
+ int h = cdate.getHours();\r
+ if (thc) {\r
+ h -= h < 12 ? 0 : 12;\r
+ }\r
+ add(new ILabel(h < 10 ? "0" + h : "" + h));\r
+\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_MIN) {\r
+ add(new ILabel(delimiter));\r
+ final int m = mins.getSelectedIndex();\r
+ add(new ILabel(m < 10 ? "0" + m : "" + m));\r
+ }\r
+ if (datefield.getCurrentResolution() >= IDateField.RESOLUTION_SEC) {\r
+ add(new ILabel(delimiter));\r
+ final int s = sec.getSelectedIndex();\r
+ add(new ILabel(s < 10 ? "0" + s : "" + s));\r
+ }\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_MSEC) {\r
+ add(new ILabel("."));\r
+ final int m = datefield.getMilliseconds();\r
+ final String ms = m < 100 ? "0" + m : "" + m;\r
+ add(new ILabel(m < 10 ? "0" + ms : ms));\r
+ }\r
+ if (datefield.getCurrentResolution() == IDateField.RESOLUTION_HOUR) {\r
+ add(new ILabel(delimiter + "00")); // o'clock\r
+ }\r
+ if (thc) {\r
+ add(new ILabel(" "));\r
+ add(new ILabel(ampm.getItemText(cdate.getHours() < 12 ? 0 : 1)));\r
+ }\r
+ }\r
+\r
+ final boolean enabled = datefield.isEnabled();\r
+ hours.setEnabled(enabled);\r
+ if (mins != null) {\r
+ mins.setEnabled(enabled);\r
+ }\r
+ if (sec != null) {\r
+ sec.setEnabled(enabled);\r
+ }\r
+ if (msec != null) {\r
+ msec.setEnabled(enabled);\r
+ }\r
+ if (ampm != null) {\r
+ ampm.setEnabled(enabled);\r
+ }\r
+\r
+ }\r
+\r
+ public void updateTime(boolean redraw) {\r
+ buildTime(redraw || resolution != datefield.getCurrentResolution()\r
+ || readonly != datefield.isReadonly());\r
+ if (datefield instanceof ITextualDate) {\r
+ ((ITextualDate) datefield).buildDate();\r
+ }\r
+ resolution = datefield.getCurrentResolution();\r
+ readonly = datefield.isReadonly();\r
+ }\r
+\r
+ public void onChange(Widget sender) {\r
+ if (datefield.getCurrentDate() == null) {\r
+ // was null on server, need to set\r
+ Date now = datefield.getShowingDate();\r
+ if (now == null) {\r
+ now = new Date();\r
+ datefield.setShowingDate(now);\r
+ }\r
+ datefield.setCurrentDate(new Date(now.getTime()));\r
+\r
+ // Init variables with current time\r
+ datefield.getClient().updateVariable(datefield.getId(), "year",\r
+ now.getYear() + 1900, false);\r
+ datefield.getClient().updateVariable(datefield.getId(), "month",\r
+ now.getMonth() + 1, false);\r
+ datefield.getClient().updateVariable(datefield.getId(), "day",\r
+ now.getDate(), false);\r
+ datefield.getClient().updateVariable(datefield.getId(), "hour",\r
+ now.getHours(), false);\r
+ datefield.getClient().updateVariable(datefield.getId(), "min",\r
+ now.getMinutes(), false);\r
+ datefield.getClient().updateVariable(datefield.getId(), "sec",\r
+ now.getSeconds(), false);\r
+ datefield.getClient().updateVariable(datefield.getId(), "msec",\r
+ datefield.getMilliseconds(), false);\r
+ }\r
+ if (sender == hours) {\r
+ int h = hours.getSelectedIndex();\r
+ if (datefield.getDateTimeService().isTwelveHourClock()) {\r
+ h = h + ampm.getSelectedIndex() * 12;\r
+ }\r
+ datefield.getCurrentDate().setHours(h);\r
+ datefield.getShowingDate().setHours(h);\r
+ datefield.getClient().updateVariable(datefield.getId(), "hour", h,\r
+ datefield.isImmediate());\r
+ updateTime(false);\r
+ } else if (sender == mins) {\r
+ final int m = mins.getSelectedIndex();\r
+ datefield.getCurrentDate().setMinutes(m);\r
+ datefield.getShowingDate().setMinutes(m);\r
+ datefield.getClient().updateVariable(datefield.getId(), "min", m,\r
+ datefield.isImmediate());\r
+ updateTime(false);\r
+ } else if (sender == sec) {\r
+ final int s = sec.getSelectedIndex();\r
+ datefield.getCurrentDate().setSeconds(s);\r
+ datefield.getShowingDate().setSeconds(s);\r
+ datefield.getClient().updateVariable(datefield.getId(), "sec", s,\r
+ datefield.isImmediate());\r
+ updateTime(false);\r
+ } else if (sender == msec) {\r
+ final int ms = msec.getSelectedIndex();\r
+ datefield.setMilliseconds(ms);\r
+ datefield.setShowingMilliseconds(ms);\r
+ datefield.getClient().updateVariable(datefield.getId(), "msec", ms,\r
+ datefield.isImmediate());\r
+ updateTime(false);\r
+ } else if (sender == ampm) {\r
+ final int h = hours.getSelectedIndex() + ampm.getSelectedIndex()\r
+ * 12;\r
+ datefield.getCurrentDate().setHours(h);\r
+ datefield.getShowingDate().setHours(h);\r
+ datefield.getClient().updateVariable(datefield.getId(), "hour", h,\r
+ datefield.isImmediate());\r
+ updateTime(false);\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+@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 = "<div class=\"top-left\"></div><div class=\"top\"></div><div class=\"top-right\"></div><div class=\"left\"></div><div class=\"center\"></div><div class=\"right\"></div><div class=\"bottom-left\"></div><div class=\"bottom\"></div><div class=\"bottom-right\"></div>";
+
+ 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();
+ }
+ }
+
+ }
+
+}
--- /dev/null
+/*
+@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;
+ }
+}
--- /dev/null
+/* \r
+@ITMillApache2LicenseForJavaFiles@\r
+ */\r
+\r
+package com.itmill.toolkit.terminal.gwt.client.ui;\r
+\r
+import com.google.gwt.user.client.ui.AbstractImagePrototype;\r
+\r
+public interface TreeImages extends com.google.gwt.user.client.ui.TreeImages {\r
+\r
+ /**\r
+ * An image indicating an open branch.\r
+ * \r
+ * @return a prototype of this image\r
+ * @gwt.resource com/itmill/toolkit/terminal/gwt/public/default/tree/img/expanded.png\r
+ */\r
+ AbstractImagePrototype treeOpen();\r
+\r
+ /**\r
+ * An image indicating a closed branch.\r
+ * \r
+ * @return a prototype of this image\r
+ * @gwt.resource com/itmill/toolkit/terminal/gwt/public/default/tree/img/collapsed.png\r
+ */\r
+ AbstractImagePrototype treeClosed();\r
+\r
+}\r
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+/*
+@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);
+
+ }
+
+}
--- /dev/null
+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
--- /dev/null
+/*
+ * 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());
+ }
+ }
+}