menu.lzx
<library>
<include href="button.lzx"/>
<include href="base/basescrollbar.lzx"/>
<include href="lz/list.lzx"/>
<include href="lz/textlistitem.lzx"/>
<include href="lz/floatinglist.lzx"/>
<resource name="menuarrow_rsrc">
<frame src="resources/menu/menu_arrow_rt.swf"/>
<frame src="resources/menu/menu_arrow_lft.swf"/>
</resource>
<class name="menutrackgroup" extends="basetrackgroup">
<method name="addSubview" args="sv">
if( !(sv instanceof lz.menu) ){
super.addSubview( sv );
}
</method>
</class>
<class name="menubar" extends="basecomponent" focusable="false">
<attribute name="defaultplacement" value="mbarcontent
" type="string"/>
<attribute name="height" value="20
"/>
<attribute name="width" value="${immediateparent.width + 1}"/>
<attribute name="_clickcounter" value="0
"/>
<attribute name="_openedmenu" value="0
"/>
<view name="_outerbezel" resource="lzbutton_bezel_outer_rsc" stretches="both" width="${parent.width-1}" height="${parent.height-1}"/>
<view name="_innerbezel" resource="lzbutton_bezel_inner_rsc" stretches="both" x="1" y="1" width="${parent.width-3}" height="${parent.height-3}"/>
<view name="_face" resource="lzbutton_face_rsc" stretches="both" x="2" y="2" width="${parent.width-4}" height="${parent.height-4}"/>
<menutrackgroup name="mbarcontent" layout="axis:x; spacing:-2" deactivateevents="[]" height="100%"/>
<method name="openMenu" args="menuref,openit">
if ( menuref == this._openedmenu ){
if ( openit ) return; // don't reopen menu if already opened
else {
this._openedmenu._menubutton.showUp();
this._openedmenu.setOpen( false );
this._openedmenu = 0;
// deactiavte basetracker
this.mbarcontent.deactivateTrackgroup(null);
// set clickcounter back to zero
this._clickcounter = 0;
this._openedmenu = 0;
}
} else {
// about to open/close a different menu so ...
// close the previously opened menu
if ( this._openedmenu ) {
this._openedmenu._menubutton.showUp();
this._openedmenu.close();
}
// now open/close requested menu
if ( menuref != null) {
menuref.setOpen( openit );
if ( openit ) this._openedmenu = menuref
} else this._openedmenu = 0
}
</method>
<method name="_applystyle" args="s">
if (this.style != null) {
setTint( _outerbezel, s.basecolor);
setTint( _innerbezel, s.basecolor);
setTint( _face, s.basecolor);
}
</method>
<doc>
<tag name="shortdesc">
<text>A menubar that contains menus.</text>
</tag>
<text>
<p>
The <tagname>menubar</tagname> tag creates a menubar that can be
placed within any view, but is most commonly found within a window.
By default, a menubar adjusts to the width of its parent unless
otherwise directed. Within a window a menubar has its own
<code>placement="menubar"</code> so that it lives above the contents
of the window.
</p>
<example>
<canvas debug="true" height="250">
<debug y="120"/>
<menubar width="200" >
<menu text="Menu 1" width="100">
<menuitem text="Item 1" onselect="canvas.whichOne(this);"/>
<menuitem text="Item 2" onselect="canvas.whichOne(this);"/>
<menuitem text="Item 3" onselect="canvas.whichOne(this);"/>
<menuseparator/>
<menuitem text="Item 4" onselect="canvas.whichOne(this);"/>
</menu>
<menu text="Menu 2" width="100">
<menuitem text="More items..." onselect="canvas.whichOne(this);"/>
</menu>
</menubar>
<method name="whichOne" args="vThis">
Debug.debug("%w - %w", vThis.parent.owner.text, vThis.text);
</method>
</canvas>
</example>
<p><b>See Also:</b></p>
<ul>
<li>
<sgmltag class="element" role="lzCommand"><command></sgmltag>, <sgmltag class="element" role="lz.menu"><menu></sgmltag>,
<sgmltag class="element" role="lz.menuitem"><menuitem></sgmltag> and <sgmltag class="element" role="lz.simplelayout"><menuseparator></sgmltag>
</li>
</ul>
</text>
</doc>
</class>
<class name="menuarrow" resource="menuarrow_rsrc" x="${parent.width - width - 5}" y="5"/>
<class name="menubutton" extends="button" respondtomouseout="false" text="${this._menuref.text}" height="${parent.height}" doesenter="false" focusable="false">
<attribute name="_menuref" value="null
"/>
<method name="registerEventHandlers">
// ignore the default event handling from the button class.
// new event handling is defined in methods below.
</method>
<handler name="onmousedown">
// a menu button only exists within a menubar so
// toggle menubar's clickcounter between 0 and 1
this.parent._clickcounter += 1; // this.parent is a menubar
if ( this.parent._clickcounter > 1 ) this.parent._clickcounter = 0;
this.showDown();
</handler>
<method name="showDown" args="sd=null">
super.showDown();
if ( this._menuref )
parent.openMenu(this._menuref,true);
</method>
<handler name="onmousetrackup">
// on first click keep the button down
// on second click treat it like a onmouseupinside for the button
if ( this.parent._clickcounter == 0) {
this.showOver();
// close the menu associated with this menuitem
if ( this._menuref )
parent.openMenu( this._menuref, false );
}
</handler>
<handler name="onmouseover">
// if the clickcounter == 1 then a rollover action is like a
// mousedown, otherwise do a normal rollover.
if ( this.parent._clickcounter ) {
this.showDown();
} else {
this.showOver();
}
</handler>
<handler name="onmouseout" method="showout"/>
<method name="showout" args="ignore">
if ( !this.parent._clickcounter ) {
this.showUp();
}
</method>
<handler name="onmousetrackover">
this.showDown();
</handler>
<handler name="onmousetrackout">
//this.myshowup();
</handler>
<method name="toString">
return "MENU BUTTON title =" + this.text;
</method>
</class>
<class name="menuseparator" width="100%" opacity="1" height="6" extends="basecomponent" focusable="false">
<attribute name="xinset" value="3
" type="number"/>
<view x="${parent.xinset}" width="${parent.width - 2*parent.xinset}" bgcolor="black" height="1" y="3" opacity=".5"/>
<view x="${parent.xinset}" width="${parent.width - 2*parent.xinset}" bgcolor="white" height="1" y="4"/>
<method name="_applystyle" args="s">
if (this.style != null) {
//parent.setAttribute('bgcolor',s.menuitembgcolor);
this.setAttribute('bgcolor', s.textfieldcolor);
}
</method>
<doc>
<tag name="shortdesc"><text>
A horizontal separator for pull-down menus.
</text></tag>
<text>
<p>
The <classname>menuseparator</classname> tag provides a
horizontal separator for pull-down menus. This tag must
reside within a <xref linkend="lz.menu"/> tag.
</p>
<p>
For an example, see <xref linkend="lz.menu"/>.
</p>
<p><b>See Also:</b></p>
<ul>
<li>
<sgmltag class="element" role="lz.menubar"><menubar></sgmltag>, <sgmltag class="element" role="lz.menu"><menu></sgmltag>
and <sgmltag class="element" role="lz.menuitem"><menuitem></sgmltag>
</li>
</ul>
</text>
</doc>
</class>
<class name="menuitem" extends="textlistitem" focusable="false">
<attribute name="delaytime" value="300
"/>
<attribute name="command" value="null
" type="expression" setter="this.setCommand( command )" when="once"/>
<event name="onselect"/>
<attribute name="_doshowsubmenu" value="false
"/>
<attribute name="_submenu" value="null
"/>
<attribute name="showsubmenu_del" value="null
"/>
<attribute name="hidesubmenu_del" value="null
"/>
<state name="commandstate" applied="false">
<text name="cmdkeys" x="${parent.width - 90}" y="${classroot.text_y}" width="90"/>
</state>
<method name="init">
super.init();
// now that the menuitem is inited make sure the floatinglist
// constraints are updated by resetting the attachtarget
if ( this._submenu ) this._submenu.flist.setAttribute('attachtarget',this);
</method>
<handler name="onmouseover">
// listitem ( that menuitem extends) is resposible for the visual
// changes when a menuitem is hilited.
// If a menuitem also has a submenu then that is displayed by calling...
if ( _submenu ) this._opensubmenu( true );
</handler>
<handler name="onmouseout">
// hide the submenu if exiting item
// except when moving into the submenu
// x can be variable based on performance of system
// and speed of mouse movement, hence a small buffer
if (this.getMouse('x') < (parent.width-6)) {
if ( _submenu ) this._opensubmenu( false );
}
</handler>
<handler name="onmousetrackover">
// listitem ( that menuitem extends) is resposible for the visual
// changes when a menuitem is hilited.
// If a menuitem also has a submenu then that is displayed by calling...
if ( _submenu ) this._opensubmenu( true );
</handler>
<handler name="onmousetrackout">
// don't need to unhilite listitem, parent view
// will do this when another listitem is hilited
// this is needed for menus
//_ipclassroot.setHilite( null );
</handler>
<handler name="onmousetrackup">
if ( !_submenu && this.enabled ) {
// no submenu so execute the menu command
// and close this menuitem's menu and all of its hierarchy
parent.owner.open( false );
if ( command ) command.onselect.sendEvent();
if ( this.onselect ) this.onselect.sendEvent();
}
// else do nothing because hiliting this menuitem
// has already opened the submenu if there is one
</handler>
<method name="setHilite" args="ishilite">
super.setHilite( ishilite );
if ( !_initcomplete ) return;
if (ishilite){
if ( _submenu ) this._menuarrow.setAttribute('fgcolor',this.style.texthilitecolor);
} else {
if ( _submenu ) {
this._menuarrow.setAttribute('fgcolor',0x000000);
this._opensubmenu( false );
}
}
</method>
<method name="showSubmenu" args="ignore">
// this method is called by a delegate which was activated by a timer.
// First, make sure we still want to show the submenu by checking
// the _doshowsubmenu flag.
if ( _doshowsubmenu ) this._submenu.setOpen(true);
</method>
<method name="hideSubmenu" args="ignore">
if ( !_doshowsubmenu ) this._submenu.setOpen(false);
</method>
<method name="_opensubmenu" args="doopen">
if ( doopen ) {
this._doshowsubmenu = true;
if (!this.showsubmenu_del) this.showsubmenu_del = new LzDelegate(this,'showSubmenu');
lz.Timer.addTimer( this.showsubmenu_del, delaytime );
} else {
this._doshowsubmenu = false;
if (!this.hidesubmenu_del) this.hidesubmenu_del = new LzDelegate(this,'hideSubmenu');
lz.Timer.addTimer( this.hidesubmenu_del, delaytime );
}
</method>
<method name="setSelected" args="isSelected">
// menus don't keep a selection in this version
// so override this method to block the list's
// selection manager.
// super.setSelected( isSelected );
</method>
<method name="setCommand" args="cmd">
this.command = cmd;
if ( this.command ) {
// force the appplication of this state
// because order of execution demands it.
this.commandstate.apply();
var t = command.keysToString();
this.cmdkeys.setAttribute('text', t);
}
</method>
<method name="toString">
return "menuitem text =" + this.text;
</method>
<method name="_showEnabled">
if (_initcomplete) {
_applystyle(this.style);
}
</method>
<method name="_applystyle" args="s">
if (this.style != null) {
super._applystyle(s);
if (command) {
if (_enabled) {
if (hilited) {
this.cmdkeys.setAttribute('fgcolor', style.texthilitecolor);
}
else if (selected) {
this.cmdkeys.setAttribute('fgcolor', style.textselectedcolor);
} else {
this.cmdkeys.setAttribute('fgcolor', style.textcolor);
}
} else {
this.cmdkeys.setAttribute( "fgcolor", style.textdisabledcolor );
}
}
}
</method>
</class>
<class name="menufloatinglist" extends="floatinglist" bgcolor="0x445566" autoscrollbar="false" visible="false" focusable="false">
<attribute name="issubmenu" value="false
"/>
<attribute name="_updateifsubmenu" value="true
"/>
<method name="setHilite" args="v">
super.setHilite( v );
if ( v ) {
if ( issubmenu ) {
// because this floatinglist belongs to a menu that is actually a submenu
// we need to rehilite the menuitem that owns this submenu.
// owner.parent = the menuitem that owns this submenu
// owner.parent.parent = floating list that contains that menuitem.
// however, we only want to do this once, not everytime we hilite
// a mnenuitem within the submenu, so frist check to see if we
// need to update those objects that own and manage this usbmenu.
if ( _updateifsubmenu ) {
owner.parent._doshowsubmenu = true;
owner.parent.parent.setHilite( owner.parent );
// we did it so now don't do again until this menu
// is closed and reopened.
this._updateifsubmenu = false;
}
}
} else { // need to clear hilite
if ( _hiliteview ) {
this._hiliteview.setHilite( false );
this._hiliteview= null;
}
}
</method>
<handler name="onmousetrackout" reference="this.interior">
this.setHilite(null);
</handler>
</class>
<class name="menu" width="150" extends="basecomponent" focusable="false">
<attribute name="text" value="menutitle
" type="html"/>
<attribute name="opened" value="false
" setter="this.setOpen( opened )" type="boolean"/>
<event name="onopened"/>
<attribute name="menu_ref" value="false
"/>
<attribute name="flist" value="null
"/>
<attribute name="attach" value="bottom
" type="string"/>
<handler name="onattach">
if (this.flist != null)
this.flist.setAttribute("attach", this.attach);
</handler>
<attribute name="_menuitems" value="null
"/>
<setter name="width" args="w">
super.setAttribute("width", w);
if (this.flist != null) {
this.flist.setAttribute("width", w);
}
</setter>
<method name="init">
// create a floating list. Though the parent for this floating
// list is this menu, the floating list will be created on the
// canvas and its owner attribute will point to this menu.
var w = width;
if ( (this.parent) instanceof lz.menubar) {
// menu's parent is a menubar so create a floating list
// with the following parameters...
// create a floating list that will attach to the bottom
// of the menubutton by default ( the floating list is
// canvas-aware and will try to display itself below the menubutton ).
// if this does not work, then it will try above it.
this.flist = new lz.menufloatinglist(this,
{ attach:'bottom',
attachoffset:-2,
width:w,
shadowcrn:1 }, this._menuitems);
// border_top:0, border_bottom:0,
} else if ( (this.parent) instanceof lz.menuitem ) {
// menu's parent is a menuitem so attach the floatinglist to
// the right ( again canvas-aware will try right but display left
// if necessary )
this.flist = new lz.menufloatinglist(this,
{ attach:'right',
width:w,
issubmenu:true,
shadowcrn:2 }, this._menuitems);
} else {
// menu's parent is some other type of view so attach the
// floatinglist to the value of 'attach' attribute
var atch = this.attach;
this.flist = new lz.menufloatinglist(this,
{ attach:atch,
width:w,
shadowcrn:1 }, this._menuitems);
}
// By default the floating list's target will be constrained to the
// menu's parent. This private variable wil be change if necessary
this._flisttarget = this.parent;
if ( parent instanceof lz.menubar ) {
// Since the parent is a menubar ...
// Create a menubutton and have the menu keep a reference to it.
// Likewise, have the menubutton keep a reference to this menu
this._menubutton = new lz.menubutton(parent, {_menuref:this}, null, false);
// Have the floating list constrain to the menubutton and not
// the parent of the menu ( which would have been menubar in
// this case )
this._flisttarget = this._menubutton;
} else if ( parent instanceof lz.menuitem ) {
// insert an arrow into the target
new lz.menuarrow( this._flisttarget, { name:'_menuarrow' } );
// have the menuitem keep a reference to its submenu
this.parent._submenu = this;
}
// need to wait until here when the width and heights of the target
// set before I activate the constraints
super.init();
this.flist.setAttachTarget(this._flisttarget);
</method>
<method name="createChildren" args="childrenarray">
this._menuitems = childrenarray;
super.createChildren( [] );
</method>
<method name="childOfMenuHierarchy" args="v">
var result = v.childOf( this ) || v.childOf( flist );
if ( result ) return true; //don't need to check any further
// Now check to make sure that mousedown has not occurred in another
// part of the menu hierarchy
if ( v instanceof lz.menuitem ) {
var aMenuRef = v.parent.owner;
while ( aMenuRef instanceof lz.menu) {
result = ( this == aMenuRef );
if (result) break;
if (aMenuRef.parent.parent['owner'] == null) break;
aMenuRef = aMenuRef.parent.parent.owner;
}
}
return result;
</method>
<method name="passModeEvent" args="event,view">
// Once a view has been made modal, this method
// gets called by the system ONLY when a user clicks on
// a view 'outside' the contents of this view.
if ( event == 'onmousedown' ) {
if ( view != null ) { // view is a clickable view
// view is not LITERALLY part of the class hierarchy but
// it maybe part of the floatingview of this menu, or part
// of the parent view that contains this menu
var inMenu = this.childOfMenuHierarchy( view ) ||
view.childOf( parent );
if ( !inMenu ) {
// view is REALLY outside of the menu so close the menu
this.open(false);
}
} else { // view is not a clickable view so close the menu
this.open(false);
}
}
// since a menu is not strictly modal ( 'fragile' ), always return
// true to pass the event to the object that was clicked on
return true;
</method>
<method name="getTopMenu">
// this is called by a menu so start with this
var mc = this;
// iterate up the parent chain starting with this menu's parent
var mcp = mc;
while ( mcp !== canvas ){
if ( mcp instanceof lz.menu ){
mc = mcp;
}
// in case view is contained within a floating list
if ( mcp['owner'] ) mcp = mcp.owner;
else mcp = mcp.parent;
}
// return this or another menu that own's this one
return mc;
</method>
<method name="open" args="openit">
// this is called by a menu so start with this
var mc = this;
// iterate up the parent chain starting with this menu's parent
// and as we iterate open/close as we go
var mcp = mc;
while ( mcp != canvas ){
if ( mcp instanceof lz.menu ){
mc = mcp;
mc.setOpen( openit );
} else if ( mcp instanceof lz.menubar){
mcp.openMenu( mc, openit )
}
// in case view is contained within a floating list
if ( mcp['owner'] ) mcp = mcp.owner;
else mcp = mcp.parent;
}
</method>
<method name="close">
var mi;
for (var k=0; k < this.flist.interior.content.subviews.length ; k++) {
mi = this.flist.interior.content.subviews[k];
if ( mi instanceof lz.menuitem){
if ( mi._submenu ) mi._submenu.close();
}
}
this.setOpen(false);
</method>
<method name="setOpen" args="isopened">
if ( _initcomplete ){
if ( this.opened == isopened ) return;
this.opened = isopened;
if ( isopened ){
lz.ModeManager.makeModal( this );
this.flist.bringToFront();
this.flist.setHilite(null);
this.flist.setAttribute('tracking',true);
this.flist._updateifsubmenu = true;
this.flist.setAttribute('visible', true);
} else {
lz.ModeManager.release( this );
this.flist.setHilite(null);
this.flist.setAttribute('tracking',false);
this.flist.setAttribute('visible', false);
}
if ( onopened ) this.onopened.sendEvent( this.opened );
} else this.opened = isopened;
</method>
<method name="toggle">
this.setOpen(!this.opened);
</method>
<doc>
<tag name="shortdesc"><text>
A pull-down menu that can be used within a menubar, a menuitem or any other view.
</text></tag>
<text>
<p>The <tagname>menu</tagname> tag creates a menu that floats above
all other views when activated. If a menu is a subview of a
<tagname>menubar</tagname>, then it will create a
"menubutton" in the menubar with the its
<attribute>text</attribute> attribute equal to that of the menu's. This "menubutton"
inherits its properties from <tagname>button</tagname>.
</p>
<p>
typical use of a menu is shown below, with its children of <tagname link="true">menuitem</tagname>s and <tagname link="true">menuseparator</tagname>s. Note, however, that the parent
of a menuitem is not the menu itself, but rather a <tagname link="true">floatinglist</tagname> that is implicity part of the menu.
To access the menu from a menuitem (or any other subview) use the
reference "<code>parent.owner</code>".
</p>
<example title="using the menubar">
<canvas debug="true" height="230">
<debug y="100"/>
<menubar width="200" >
<menu text="Menu 1" width="100">
<menuitem text="Item 1" onselect="canvas.whichOne(this);"/>
<menuitem text="Item 2" onselect="canvas.whichOne(this);"/>
<menuitem text="Item 3" onselect="canvas.whichOne(this);"/>
<menuseparator/>
<menuitem text="Item 4" onselect="canvas.whichOne(this);"/>
</menu>
<menu text="Menu 2" width="100">
<menuitem text="More items..." onselect="canvas.whichOne(this);"/>
</menu>
</menubar>
<method name="whichOne" args="vThis">
Debug.debug("%w - %w", vThis.parent.owner.text, vThis.text);
</method>
</canvas>
</example>
<example title="simple menu">
<canvas debug="true" height="235">
<debug y="105"/>
<button text="openMenu below">
<menu name="topmenu" attach="bottom">
<menuitem text="item 1"/>
<menuitem text="item 2">
<menu name="submenu">
<menuitem text="subitem 1"/>
<menuitem text="subitem 2"/>
</menu>
</menuitem>
</menu>
<handler name="onmousedown">
this.topmenu.setOpen(true);
</handler>
</button>
</canvas>
</example>
<seealso>
<tags>command menuitem menubar menuseparator floatinglist</tags>
<component-design id="menus" title="Menus"/>
</seealso>
</text>
</doc>
</class>
</library>
Cross References
Includes
Resources
Classes
Named Instances
- <component-design id="menus">