
eu.webtoolkit.jwt.WPopupMenu Maven / Gradle / Ivy
Show all versions of jwt Show documentation
/*
* Copyright (C) 2009 Emweb bvba, Leuven, Belgium.
*
* See the LICENSE file for terms of use.
*/
package eu.webtoolkit.jwt;
import java.util.*;
import java.util.regex.*;
import java.io.*;
import java.lang.ref.*;
import java.util.concurrent.locks.ReentrantLock;
import javax.servlet.http.*;
import javax.servlet.*;
import eu.webtoolkit.jwt.*;
import eu.webtoolkit.jwt.chart.*;
import eu.webtoolkit.jwt.utils.*;
import eu.webtoolkit.jwt.servlet.*;
/**
* A menu presented in a popup window.
*
*
* The menu implements a typical context menu, with support for submenu's.
* It is not to be confused with {@link WMenu} which implements an
* always-visible navigation menu for a web application.
*
* When initially created, the menu is invisible, until
* {@link WPopupMenu#popup(WPoint p) popup()} or
* {@link WPopupMenu#exec(WPoint p) exec()} is called. Then, the menu will
* remain visible until an item is selected, or the user cancels the menu (by
* hitting Escape or clicking elsewhere).
*
* The implementation assumes availability of JavaScript to position the menu at
* the current mouse position and provide feed-back of the currently selected
* item.
*
* Similar in use as {@link WDialog}, there are two ways of using the menu. The
* simplest way is to use one of the {@link WPopupMenu#exec(WPoint p) exec()}
* methods, to use a reentrant event loop and wait until the user cancelled the
* popup menu (by hitting Escape or clicking elsewhere), or selected an item.
*
* Alternatively, you can use one of the {@link WPopupMenu#popup(WPoint p)
* popup()} methods to show the menu and listen to the
* {@link WPopupMenu#aboutToHide() aboutToHide} signal where you read the
* {@link WPopupMenu#getResult() getResult()}.
*
* You have several options to react to the selection of an item:
*
* - Either you use the {@link WPopupMenuItem} itself to identify the action,
* perhaps by specialization or simply by binding custom data using
* {@link WPopupMenuItem#setData(Object data) WPopupMenuItem#setData()}.
* - You can bind a separate method to each item's
* {@link WPopupMenuItem#triggered() WPopupMenuItem#triggered()} signal.
*
*
* Usage example:
*
*
*
*
* // Create a menu with some items
* WPopupMenu popup = new WPopupMenu();
* popup.addItem("icons/item1.gif", "Item 1");
* popup.addItem("Item 2").setCheckable(true);
* popup.addItem("Item 3");
* popup.addSeparator();
* popup.addItem("Item 4");
* popup.addSeparator();
* popup.addItem("Item 5");
* popup.addItem("Item 6");
* popup.addSeparator();
*
* WPopupMenu subMenu = new WPopupMenu();
* subMenu.addItem("Sub Item 1");
* subMenu.addItem("Sub Item 2");
* popup.addMenu("Item 7", subMenu);
*
* WPopupMenuItem item = popup.exec(event);
*
* if (item) {
* // ... do associated action.
* }
*
*
*
*
*
CSS
*
* A {@link WPopupMenu} has the Wt-popupmenu
style class. The look
* can be overridden using the following style class selectors:
*
*
*
*
* .Wt-popupmenu .Wt-item, .Wt-popupmenu .Wt-selected : item
* .Wt-popupmenu .Wt-selected : selected item
* .Wt-popupmenu .Wt-separator : separator
*
*
*
*
* A snapshot of the {@link WPopupMenu}:
*
* WPopupMenu example (default)
*
*
*
* WPopupMenu example (polished)
*
*
*
* @see WPopupMenuItem
*/
public class WPopupMenu extends WCompositeWidget {
/**
* Creates a new popup menu.
*
* The menu is hidden, by default, and must be shown using
* {@link WPopupMenu#popup(WPoint p) popup()} or
* {@link WPopupMenu#exec(WPoint p) exec()}.
*/
public WPopupMenu() {
super();
this.parentItem_ = null;
this.result_ = null;
this.aboutToHide_ = new Signal(this);
this.triggered_ = new Signal1(this);
this.cancel_ = new JSignal(this, "cancel");
this.globalClickConnection_ = new AbstractSignal.Connection();
this.globalEscapeConnection_ = new AbstractSignal.Connection();
this.recursiveEventLoop_ = false;
this.autoHideDelay_ = -1;
String TEMPLATE = "${shadow-x1-x2}${contents}";
this
.setImplementation(this.impl_ = new WTemplate(new WString(
TEMPLATE)));
this.impl_.setLoadLaterWhenInvisible(false);
this.setPositionScheme(PositionScheme.Absolute);
this.setStyleClass("Wt-popupmenu Wt-outset");
this.impl_.bindString("shadow-x1-x2", WTemplate.DropShadow_x1_x2);
WContainerWidget content = new WContainerWidget();
content.setStyleClass("content");
this.impl_.bindWidget("contents", content);
String CSS_RULES_NAME = "Wt::WPopupMenu";
WApplication app = WApplication.getInstance();
if (!app.getStyleSheet().isDefined(CSS_RULES_NAME)) {
app.getStyleSheet().addRule(".Wt-notselected .Wt-popupmenu",
"visibility: hidden;", CSS_RULES_NAME);
}
app.getDomRoot().addWidget(this);
this.hide();
}
/**
* Adds an item with given text.
*
* Adds an item to the menu with given text, and returns the corresponding
* item object.
*
*
* @see WPopupMenu#add(WPopupMenuItem item)
*/
public WPopupMenuItem addItem(CharSequence text) {
return this.addItem("", text);
}
/**
* Adds an item with given icon and text.
*
* Adds an item to the menu with given text and icon, and returns the
* corresponding item object.
*
*
* Note: The icon should have a width of 16 pixels.
*
*
* @see WPopupMenu#add(WPopupMenuItem item)
*/
public WPopupMenuItem addItem(String iconPath, CharSequence text) {
WPopupMenuItem item = new WPopupMenuItem(iconPath, text);
this.add(item);
return item;
}
// public WPopupMenuItem addItem(CharSequence text, T target,
// methodpointertomember or
// dependentsizedarray>) ;
// public WPopupMenuItem addItem(String iconPath, CharSequence text, T
// target, methodpointertomember or
// dependentsizedarray>) ;
/**
* Adds a submenu, with given text.
*
* Adds an item with text text
, that leads to a submenu
* menu
.
*
*
* @see WPopupMenu#add(WPopupMenuItem item)
*/
public WPopupMenuItem addMenu(CharSequence text, WPopupMenu menu) {
return this.addMenu("", text, menu);
}
/**
* Adds a submenu, with given icon and text.
*
* Adds an item with given text and icon, that leads to a submenu
* menu
.
*
*
* @see WPopupMenu#add(WPopupMenuItem item)
*/
public WPopupMenuItem addMenu(String iconPath, CharSequence text,
WPopupMenu menu) {
WPopupMenuItem item = this.addItem(iconPath, text);
item.setPopupMenu(menu);
return item;
}
/**
* Adds a menu item.
*
* Adds an item to the popup menu.
*/
public void add(WPopupMenuItem item) {
this.getContents().addWidget(item);
}
/**
* Adds a separator to the menu.
*
* Adds a separator the popup menu. The separator is an empty div with
* style-class "separator".
*/
public void addSeparator() {
this.add(new WPopupMenuItem(true));
}
/**
* Shows the the popup at a position.
*
* Displays the popup at a point with document coordinates
* point
. The positions intelligent, and will chose one of the
* four menu corners to correspond to this point so that the popup menu is
* completely visible within the window.
*
*
* @see WPopupMenu#exec(WPoint p)
*/
public void popup(WPoint p) {
this.popupImpl();
this.setOffsets(new WLength(42), EnumSet.of(Side.Left, Side.Top));
this.setOffsets(new WLength(-10000), EnumSet.of(Side.Left, Side.Top));
WApplication.getInstance().doJavaScript(
"Wt3_1_11.positionXY('" + this.getId() + "',"
+ String.valueOf(p.getX()) + ","
+ String.valueOf(p.getY()) + ");");
}
/**
* Shows the the popup at the location of a mouse event.
*
* This is a convenience method for {@link WPopupMenu#popup(WPoint p)
* popup()} that uses the event's document coordinates.
*
*
* @see WPopupMenu#popup(WPoint p)
* @see WMouseEvent#getDocument()
*/
public void popup(WMouseEvent e) {
this.popup(new WPoint(e.getDocument().x, e.getDocument().y));
}
/**
* Shows the popup besides a widget.
*
*
* @see WWidget#positionAt(WWidget widget, Orientation orientation)
*/
public void popup(WWidget location, Orientation orientation) {
this.popupImpl();
this.positionAt(location, orientation);
}
/**
* Shows the popup besides a widget.
*
* Calls {@link #popup(WWidget location, Orientation orientation)
* popup(location, Orientation.Vertical)}
*/
public final void popup(WWidget location) {
popup(location, Orientation.Vertical);
}
/**
* Executes the the popup at a position.
*
* Displays the popup at a point with document coordinates p
,
* using {@link WPopupMenu#popup(WPoint p) popup()}, and the waits until a
* menu item is selected, or the menu is cancelled.
*
* Returns the selected menu (or sub-menu) item, or null
if the
* user cancelled the menu.
*
*
* @see WPopupMenu#popup(WPoint p)
*/
public WPopupMenuItem exec(WPoint p) {
if (this.recursiveEventLoop_) {
throw new WtException(
"WPopupMenu::exec(): already in recursive event loop.");
}
WApplication app = WApplication.getInstance();
this.recursiveEventLoop_ = true;
this.popup(p);
if (app.getEnvironment().isTest()) {
app.getEnvironment().popupExecuted().trigger(this);
if (this.recursiveEventLoop_) {
throw new WtException("Test case must close popup menu.");
}
} else {
do {
app.getSession().doRecursiveEventLoop();
} while (this.recursiveEventLoop_);
}
return this.result_;
}
/**
* Executes the the popup at the location of a mouse event.
*
* This is a convenience method for {@link WPopupMenu#exec(WPoint p) exec()}
* that uses the event's document coordinates.
*
*
* @see WPopupMenu#exec(WPoint p)
*/
public WPopupMenuItem exec(WMouseEvent e) {
return this.exec(new WPoint(e.getDocument().x, e.getDocument().y));
}
/**
* Executes the popup besides a widget.
*
*
* @see WWidget#positionAt(WWidget widget, Orientation orientation)
*/
public WPopupMenuItem exec(WWidget location, Orientation orientation) {
if (this.recursiveEventLoop_) {
throw new WtException(
"WPopupMenu::exec(): already in recursive event loop.");
}
WebSession session = WApplication.getInstance().getSession();
this.recursiveEventLoop_ = true;
this.popup(location, orientation);
do {
session.doRecursiveEventLoop();
} while (this.recursiveEventLoop_);
return this.result_;
}
/**
* Executes the popup besides a widget.
*
* Returns {@link #exec(WWidget location, Orientation orientation)
* exec(location, Orientation.Vertical)}
*/
public final WPopupMenuItem exec(WWidget location) {
return exec(location, Orientation.Vertical);
}
/**
* Returns the last triggered menu item.
*
* The result is null
when the user cancelled the popup menu.
*/
public WPopupMenuItem getResult() {
return this.result_;
}
public void setHidden(boolean hidden, WAnimation animation) {
super.setHidden(hidden, animation);
if (hidden) {
this.renderOutAll();
}
}
public void setMaximumSize(WLength width, WLength height) {
super.setMaximumSize(width, height);
this.getContents().setMaximumSize(width, height);
}
public void setMinimumSize(WLength width, WLength height) {
super.setMinimumSize(width, height);
this.getContents().setMinimumSize(width, height);
}
/**
* Signal emitted when the popup is hidden.
*
* This signal is emitted when the popup is hidden, either because an item
* was selected, or when the menu was cancelled.
*
* You can use {@link WPopupMenu#getResult() getResult()} to get the
* selected item.
*/
public Signal aboutToHide() {
return this.aboutToHide_;
}
/**
* Signal emitted when an item is activated.
*
* Passes the activated item as argument. This signal is only emitted for
* the toplevel menu.
*
*
* @see WPopupMenuItem#triggered()
*/
public Signal1 triggered() {
return this.triggered_;
}
/**
* Configure auto-hide when the mouse leaves the menu.
*
* If enabled
, The popup menu will be hidden when the mouse
* leaves the menu for longer than autoHideDelay
* (milliseconds). The popup menu result will be 0, as if the user
* cancelled.
*
* By default, this option is disabled.
*/
public void setAutoHide(boolean enabled, int autoHideDelay) {
if (enabled) {
this.autoHideDelay_ = autoHideDelay;
} else {
this.autoHideDelay_ = -1;
}
}
/**
* Configure auto-hide when the mouse leaves the menu.
*
* Calls {@link #setAutoHide(boolean enabled, int autoHideDelay)
* setAutoHide(enabled, 0)}
*/
public final void setAutoHide(boolean enabled) {
setAutoHide(enabled, 0);
}
private WTemplate impl_;
WPopupMenuItem parentItem_;
WPopupMenuItem result_;
private Signal aboutToHide_;
private Signal1 triggered_;
private JSignal cancel_;
private AbstractSignal.Connection globalClickConnection_;
private AbstractSignal.Connection globalEscapeConnection_;
private boolean recursiveEventLoop_;
private int autoHideDelay_;
private WContainerWidget getContents() {
return ((this.impl_.resolveWidget("contents")) instanceof WContainerWidget ? (WContainerWidget) (this.impl_
.resolveWidget("contents"))
: null);
}
WPopupMenu getTopLevelMenu() {
return this.parentItem_ != null ? this.parentItem_.getTopLevelMenu()
: this;
}
private void done() {
this.done((WPopupMenuItem) null);
}
void done(WPopupMenuItem result) {
this.result_ = result;
this.hide();
WApplication.getInstance().getRoot().clicked().disconnect(
this.globalClickConnection_);
WApplication.getInstance().globalEscapePressed().disconnect(
this.globalEscapeConnection_);
this.recursiveEventLoop_ = false;
this.triggered_.trigger(this.result_);
this.aboutToHide_.trigger();
}
private void popupImpl() {
this.renderOutAll();
this.result_ = null;
WApplication app = WApplication.getInstance();
if (app.globalEscapePressed().isConnected()) {
app.globalEscapePressed().trigger();
}
this.globalClickConnection_ = app.getRoot().clicked().addListener(this,
new Signal1.Listener() {
public void trigger(WMouseEvent e1) {
WPopupMenu.this.done();
}
});
this.globalEscapeConnection_ = app.globalEscapePressed().addListener(
this, new Signal.Listener() {
public void trigger() {
WPopupMenu.this.done();
}
});
this.prepareRender(app);
this.show();
}
void popupToo(WWidget location) {
this.show();
this.positionAt(location, Orientation.Horizontal);
}
private void prepareRender(WApplication app) {
if (app.getEnvironment().agentIsIE()) {
app.doJavaScript(this.getJsRef() + ".lastChild.style.width="
+ this.getJsRef() + ".lastChild.offsetWidth+'px';");
}
if (this.autoHideDelay_ >= 0) {
app.loadJavaScript("js/WPopupMenu.js", wtjs1());
if (!this.cancel_.isConnected()) {
this.doJavaScript("new Wt3_1_11.WPopupMenu("
+ app.getJavaScriptClass() + "," + this.getJsRef()
+ "," + String.valueOf(this.autoHideDelay_) + ");");
this.cancel_.addListener(this, new Signal.Listener() {
public void trigger() {
WPopupMenu.this.done();
}
});
}
}
}
void renderOutAll() {
WContainerWidget c = this.getContents();
for (int i = 0; i < c.getCount(); ++i) {
WPopupMenuItem item = ((c.getWidget(i)) instanceof WPopupMenuItem ? (WPopupMenuItem) (c
.getWidget(i))
: null);
item.renderOut();
}
}
static WJavaScriptPreamble wtjs1() {
return new WJavaScriptPreamble(
JavaScriptScope.WtClassScope,
JavaScriptObjectType.JavaScriptConstructor,
"WPopupMenu",
"function(e,b,c){function f(){e.emit(b,\"cancel\")}jQuery.data(b,\"obj\",this);var a=null,d=false;c>=0&&$(document).find(\".Wt-popupmenu\").mouseleave(function(){if(d){clearTimeout(a);a=setTimeout(f,c)}}).mouseenter(function(){d=true;clearTimeout(a)})}");
}
}