Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

Library: MultiMenu (Nested Dialog handler)

Soen Eber
Registered User
Join date: 3 Aug 2006
Posts: 428
09-04-2008 17:34
I start off with an extensive tutorial writeup to cut the learning curve for everyone. What I have documents the most common feature set.

Let me know if you spot any showstopping bugs and I'll address them as I am able.

Proposed enhancements: Right now the code parses the menu on each invocation, even when called from within the main logic. This could be optimized. Also, the code uses a lot of list management and its possible that could be optimized; I didn't test for memory leaks from sloppy list management or run this through any abusive testing.

While the code has been pretty stable for a while, it does perform some pretty complex stuff. Not Strife's level of complexity but don't put this into a commercial product until the code has been road tested some more.


MultiMenu User's Guide

MultiMenu is a LSL script for managing menus using link messages. It is designed to be "fire and forget", in that the calling script passes menu parameters to MultiMenu and accepts back the user response. Except for a minimal framework to handle link messages and responses, no other coding is required.

With the help of an embedded pragma language, MultiMenu scripts can be fine-tuned to mimic several behaviors, such as:

- A conventional menu, selecting one item from a list
- Multiple selection menus
- Nested menus with unique dialog text at each level
- Multi-page menus, allowing users to scroll over several pages of items
- ATM style interface allowing for PIN entry
- Menus showing defaulted values
- Locked menu items (useful for displaying status)
- "OK/Cancel" menu items, allowing menu selections to be cleared or reset to default values
- Menus with branch logic based on the selected response (useful for quizes or to guide a user through a set scenario such as a troubleshooting guide)

Lesson 1: A simple menu
-----------------------

A sample call for a single-item dialog menu follows. Here, a dialog box pops up with the message "select a color" and the menu items red, green, & blue. Selecting any color closes the dialog, and "You selected red" (or green, or blue) is said in chat. The dialog waits for 10 seconds before timing out, and if it does so "You did not select anything" is also said in chat.

In the sample, the code for the on_rez and changed events is not required, but is a generally good thing to have.

MultiMenu uses a standardized link message protocol to communicate addressing information, commands, and command arguments throughout an object's link set. Each element is space separated, so natural word breaks should use underscores or MixedCase to separate words.

The protocol takes four arguments:
- source
- target
- command
- arguments
Source & Target are software defined - they do not default to the name of the prim. Rather, think of them as service names.
Source is defined by your script
Target, Commands and arguments are defined by MultiMenu. Here is an example:

llMessageLinked(
LINK_SET,0,"test_single MultiMenu_color show select a color;red,green,blue;10",
llDetectedKey(0)
);

In this example, you define the source as "test_single", while MultiMenu uses the hardcoded target "MultiMenu" as the target and "show" as the command. Only the "MultiMenu" part of the command is required, although it is highly recommended that you expand on this for the specific menu, as with "MultiMenu_color". The argument for the "show" command accepts a 3 part argument set, deliminated by semi-colons:
message;menu;timeout
- Message is the text displayed
- Menu is a list of menu items to display for the user to select. For this example the menu is "red,green,blue". More advanced menus may have nested levels, level-specific message text, pragmas, and/or branch logic.
- Timeout is how long the menu dialog waits before timing out. In this case it is 10 seconds.

The response from the MultiMenu show command is a message string:
"MultiMenu_color <target> response <args>"

The link_message event needs to parse the addressing information, command, and argument list. Any message received whose target is the calling script (as identified in an earlier llMessageLinked function call) should be processed.

Address identity is handled exclusively in the llMessageLinked function call and the corresponding link_message events. Compare

