import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.HasHTML;
import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
+import com.vaadin.terminal.gwt.client.ContainerResizedListener;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.UIDL;
public class VMenuBar extends Widget implements Paintable,
- CloseHandler<PopupPanel> {
+ CloseHandler<PopupPanel>, ContainerResizedListener {
/** Set the CSS class name to allow styling. */
public static final String CLASSNAME = "v-menubar";
protected final VMenuBar hostReference = this;
protected String submenuIcon = null;
- protected boolean collapseItems = true;
protected CustomMenuItem moreItem = null;
+ protected VMenuBar collapsedRootItems;
// Construct an empty command to be used when the item has no command
// associated
/**
* 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.
*/
submenuIcon = null;
}
- collapseItems = options.getBooleanAttribute("collapseItems");
-
- if (collapseItems) {
+ if (uidl.hasAttribute("width")) {
UIDL moreItemUIDL = options.getChildUIDL(0);
StringBuffer itemHTML = new StringBuffer();
if (moreItemUIDL.hasAttribute("icon")) {
itemHTML.append("<img src=\""
+ client.translateVaadinUri(moreItemUIDL
- .getStringAttribute("icon"))
- + "\" align=\"left\" />");
+ .getStringAttribute("icon")) + "\" class=\""
+ + Icon.CLASSNAME + "\" alt=\"\" />");
}
itemHTML.append(moreItemUIDL.getStringAttribute("text"));
moreItem = new CustomMenuItem(itemHTML.toString(), emptyCommand);
+ collapsedRootItems = new VMenuBar(true);
+ moreItem.setSubMenu(collapsedRootItems);
+ moreItem.addStyleName(CLASSNAME + "-more-menuitem");
}
UIDL uidlItems = uidl.getChildUIDL(1);
if (item.hasAttribute("icon")) {
itemHTML.append("<img src=\""
+ client.translateVaadinUri(item
- .getStringAttribute("icon"))
- + "\" align=\"left\" />");
+ .getStringAttribute("icon")) + "\" class=\""
+ + Icon.CLASSNAME + "\" alt=\"\" />");
}
itemHTML.append(itemText);
- if (currentMenu != this && item.getChildCount() > 0
- && submenuIcon != null) {
- itemHTML.append("<img src=\"" + submenuIcon
- + "\" align=\"right\" />");
+ // Add submenu indicator
+ if (item.getChildCount() > 0) {
+ // FIXME For compatibility reasons: remove in version 7
+ String bgStyle = "";
+ if (submenuIcon != null) {
+ bgStyle = " style=\"background-image: url(" + submenuIcon
+ + "); text-indent: -999px; width: 1em;\"";
+ }
+ itemHTML
+ .append("<span class=\"" + CLASSNAME
+ + "-submenu-indicator\"" + bgStyle
+ + ">▶</span>");
}
Command cmd = null;
}
}// while
- // we might need to collapse the top-level menu
- // Only needed if there is more than 1 top level item
- // TODO and if width is defined
- if (collapseItems && getItems().size() > 1) {
-
- int topLevelWidth = 0;
-
- int ourWidth = getOffsetWidth();
-
- int i = 0;
- for (; i < getItems().size() && topLevelWidth < ourWidth; i++) {
- CustomMenuItem item = getItems().get(i);
- topLevelWidth += item.getOffsetWidth();
- }
-
- if (topLevelWidth > getOffsetWidth()) {
- ArrayList<CustomMenuItem> toBeCollapsed = new ArrayList<CustomMenuItem>();
- VMenuBar collapsed = new VMenuBar(true);
- for (int j = i - 2; j < getItems().size(); j++) {
- toBeCollapsed.add(getItems().get(j));
- }
-
- for (int j = 0; j < toBeCollapsed.size(); j++) {
- CustomMenuItem item = toBeCollapsed.get(j);
- removeItem(item);
-
- // it's ugly, but we have to insert the submenu icon
- if (item.getSubMenu() != null && submenuIcon != null) {
- StringBuffer itemText = new StringBuffer(item.getHTML());
- itemText.append("<img src=\"");
- itemText.append(submenuIcon);
- itemText.append("\" align=\"right\" />");
- item.setHTML(itemText.toString());
- }
+ iLayout();
- 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
*/
/**
* Returns the containing element of the menu
- *
+ *
* @return
*/
public Element getContainingElement() {
/**
* Returns a new child element to add an item to
- *
+ *
+ * @param index
+ * the index in which point to add a new element in a submenu. -1
+ * will add the new element as the last child (append)
+ *
* @return
*/
- public Element getNewChildElement() {
+ public Element getNewChildElement(int index) {
if (subMenu) {
Element tr = DOM.createTR();
- DOM.appendChild(getContainingElement(), tr);
+ if (index == -1) {
+ DOM.appendChild(getContainingElement(), tr);
+ } else {
+ DOM.insertChild(getContainingElement(), tr, index);
+ }
return tr;
} else {
return getContainingElement();
}
-
}
/**
* Add a new item to this menu
- *
+ *
* @param html
* items text
* @param cmd
/**
* Add a new item to this menu
- *
+ *
* @param item
*/
public void addItem(CustomMenuItem item) {
- DOM.appendChild(getNewChildElement(), item.getElement());
+ if (items.contains(item)) {
+ return;
+ }
+ DOM.appendChild(getNewChildElement(-1), item.getElement());
item.setParentMenu(this);
item.setSelected(false);
items.add(item);
}
+ public void addItem(CustomMenuItem item, int index) {
+ if (items.contains(item)) {
+ return;
+ }
+ if (subMenu) {
+ DOM.appendChild(getNewChildElement(index), item.getElement());
+ } else {
+ DOM.insertChild(getNewChildElement(-1), item.getElement(), index);
+ }
+ item.setParentMenu(this);
+ item.setSelected(false);
+ items.add(index, item);
+ }
+
/**
* Remove the given item from this menu
- *
+ *
* @param item
*/
public void removeItem(CustomMenuItem item) {
/**
* When an item is clicked
- *
+ *
* @param item
*/
public void itemClick(CustomMenuItem item) {
/**
* When the user hovers the mouse over the item
- *
+ *
* @param item
*/
public void itemOver(CustomMenuItem item) {
if (menuWasVisible && visibleChildMenu != item.getSubMenu()) {
popup.hide();
- visibleChildMenu = null;
}
if (item.getSubMenu() != null && (parentMenu != null || menuWasVisible)
/**
* When the mouse is moved away from an item
- *
+ *
* @param item
*/
public void itemOut(CustomMenuItem item) {
/**
* Shows the child menu of an item. The caller must ensure that the item has
* a submenu.
- *
+ *
* @param item
*/
public void showChildMenu(CustomMenuItem item) {
popup = new VOverlay(true, false, true);
popup.setWidget(item.getSubMenu());
popup.addCloseHandler(this);
-
+ int left = 0;
+ int top = 0;
if (subMenu) {
- popup.setPopupPosition(item.getParentMenu().getAbsoluteLeft()
- + item.getParentMenu().getOffsetWidth(), item
- .getAbsoluteTop());
+ left = item.getParentMenu().getAbsoluteLeft()
+ + item.getParentMenu().getOffsetWidth();
+ top = item.getAbsoluteTop();
} else {
- popup.setPopupPosition(item.getAbsoluteLeft(), item.getParentMenu()
- .getAbsoluteTop()
- + item.getParentMenu().getOffsetHeight());
+ left = item.getAbsoluteLeft();
+ top = item.getParentMenu().getAbsoluteTop()
+ + item.getParentMenu().getOffsetHeight();
}
+ popup.setPopupPosition(left, top);
item.getSubMenu().onShow();
visibleChildMenu = item.getSubMenu();
item.getSubMenu().setParentMenu(this);
popup.show();
+
+ if (left + popup.getOffsetWidth() >= RootPanel.getBodyElement()
+ .getOffsetWidth()) {
+ if (subMenu) {
+ left = item.getParentMenu().getAbsoluteLeft()
+ - popup.getOffsetWidth();
+ } else {
+ left = RootPanel.getBodyElement().getOffsetWidth()
+ - popup.getOffsetWidth();
+ ApplicationConnection.getConsole().log("" + left);
+ }
+ popup.setPopupPosition(left, top);
+ }
}
/**
* Hides the submenu of an item
- *
+ *
* @param item
*/
public void hideChildMenu(CustomMenuItem item) {
* When the menu is shown.
*/
public void onShow() {
- if (!items.isEmpty()) {
- (items.get(0)).setSelected(true);
+ // remove possible previous selection
+ if (selected != null) {
+ selected.setSelected(false);
+ selected = null;
}
}
/**
* Returns the parent menu of this menu, or null if this is the top-level
* menu
- *
+ *
* @return
*/
public VMenuBar getParentMenu() {
/**
* Set the parent menu of this menu
- *
+ *
* @param parent
*/
public void setParentMenu(VMenuBar parent) {
/**
* Returns the currently selected item of this menu, or null if nothing is
* selected
- *
+ *
* @return
*/
public CustomMenuItem getSelected() {
/**
* Set the currently selected item of this menu
- *
+ *
* @param item
*/
public void setSelected(CustomMenuItem item) {
if (event.isAutoClosed()) {
hideParents();
}
- // setSelected(null);
visibleChildMenu = null;
popup = null;
}
/**
- *
+ *
* A class to hold information on menu items
- *
+ *
*/
private class CustomMenuItem extends UIObject implements HasHTML {
}
}
-}// class VMenuBar
+ /**
+ * @author Jouni Koivuviita / IT Mill Ltd.
+ */
+
+ public void iLayout() {
+ // Only collapse if there is more than one item in the root menu and the
+ // menu has an explicit size
+ if ((getItems().size() > 1 || collapsedRootItems.getItems().size() > 0)
+ && getElement().getStyle().getProperty("width") != null) {
+
+ // Measure the width of the "more" item
+ final boolean morePresent = getItems().contains(moreItem);
+ addItem(moreItem);
+ final int moreItemWidth = moreItem.getOffsetWidth();
+ if (!morePresent) {
+ removeItem(moreItem);
+ }
+
+ // Measure available space
+ int availableWidth = getElement().getClientWidth();
+ final int rootWidth = getElement().getFirstChildElement()
+ .getOffsetWidth();
+ int diff = availableWidth - rootWidth;
+
+ removeItem(moreItem);
+
+ if (diff < 0) {
+ // Too many items: collapse last items from root menu
+ final int widthNeeded = moreItemWidth - diff;
+ int widthReduced = 0;
+
+ while (widthReduced < widthNeeded && getItems().size() > 0) {
+ // Move last root menu item to collapsed menu
+ CustomMenuItem collapse = getItems().get(
+ getItems().size() - 1);
+ widthReduced += collapse.getOffsetWidth();
+ removeItem(collapse);
+ collapsedRootItems.addItem(collapse, 0);
+ }
+ } else if (collapsedRootItems.getItems().size() > 0) {
+ // Space available for items: expand first items from collapsed
+ // menu
+ int widthAvailable = diff + moreItemWidth;
+ int widthGrowth = 0;
+
+ while (widthAvailable > widthGrowth) {
+ // Move first item from collapsed menu to the root menu
+ CustomMenuItem expand = collapsedRootItems.getItems()
+ .get(0);
+ collapsedRootItems.removeItem(expand);
+ addItem(expand);
+ widthGrowth += expand.getOffsetWidth();
+ if (collapsedRootItems.getItems().size() > 0) {
+ widthAvailable -= moreItemWidth;
+ }
+ if (widthGrowth > widthAvailable) {
+ removeItem(expand);
+ collapsedRootItems.addItem(expand, 0);
+ } else {
+ widthAvailable = diff;
+ }
+ }
+ }
+ if (collapsedRootItems.getItems().size() > 0) {
+ addItem(moreItem);
+ }
+ }
+ }
+
+}
// Number of items in this menu
private static int numberOfItems = 0;
+ /**
+ * @deprecated
+ * @see #setCollapse(boolean)
+ */
+ @Deprecated
private boolean collapseItems;
+
+ /**
+ * @deprecated
+ * @see #setSubmenuIcon(Resource)
+ */
+ @Deprecated
private Resource submenuIcon;
+
private MenuItem moreItem;
/** Paint (serialise) the component for the client. */
target.addAttribute("submenuIcon", submenuIcon);
}
- target.addAttribute("collapseItems", collapseItems);
-
- if (collapseItems) {
+ if (getWidth() > -1) {
target.startTag("moreItem");
target.addAttribute("text", moreItem.getText());
if (moreItem.getIcon() != null) {
* Set the icon to be used if a sub-menu has children. Defaults to null;
*
* @param icon
+ * @deprecated (since 6.2, will be removed in 7.0) Icon is set in theme, no
+ * need to worry about the visual representation here.
*/
+ @Deprecated
public void setSubmenuIcon(Resource icon) {
submenuIcon = icon;
requestRepaint();
}
/**
- * Get the icon used for sub-menus. Returns null if no icon is set.
- *
- * @return
+ * @deprecated
+ * @see #setSubmenuIcon(Resource)
*/
+ @Deprecated
public Resource getSubmenuIcon() {
return submenuIcon;
}
/**
* Enable or disable collapsing top-level items. Top-level items will
- * collapse to if there is not enough room for them. Items that don't fit
- * will be placed under the "More" menu item.
+ * collapse together if there is not enough room for them. Items that don't
+ * fit will be placed under the "More" menu item.
*
* Collapsing is enabled by default.
*
* @param collapse
+ * @deprecated (since 6.2, will be removed in 7.0) Collapsing is always
+ * enabled if the MenuBar has a specified width.
*/
+ @Deprecated
public void setCollapse(boolean collapse) {
collapseItems = collapse;
requestRepaint();
}
/**
- * Collapsing is enabled by default.
- *
- * @return true if the top-level items will be collapsed
+ * @see #setCollapse(boolean)
+ * @deprecated
*/
+ @Deprecated
public boolean getCollapse() {
return collapseItems;
}
/**
* Set the item that is used when collapsing the top level menu. All
* "overflowing" items will be added below this. The item command will be
- * ignored. If set to null, the default item with the "More" text is be
+ * ignored. If set to null, the default item with the "More..." text is be
* used.
*
+ * The item command (if specified) is ignored.
+ *
* @param item
*/
public void setMoreMenuItem(MenuItem item) {
if (item != null) {
moreItem = item;
} else {
- moreItem = new MenuItem("More", null, null);
+ moreItem = new MenuItem("More...", null, null);
}
requestRepaint();
}