LINK_SET,0,"test_single MultiMenu_color show select a color;red,green,blue;10",
to
if (tgt == "test_single";) {
if ((src == "MultiMenu_color";) && (cmd == "response";)) {

in the code below. For this example, then, the MultiMenu dialog response is "MultiMenu_color test_single response <args>"

<args> is a list of all the selected menu items, delimited by semi-colons. In this example the user may only choose one color, say "red", so the response string returned is "MultiMenu_color test_single response red"

CODE

default
{
state_entry()
{
}
on_rez(integer startup_param)
{
llResetScript();
}
changed(integer change)
{
if(change & CHANGED_OWNER) {
llResetScript();
}
}
touch_start(integer total_number)
{
llMessageLinked(
LINK_SET,0,"test_single MultiMenu_color show select a color;red,green,blue;10",
llDetectedKey(0)
);
}
link_message(integer source, integer num, string msg, key id)
{
list lMsg = llParseString2List(msg, [" "], []);
string src = llToLower(llList2String(lMsg,0));
string tgt = llToLower(llList2String(lMsg,1));
string cmd = llToLower(llList2String(lMsg,2));
string arg = llList2String(lMsg,3);
if (tgt == "test_single") {
if ((src == "MultiMenu_color") && (cmd == "response")) {
if (arg == "red") llSay(0,"You selected red");
if (arg == "green") llSay(0,"You selected green");
if (arg == "blue") llSay(0,"You selected blue");
if (arg == "") llSay(0,"You did not select anything");
}
}
}
}


Lesson 2: A simple multiple choice menu
---------------------------------------

Now let us look at a multiple choice menu (below). The llMessageLinked call changes to:

llMessageLinked(
.
.
+"red~np,green~np,blue~np,ok~sn;10",
.
);
Contrast this to the previous: "red,green,blue;10". The red,green & blue menu items are followed by a ~np pragma, and there is a new menu item "ok" with its own ~sn pragma.

Introducing pragmas
Pragmas are menu directives which change the default behavior of a menu. This behavior is itself defined through the use of default pragmas, which may be overridden by assigning a new pragma at each menu item. There are three general types of pragmas: navigation, storage, and group behavior.

When a menu item is normally selected, its default pragmas are ~nx~si, meaning it should close the menu and return the selected item. Its parent's pragmas are ~ns~sn, meaning don't save the selected item but instead open its menu. MultiMenu differentiates between menu items and their parents by whether or not a menu item has a submenu. Let's explore these pragmas individually:
~nx Navigate eXit Close the menu and return the list of selected items to the calling script
~ns Navigate Selected Open up the selected item's menu
~np Navigate Parent Open up the selected item's parent's menu (essentially no change)
~si Store Item Add the selected item to the selection list
~sn Store No Do not add the selected item to the selection list
Pragmas may be shown in any order. Storage pragmas will always be executed before navigation pragmas, and group behavior pragmas determine how storage pragmas operate.

Let's return to what started this. In the code sample, ~np prevents the menu from closing when an item is selected, and the menu is redisplayed. Notice an asterisk preceeds all selected items as a visual aid. When you select an item a 2nd time, you will see that the asterisk disappears. This is a selection toggle. Only selected items will be returned to the calling script when the menu closes.

Notice the ok item has a ~sn pragma. That is because your script does not really need to know that the user pressed the OK button, so "ok" is not included in the selection list. You will also see there is no navigation override associated with the ok button; the menu will close and return its selection list to the calling script when this is selected.

Now lets look at the link_message event. The most significant change here is the inclusion of a for loop. With multiple items being selected, you'll need to look at each item returned.

When only one item could be selected, the selection list looks like "red", or "green", or "blue". With multiple items, the list is now delimited with semi-colons. Selecting red and blue returns "red;blue" as the selection list.

CODE

default
{
state_entry()
{
}
on_rez(integer startup_param)
{
llResetScript();
}
changed(integer change)
{
if(change & CHANGED_OWNER) {
llResetScript();
}
}
touch_start(integer total_number)
{
llMessageLinked(
LINK_SET,0,
"test_multiple MultiMenu_color show select one or more colors, press ok when done;"
+"red~np,green~np,blue~np,ok~sn;10",
llDetectedKey(0)
);
}
link_message(integer source, integer num, string msg, key id)
{
integer i;

list lMsg = llParseString2List(msg, [" "], []);
string src = llToLower(llList2String(lMsg,0));
string tgt = llToLower(llList2String(lMsg,1));
string cmd = llToLower(llList2String(lMsg,2));
string arg = llList2String(lMsg,3);
if (tgt == "test_multiple") {
if ((src == "MultiMenu_color") && (cmd == "response")) {
list l = llParseString2List(arg,[";"],[]);
for (i=0; i<llGetListLength(l); i++) {
string s = llList2String(l,i);
if (s == "red") llSay(0,"You selected red");
if (s == "green") llSay(0,"You selected green");
if (s == "blue") llSay(0,"You selected blue");
}
)
}
}
}


Lesson 3: A simple nested Menu
------------------------------

One of the key features of MultiMenu is the ability to encode a nested or hierarchical menu structure, such as the one shown below.

color
red
green
blue
alpha
20%
80%
OK

The llMessageLinked call for this one is:

llMessageLinked(
.
.
+"select colors and transparency(alpha) level, press ok when done;"
+"color(red~npp,green~npp,blue~npp)alpha(20%~npp,80%~npp),OK~sn;"
.
.
);

The prompt "select colors and transparency..." is displayed, along with three choices: color, alpha, and OK. Note that both color and alpha enclose elements in parenthesis. These are their respective submenu items. For color, the submenu is red, green, and blue, but each has a ~npp pragma, which is new to us. In the earlier multiple choice menu, ~np was used to navigate to the selected item's parent. ~npp, however, navigates to and opens the selected item's grandparent (parent's parent), effectively closing the color menu (red,green,blue) and returning control to the higher level menu (color,alpha,OK). Logic for the alpha menu behaves similarly. This does not mean nested menus cannot have multiple selections, but for simplicity's sake this lesson focuses on what is new. Finally, we have the familiar OK~sn menu item, which closes the menu and returns the selected items.

What gets returned? If the user selects red and 20%, the returned selection list is not "red;20%" as one would expect from the previous lesson. Instead, the menu's full path is returned: "color:red;alpha:20%". The individual items are still delimited by semicolons, but each item also contains the full menu path, delimited by colons. If you had a nested menu where someone could select the colors of both their shirt and their pants, for example, you would want to know that "red" meant a red shirt, and "blue" meant a blue pair of pants.

The link_message event expands somewhat to deal with the nested menu structure. In this example, llGetSubString tests are used to extract the parent level (color or alpha) and then direct string comparisons are used to test individual items. The initial comparison for "color" is merely a convenience that allows you to group similar code together -- removing it would simplify the code with no ill effect. There are no hard and fast rules for handling the returned menu selections, so future lessons will explore alternative methods.

if (llGetSubString(s,0,4) == "color";) {
if (s == "color:red";) llWhisper(0,"You selected red";);
if (s == "color:green";) llWhisper(0,"You selected green";);
if (s == "color:blue";) llWhisper(0,"You selected blue";);
}

CODE

default
{
state_entry()
{
}
on_rez(integer startup_param)
{
llResetScript();
}
changed(integer change)
{
if(change & CHANGED_OWNER) {
llResetScript();
}
}
touch_start(integer total_number)
{
llMessageLinked(
LINK_SET,0,
"test_nested MultiMenu_nested show "
+"select colors and transparency(alpha) level, press ok when done;"
+"color(red~npp,green~npp,blue~npp)alpha(20%~npp,80%~npp),OK~sn;"
+"10",
llDetectedKey(0)
);
}
link_message(integer source, integer num, string msg, key id)
{
integer i;
list lMsg = llParseString2List(msg, [" "], []);
string src = llToLower(llList2String(lMsg,0));
string tgt = llToLower(llList2String(lMsg,1));
string cmd = llToLower(llList2String(lMsg,2));
string arg = llList2String(lMsg,3);

if (tgt == "test_nested") {
if ((src == "MultiMenu_nested") && (cmd == "response")) {
list l = llParseString2List(arg,[";"],[]);

for (i=0; i<llGetListLength(l); i++) {
string s = llList2String(l,i);
if (llGetSubString(s,0,4) == "color") {
if (s == "color:red") llWhisper(0,"You selected red");
if (s == "color:green") llWhisper(0,"You selected green");
if (s == "color:blue") llWhisper(0,"You selected blue");
}
if (llGetSubString(s,0,4) == "alpha") {
if (s == "alpha:20%") llWhisper(0,"You selected 20%");
if (s == "alpha:80%") llWhisper(0,"You selected 80%");
}
if (s == "") llWhisper(0,"You did not select anything");
}

}
}
}
}


Lesson 4: Expanding on the nested menu
--------------------------------------

That last menu in lesson 3 was pretty basic. Let's see what can be done to improve it. This new lesson:
- changes the menu prompt at each nesting level
- sets a default color & alpha
- improves the multiple-select for colors
- puts the user immediately at the alpha menu selection once color(s) are selected
- limits alpha selection to a single choice

Here's the menu:

llMessageLinked(
.
.
+"select colors and transparency(alpha) level, press OK when done;"
+"color('Select one or more colors, select OK when done'"
+"red~np,green~np,*blue~np,OK~sn~n=alpha)"
+"alpha~gr('Select transparancy or OK when done'"
+"*20%~np,40%~np,60%~np,80%~np,100%~np,OK~sn~npp)OK~sn;"
.
.
);

There are 3 menu prompts now, one when the menu is first displayed, and two more related to the color and alpha sub-menus. Menu prompts immediately follow the opening parens and are enclosed in single quotes. That's pretty much it.

Look at the third and fifth lines, "*blue~np" and "*20%~np". The asterisks mark those menu items as defaults. When the user first enters the color sub-menu blue will be indicated with an asterisk, and by pressing OK "color:blue" will be included in the selection list passed back to the calling script. Of course, they can also deselect blue and choose another color, that functionalty does not change.

The color menu returns to what it was in the multi-select lesson (lesson 2) using ~np instead of ~npp to make it easier to select multiple colors. What changes, however, is the OK menu item. It is now "OK~sn~n=alpha". The ~sn you recognize as indicating that "OK" won't be added to the selection list, but "~n=alpha" is new to you. ~n= is a branch pragma. When OK is selected, MultiMenu navigates to the alpha menu and shows its submenu. This way, the user is steered to selecting an alpha menu item. Using ~n=, you can create a guided sequence of menus from a single call to MultiMenu.

You will learn in a future lesson on creating a quiz how to combine ~n= with ~nh (Navigation Hide) for a more powerful application which utilizes branch logic in a menu.

The final change in the menu is the addition of the ~gr pragma to the alpha menu item. This limits the group behavior to "radio style" selection, as opposed to the normal "checkbox style" selection which allowed you create multiple-selection menus. The ~gr menu item applies to all menu items immediately below the parent menu item, in that only a single item may be selected. This makes sense for the example, because while a prim can be any combination of colors, there may only be one alpha (transparency) level.



CODE

default
{
state_entry()
{
}
on_rez(integer startup_param)
{
llResetScript();
}
changed(integer change)
{
if(change & CHANGED_OWNER) {
llResetScript();
}
}
touch_start(integer total_number)
{
llMessageLinked(
LINK_SET,0,
"test_nested2 MultiMenu_nested show "
+"select colors and transparency(alpha) level, press OK when done;"
+"color('Select one or more colors, select OK when done'"
+"red~np,green~np,*blue~np,OK~sn~n=alpha)"
+"alpha~gr('Select transparancy or OK when done'"
+"*20%~np,40%~np,60%~np,80%~np,100%~np,OK~sn~npp)OK~sn;"
+"10",
llDetectedKey(0)
);
}
link_message(integer source, integer num, string msg, key id)
{
llOwnerSay("lm heard "+msg);
integer i;
list lMsg = llParseString2List(msg, [" "], []);
string src = llToLower(llList2String(lMsg,0));
string tgt = llToLower(llList2String(lMsg,1));
string cmd = llToLower(llList2String(lMsg,2));
string arg = llList2String(lMsg,3);

if (tgt == "test_nested2") {
if ((src == "MultiMenu_nested") && (cmd == "response")) {
list l = llParseString2List(arg,[";"],[]);

for (i=0; i<llGetListLength(l); i++) {
string s = llList2String(l,i);
if (llGetSubString(s,0,4) == "color") {
if (s == "color:red") llWhisper(0,"You selected red");
if (s == "color:green") llWhisper(0,"You selected green");
if (s == "color:blue") llWhisper(0,"You selected blue");
}
if (llGetSubString(s,0,4) == "alpha") {
if (s == "alpha:20%") llWhisper(0,"You selected 20%");
if (s == "alpha:40%") llWhisper(0,"You selected 40%");
if (s == "alpha:60%") llWhisper(0,"You selected 60%");
if (s == "alpha:80%") llWhisper(0,"You selected 80%");
if (s == "alpha:100%") llWhisper(0,"You selected 100%");
}
if (s == "") llWhisper(0,"You did not select anything");
}

}
}
}

}



pragma list



Group
~gc group checkbox
~gr group radio

Navigation
~n= navigate to named menu item
~nh hide menu item
~np navigate to parent
~npp navigate to parent's parent
~nr navigate to root
~ns navigate to selection
~nx close menu dialog

Storage
~s+ add selection to accumulator
~sa assign accumulator to parent
~si store selection
~sn do not store selection

Clear
~cp clear parent selections
~cr clear all selections
~ct clear tree selections

Revert
~rp revert parent selections to defaults
~rr revert all selections to defaults
~rt revert tree selections to defaults
Soen Eber
Registered User
Join date: 3 Aug 2006
Posts: 428
09-04-2008 17:34
MultiMenu Code
CODE

// Multimenu: Multi-Page Nested Dialog control, 4/5/2008 by Soen Eber
// See user manual (posted to Secondlife.com's scripting forums) for usage
// Free for public use so long as this header is included.

string gSrc;
string gTgt;
string gMsg;
key gID;
integer dlg_channel;
integer dlg_listenHandle;
list gDlgArguments;
integer gCurPage = 1;
list gStack;
string gParent;
list glMenu;
list glSubMenu;
list glDisplayMenu;
list glMenuText;
list glMenuPragmas;
list glMenuSelections;
list glMenuSelectionsRestore;
string sAccum;

clear_globals()
{
gSrc="";
gTgt="";
gMsg="";
gID=NULL_KEY;
gDlgArguments=[];
gCurPage = 1;
gStack = [];
gParent = "";
glMenu = [];
glSubMenu = [];
glDisplayMenu = [];
glMenuText = [];
glMenuPragmas = [];
glMenuSelections = [];
glMenuSelectionsRestore = [];
sAccum = "";
}
string lPop()
{
string s = llList2String(gStack,0);
gStack = llDeleteSubList(gStack,0,0);
return s;
}
string rPop()
{
string s = llList2String(gStack,-1);
gStack = llDeleteSubList(gStack,-1,-1);
return s;
}
string lPeek()
{
return llList2String(gStack,0);
}
string rPeek()
{
return llList2String(gStack,-1);
}
lPush(string item)
{
gStack = item+gStack;
}
rPush(string item)
{
gStack = gStack+item;
}
lDel()
{
gStack = llDeleteSubList(gStack,0,0);
}
rDel()
{
gStack = llDeleteSubList(gStack,-1,-1);
}
integer contains(string s, string p)
{
if (llSubStringIndex(s,p) > -1) return TRUE;
return FALSE;
}
string get_parent(string menuItem, integer generation)
{
list l = llParseString2List(menuItem,[":"],[]);
l=llDeleteSubList(l,-generation,-1);
return llDumpList2String(l,":");
}
string get_child(string menuItem)
{
list l = llParseString2List(menuItem,[":"],[]);
return llList2String(l,-1);
}
string get_pragma(string menuItem)
{
integer i = llListFindList(glMenuPragmas, [menuItem]);
if (i != -1) return llList2String(glMenuPragmas,i+1);
else return "";
}
add_menu(string parent, string s, string pragma)
{
string menuItem;
integer selected = FALSE;
if (s != "") {
if (llGetSubString(s,0,0) == "*") {
s = llGetSubString(s,1,-1);
selected = TRUE;
}
if (parent == "") menuItem = s;
else menuItem = parent+":"+s;
glMenu += menuItem;
if (selected) glMenuSelections += menuItem;
if (pragma != "") {
glMenuPragmas += [menuItem, pragma];
}
}
}
parse_menu(string sMenu)
{
string token;
integer i;
integer len;
integer level=0;
string c;
string sParent="";
string text;
string pragma;
integer in_text = FALSE;
integer escaped = FALSE;

len = llStringLength(sMenu);
for (i=0; i<len; i++) {
c = llGetSubString(sMenu,i,i);
if (c == "'") {
if (in_text == FALSE) {
in_text = TRUE;
}
else {
glMenuText += [sParent,text];
in_text = FALSE;
text = "";
}
}
else {
if (in_text) {
text += c;
}
else if (escaped == TRUE) {
pragma += c;
if (contains(",()",llGetSubString(sMenu,i+1,i+1))) {
escaped = FALSE;
}
}
else if (c == "~") {
escaped = TRUE;
pragma += c;
if (contains(",()",llGetSubString(sMenu,i+1,i+1))) {
escaped = FALSE;
}
}
else if (c == ",") {
add_menu(sParent,token,pragma);
token="";
pragma="";
}
else if (c == "(") {
add_menu(sParent,token,pragma);
rPush(token);
sParent = llDumpList2String(gStack,":");
level++;
token="";
pragma="";
}
else if (c == ")") {
add_menu(sParent,token,pragma);
rDel();
sParent = llDumpList2String(gStack,":");
level--;
token="";
pragma="";
}
else token += c;
}
}
add_menu(sParent,token,pragma);
if ((level != 0) || (in_text == TRUE)) {
llWhisper(0,"Parsing error in nested menu");
glMenu = [];
}
glMenuSelectionsRestore = glMenuSelections;
}
list sub_menu(list lMenu, string sParent)
{
integer i;
integer len = llGetListLength(lMenu);
list lSub = [];

for (i=0; i<len; i++) {
string menuItem = llList2String(lMenu,i);
if (sParent == get_parent(menuItem,1)) {
string sPragma = get_pragma(menuItem);
if (contains(sPragma, "~nh") == FALSE) {
lSub += menuItem;
}
}
}
return lSub;
}
string sub_text(string sMenuItem)
{
integer i;
integer len = llGetListLength(glMenuText);
list lSub = [];

for (i=0; i<len; i+=2) {
if (sMenuItem == llList2String(glMenuText,i)) {
return llList2String(glMenuText,i+1);
}
}
return "";
}
integer strindex_i(string s_in, string c_in)
{
integer i = -1;
integer n = llStringLength(s_in);
for (i = n; i>-1; i--) {
if (llGetSubString(s_in, i, i) == c_in) return i;
}
return -1;
}
list get_display_menu(list lMenu)
{
integer i;
integer len = llGetListLength(lMenu);
list l;
for (i=0; i<len; i++) {
string selected = "";
string menuItem = llList2String(lMenu,i);
if (llListFindList(glMenuSelections, [menuItem]) != -1) {
selected = "*";
}
l += (selected+get_child(menuItem));
}
return l;
}
list strip_selected(list lMenu)
{
string menuItem;
integer i;
integer len = llGetListLength(lMenu);
list l;
for (i=0; i<len; i++) {
menuItem = llList2String(lMenu,i);
if (llGetSubString(menuItem,0,0) == "*") l+=llGetSubString(menuItem,1,-1);
else l+=menuItem;
}
return l;
}
store_check_selection(string menuItem)
{
integer i=llListFindList(glMenuSelections,[menuItem]);
if (i == -1) glMenuSelections += [menuItem];
else glMenuSelections = llDeleteSubList(glMenuSelections,i,i);
}
clear_selection_tree(string menuParent)
{
integer i;
list l=[];
integer len = llStringLength(menuParent);
if (menuParent != "") {
for (i=0; i<llGetListLength(glMenuSelections); i++) {
string s = llList2String(glMenuSelections,i);
if (llGetSubString(s,0,len-1) != menuParent) l += s;
}
}
glMenuSelections = l;
}
restore_selection_tree(string menuParent)
{
clear_selection_tree(menuParent);
integer i;
integer len = llStringLength(menuParent);
if (menuParent == "") glMenuSelections = glMenuSelectionsRestore;
else {
for (i=0; i<llGetListLength(glMenuSelectionsRestore); i++) {
string s = llList2String(glMenuSelectionsRestore,i);
if (llGetSubString(s,0,len-1) == menuParent) {
glMenuSelections += s;
}
}
}
}
store_radio_selection(string menuItem)
{
clear_selection_tree(get_parent(menuItem,1));
glMenuSelections += menuItem;
}
list order_buttons(list buttons, string btnPrev, string btnMore)
{
list l;
integer i;
for (i =0; i<llGetListLength(buttons); i++) {
if (i == 9) {
if (btnPrev != "") l += "...Prev";
}
l += llList2List(buttons, i, i);
}
if (btnMore != "") l+= "More...";

return llList2List(l, -3, -1) + llList2List(l, -6, -4)
+ llList2List(l, -9, -7) + llList2List(l, -12, -10);
}
finish()
{
llListenRemove(dlg_channel);
llSetTimerEvent(0.0);
gMsg = llDumpList2String(glMenuSelections,";");
llMessageLinked(LINK_SET,0,gTgt+" "+gSrc+" response "+gMsg,"");
}
show(string sParent)
{
gMsg="";
string btnMore = "";
string btnPrev = "";
integer iFirstButton;
integer iLastButton;
integer numPages;
integer numButtons;
integer i;

string dlg_msg_root;
string dlg_msg_display;
string str_btns;
integer dlg_wait;
list dlg_page_btns;

if (glMenu == []) {
str_btns = llList2String(gDlgArguments,1);
if (str_btns == "") {
finish();
return;
}
parse_menu(str_btns);
}
dlg_msg_root = llList2String(gDlgArguments,0);
dlg_wait = llList2Integer(gDlgArguments,2);
glSubMenu = sub_menu(glMenu, sParent);
glDisplayMenu = get_display_menu(glSubMenu);
dlg_msg_display = sub_text(sParent);
if (dlg_msg_display == "") dlg_msg_display = dlg_msg_root;

if (dlg_wait == 0) dlg_wait = 10;

numButtons = llGetListLength(glDisplayMenu);
numPages = llFloor((llAbs(numButtons-3))/10)+1;
if (gCurPage == 1) {
iFirstButton = 0;
}
else {
btnPrev = "...Prev";
iFirstButton = (gCurPage*10)-9;
}
if (gCurPage == numPages) {
iLastButton = (gCurPage*10)+1;
}
else {
btnMore = "More...";
iLastButton = (gCurPage*10);
}
dlg_page_btns = llList2List(glDisplayMenu,iFirstButton, iLastButton);

integer num_page_buttons = llGetListLength(dlg_page_btns);
if (numButtons > 12) {
integer num_to_fill = 12 - num_page_buttons;
if (btnPrev != "") num_to_fill--;
if (btnMore != "") num_to_fill --;
for (i=0; i<num_to_fill;i++) {
dlg_page_btns += [" "];
}
}
dlg_page_btns = order_buttons(dlg_page_btns, btnPrev, btnMore);

dlg_listenHandle = llListen(dlg_channel,"","","");
llDialog(gID, dlg_msg_display, dlg_page_btns, dlg_channel);
llSetTimerEvent(dlg_wait);
}
default
{
state_entry()
{
gSrc="";
gMsg="";
dlg_channel = (integer)(llFrand(-1000000000.0) - 1000000000.0);
}
on_rez(integer start_param)
{
llResetScript();
}
timer()
{
llSetTimerEvent(0.0);
llListenRemove(dlg_channel);
llMessageLinked(LINK_SET,0,gTgt+" "+gSrc+" response "+gMsg,"");
}
changed(integer change)
{
if(change & CHANGED_OWNER) {
llResetScript();
}
}
link_message(integer source, integer num, string msg, key id)
{
string delim = " ";
list lMsg = llParseString2List(msg, [delim], [""]);
string src = llList2String(lMsg,0);
string tgt = llList2String(lMsg,1);
string cmd = llToLower(llList2String(lMsg,2));
string arg = llList2String(lMsg,3);
if (llToLower(llGetSubString(tgt,0,8)) == "multimenu") {
clear_globals();
gSrc=src;
gTgt=tgt;
gID = id;
if (cmd == "show") {
gParent = "";
gCurPage = 1;
gDlgArguments = llParseStringKeepNulls(
llDumpList2String(llList2List(lMsg,3,-1)," "),[";"],[]
);
show(gParent);
}
}
}
listen(integer chan, string name, key id, string cmd)
{
integer i;
integer j;
integer len;
string default_navigation;
string default_group;
string default_storage;
string current_navigation;
string current_group;
string current_storage;
string s;
if (cmd == "...Prev") {
gCurPage--;
show(gParent);
}
else if (cmd == "More...") {
gCurPage++;
show(gParent);
}
else if (cmd == " ") {
show(gParent);

}
else {
string menuItem;
string menuPragma;
list l;
len = llGetListLength(glDisplayMenu);
if (llGetSubString(cmd,0,0) == "*") cmd = llGetSubString(cmd,1,-1);
i = llListFindList(strip_selected(glDisplayMenu), [cmd]);
if (i != -1) {
menuItem = llList2String(glSubMenu,i);
menuPragma = get_pragma(menuItem);
l = sub_menu(glMenu, menuItem);
if (l == []) {
default_navigation = "~nx";
default_group = "~gc";
default_storage = "~si";
}
else {
default_navigation = "~ns";
default_group = "";
default_storage = "";
}
current_navigation = default_navigation;
if (llSubStringIndex(menuPragma,"~nx" ) != -1) current_navigation = "~nx";
if (llSubStringIndex(menuPragma,"~ns" ) != -1) current_navigation = "~ns";
if (llSubStringIndex(menuPragma,"~nr" ) != -1) current_navigation = "~nr";
if (llSubStringIndex(menuPragma,"~np" ) != -1) current_navigation = "~np";
if (llSubStringIndex(menuPragma,"~npp") != -1) current_navigation = "~npp";
if (llSubStringIndex(menuPragma,"~n=" ) != -1) {
current_navigation=llGetSubString(menuPragma,llSubStringIndex(menuPragma,"~n="),-1);
}

current_group = default_group;
s = get_pragma(get_parent(menuItem,1));
if (llSubStringIndex(s,"~gc") != -1) current_group = "~gc";
if (llSubStringIndex(s,"~gr") != -1) current_group = "~gr";

current_storage = default_storage;
if (llSubStringIndex(menuPragma,"~sn") != -1) current_storage = "~sn";
if (llSubStringIndex(menuPragma,"~s+") != -1) current_storage = "~s+";
if (llSubStringIndex(menuPragma,"~sa") != -1) current_storage = "~sa";
if (llSubStringIndex(menuPragma,"~cr") != -1) current_storage = "~cr";
if (llSubStringIndex(menuPragma,"~ct") != -1) current_storage = "~ct";
if (llSubStringIndex(menuPragma,"~cp") != -1) current_storage = "~cp";
if (llSubStringIndex(menuPragma,"~rr") != -1) current_storage = "~rr";
if (llSubStringIndex(menuPragma,"~rt") != -1) current_storage = "~rt";
if (llSubStringIndex(menuPragma,"~rp") != -1) current_storage = "~rp";
if (current_storage == "~si") {
if (current_group == "~gc") store_check_selection(menuItem);
if (current_group == "~gr") store_radio_selection(menuItem);
}
if (current_storage == "~sa") {
string sParent = get_parent(menuItem,1);
clear_selection_tree(sParent);
if (current_group == "~gc") store_check_selection(sParent+":"+sAccum);
if (current_group == "~gr") store_radio_selection(sParent+":"+sAccum);
sAccum="";
}
if (current_storage == "~s+") sAccum += menuItem;
if (current_storage == "~cr") clear_selection_tree("");
if (current_storage == "~ct") clear_selection_tree(menuItem);
if (current_storage == "~cp") {
string p = get_parent(menuItem,1);
clear_selection_tree(p);
}
if (current_storage == "~rr") restore_selection_tree("");
if (current_storage == "~rt") restore_selection_tree(menuItem);
if (current_storage == "~rp") {
string p = get_parent(menuItem,1);
restore_selection_tree(p);
}
if (current_navigation == "~nx") finish();
if (current_navigation == "~ns") { gParent = menuItem; show(gParent);}
if (current_navigation == "~nr") { gParent = ""; show(gParent);}
if (current_navigation == "~np") {
gParent = get_parent(menuItem,1);
show(gParent);
}
if (current_navigation == "~npp") {
gParent = get_parent(menuItem, 2);
show(gParent);
}
if (llSubStringIndex(current_navigation, "~n=") != -1) {
gParent = llGetSubString(current_navigation,3,-1);
show(gParent);
}
}
}
}
}
Soen Eber
Registered User
Join date: 3 Aug 2006
Posts: 428
09-13-2008 16:17
Has anyone had a chance to work with this yet? Just curious if they had and what they thought of it...
Amiz Munro
Registered User
Join date: 8 Jul 2008
Posts: 54
09-15-2008 07:33
Wow! you put a lot of work into this thank you, I will play around with this today
Okegima Amat
Registered User
Join date: 18 Mar 2007
Posts: 138
01-06-2009 09:48
i tryed this code but nothing works ! too bad
Ambre Boram
Registered User
Join date: 26 Dec 2006
Posts: 2
02-13-2009 11:37
i correct an error in it, but did not know how use it .......
some help somewhere ?