Jump to content
Rolig Loon

A Simple MultiPage Dialog Menu System

Recommended Posts

There are many multipage dialog scripts out there, including a couple of good ones in this LSL Scripting Library.  This particular one, inspired by a user question in the LSL Scripting forum, is very simple.  Aside from being fairly compact and easily readable, it has the advantage of never using more list memory than it needs for the 12 buttons on a single menu page. 

// Simple Multipage Menu  --  Rolig Loon --  August 2013

// The Menu routine in this script will parse your input list (gNames) into menu pages
//    of 12 buttons each, including a forward button and a back button, as appropriate.
//    It generates a page's buttons on demand, so that the list of button labels is
//    never more than 12 items long.
//    It does NOT trim potential menu items to fit the 25-character limit (or the 
//    12-character display limit), nor does it sort buttons or do other fancy things
//    that you are free to add for yourself.


list gNames = ["A","B","C","D","E","F","G",
       "H","I","J","K","L","M","N","O","P","Q",
       "R","S","T","U","V","W","X","Y"];  // Your list of potential menu choices
integer gMenuPosition;  // Index number of the first button on the current page
integer gLsn;   // Dialog Listen Handle

Menu()
{
    integer Last;
    list Buttons;
    integer All = llGetListLength(gNames);
    if(gMenuPosition >= 9)   //This is NOT the first menu page
    {
        Buttons += "    <----";
        if((All - gMenuPosition) > 11)  // This is not the last page
        {
            Buttons += "    ---->";
        }
        else    // This IS the last page
        {
            Last = TRUE;
        }            
    }    
    else if (All > gMenuPosition+9) // This IS the first page
    {
        if((All - gMenuPosition) > 11)  // There are more pages to follow
        {
            Buttons += "    ---->";
        }
        else    // This IS the last page
        {
            Last = TRUE;
        }            
    }
    else    // This is the only menu page
    {
        Last = TRUE;
    }
    if (All > 0)
    {
        integer b;
        integer len = llGetListLength(Buttons);
        // This bizarre test does the important work ......        
        for(b = gMenuPosition + len + Last - 1 ; (len < 12)&&(b < All); ++b)
        {
            Buttons = Buttons + [llList2String(gNames,b)];
            len = llGetListLength(Buttons);
        }
    }
    gLsn = llListen(-12345,"","","");    
    llSetTimerEvent(10.0);
    llDialog(llGetOwner()," \n",Buttons,-12345);
}

default
{
    touch_start(integer num)
    {
        llListenRemove(gLsn);
        gMenuPosition = 0;
        Menu();
    }
    
    listen(integer channel, string name, key id, string msg)
    {
        llListenRemove(gLsn);
        llSetTimerEvent(0.0);
        if (~llSubStringIndex(msg,"---->"))
        {
            gMenuPosition += 10;
            Menu();
        }
        else if (~llSubStringIndex(msg,"<----"))
        {
            gMenuPosition -= 10;
            Menu();
        }
        else
        {
            //Do whatever the selected button directs..... your choice
        }
    }
    
    timer()
    {
        llSetTimerEvent(0.0);
        llListenRemove(gLsn);
    }
}

  EDIT:  Updated to fix the extra page issue noted by Devone Carter.  Thanks.

 

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites

This is probably a dumb question, but I am a novice scripter and am stumped. The example below works perfectly, if I want a menu with buttons A - Y.  But I want buttons of the avatars in the region.  I have replaced the FIRST line of the script with THIS     list gNames = llGetAgentList(AGENT_LIST_PARCEL, []);      and I get a syntax error.  But everywhere I look, that is supposedly the correct syntax.  Any ideas?

 

 

// Simple Multipage Menu  --  Rolig Loon --  August 2013

// The Menu routine in this script will parse your input list (gNames) into menu pages
//    of 12 buttons each, including a forward button and a back button, as appropriate.
//    It generates a page's buttons on demand, so that the list of button labels is
//    never more than 12 items long.
//    It does NOT trim potential menu items to fit the 25-character limit (or the 
//    12-character display limit), nor does it sort buttons or do other fancy things
//    that you are free to add for yourself.


list gNames = ["A","B","C","D","E","F","G",
       "H","I","J","K","L","M","N","O","P","Q",
       "R","S","T","U","V","W","X","Y"];  // Your list of potential menu choices
integer gMenuPosition;  // Index number of the first button on the current page
integer gLsn;   // Dialog Listen Handle

Menu()
{
    integer Last;
    list Buttons;
    integer All = llGetListLength(gNames);
    if(gMenuPosition >= 9)   //This is NOT the first menu page
    {
        Buttons += "    <----";
        if((All - gMenuPosition) > 11)  // This is not the last page
        {
            Buttons += "    ---->";
        }
        else    // This IS the last page
        {
            Last = TRUE;
        }            
    }    
    else if (All > gMenuPosition+9) // This IS the first page
    {
        if((All - gMenuPosition) > 11)  // There are more pages to follow
        {
            Buttons += "    ---->";
        }
        else    // This IS the last page
        {
            Last = TRUE;
        }            
    }
    else    // This is the only menu page
    {
        Last = TRUE;
    }
    if (All > 0)
    {
        integer b;
        integer len = llGetListLength(Buttons);
        // This bizarre test does the important work ......        
        for(b = gMenuPosition + len + Last - 1 ; (len < 12)&&(b < All); ++b)
        {
            Buttons = Buttons + [llList2String(gNames,b)];
            len = llGetListLength(Buttons);
        }
    }
    gLsn = llListen(-12345,"","","");    
    llSetTimerEvent(10.0);
    llDialog(llGetOwner()," \n",Buttons,-12345);
}

default
{
    touch_start(integer num)
    {
        llListenRemove(gLsn);
        gMenuPosition = 0;
        Menu();
    }
    
    listen(integer channel, string name, key id, string msg)
    {
        llListenRemove(gLsn);
        llSetTimerEvent(0.0);
        if (~llSubStringIndex(msg,"---->"))
        {
            gMenuPosition += 10;
            Menu();
        }
        else if (~llSubStringIndex(msg,"<----"))
        {
            gMenuPosition -= 10;
            Menu();
        }
        else
        {
            //Do whatever the selected button directs..... your choice
        }
    }
    
    timer()
    {
        llSetTimerEvent(0.0);
        llListenRemove(gLsn);
    }
}

Share this post


Link to post
Share on other sites

Hi Patrick,

Oh, I'm probably gonna bury you in script geek here, but you gotta learn some way!

llGetAgentList() is a function, and must be called inside the scope of some executing state. The first line of Rolig's script is a static declaration of the variable "gNames", which is allowed outside the scope of any executable code. And when it is outside all scopes, it is known to all scopes and is called "global" (hence Rolig's "g" in "gNames"). So, the first syntax error you probably got is for attempting to call a function outside of an execution scope.

You can declare gName as a global variable, but you can't fill it with the output of a function call until you're inside some executable scope. The default state calls Menu(), and Menu() is the only code that uses the variable "gName", so that's where you'd put the call. In addition to that, llGetAgentList() requires a scope parameter and a list of options, which may be empty. So, at a minimum, you'll want to move the first line of the script into the Menu() function and give it a scope, like AGENT_LIST_PARCEL.

So, the first line of the Menu() function would be this...

list gNames = llGetAgentList(AGENT_LIST_PARCE,[]);  // Your list of avatar names

That line both creates the variable gName (within the scope of the Menu() function) and fills it with the list of avatar names returned by llGetAgentList. If you declare gNames here, it won't be available anywhere outside of Menu(). That's okay for Rolig's script, but won't allow you to use gNames elsewhere should you wish. You could leave the creation of "gNames" in the global area ( list gNames; ) and remove "list" from the line in Menu(). That will still work, and make gNames available anywhere in the script.

Here's a link to the wiki page for llGetAgentList...

http://wiki.secondlife.com/wiki/LlGetAgentList

I haven't read Rolig's script, so I don't know if populating gName with the agent list will work, but you will make at least a little progress by fixing what I've noted.

Good luck!

Share this post


Link to post
Share on other sites

It's also worth noting that llGetAgentList returns a list of UUIDs, not a list of avatar names. If you want names, you'll have to do a little song and dance to llGetDisplayName or llGetUsername or whatever from each UUID in the list.   Also, because avatar names are almost guaranteed to be more than 12 characters long and therefore too big to fit on dialog buttons, you're better off using a different multipage system, like

 

Edited by Rolig Loon
  • Like 1

Share this post


Link to post
Share on other sites

A couple of points, in addition to Maddy's and Rolig's.

First, you can't call llGetAgentList up in the declarations section (at line 1).  If you try, it won't compile.  You can use lsl functions only in the body of the script.

Second, if you want a menu giving the names of all the avatars on the region, you want to populate it at run time, since the region's population is subject to change.    

While I agree with Rolig that this is a case where using numbered buttons makes more sense, here's how to do it if you want the names (or at least the first 10 or 12 characters) on the buttons.   Note that this fragment obviously won't work on its own -- it's just to demonstrate how to build the list you need in order to populate the menu, and then to find the uuid of the avatar whose name you have chosen (since you'll almost certainly need that, not their name, to do anything with your menu choice)..

list lUUIDs;
list lNamesAndUUIDs;
list lNames;
default {
	state_entry() {
		
	}


	touch_end(integer num_detected) {
		lNamesAndUUIDs = [];//clear existing list
		list lButtons =[];//clear existing list
		list lUUIDs = llGetAgentList(AGENT_LIST_REGION, []);//list of all agents on the region
		integer max = llGetListLength(lUUIDs);//length of list
		integer counter = 0;
		do{
			key k = llList2Key(lUUIDs, counter);//get each uuid in turn
			lNamesAndUUIDs+= [llGetSubString(llGetDisplayName(k),0,23)]+[k];//and add the username of that uuid and the uuid to lNamesAndUUIDs
			//truncate the names to their first 24 characters, the limit for buttons (though only the first 10 -- 12 characters will display)
		}
		while(++counter < max);
		//now, we need to populate lNames
		lNames = llList2ListStrided(lNamesAndUUIDs,0, -1, 2);//pull out the name component in lNamesAndUUIDs and add it to the buttons list
		
		//now do stuff to build a multipage menu, open a listener and on, using lNames to populate the menu buttons..
	}

	listen(integer channel, string name, key id, string message) {
		integer n = llListFindList(lNamesAndUUIDs, [message]);//is the message an item in lNamesAndUUIDs?
		if(~n){ // shorthand for "if it is an item in the list".  If it is, 
			key k = llList2Key(lNamesAndUUIDs, n + 1);//find the next item,
			//and thus you have the avatar's uuid.
		}
	}


}

 

Edited by Innula Zenovka
  • Like 1

Share this post


Link to post
Share on other sites
On 8/7/2013 at 1:42 PM, Rolig Loon said:

There are many multipage dialog scripts out there, including a couple of good ones in this LSL Scripting Library.  This particular one, inspired by a user question in the LSL Scripting forum, is very simple.  Aside from being fairly compact and easily readable, it has the advantage of never using more list memory than it needs for the 12 buttons on a single menu page. 

@Rolig Loon I'm about to use this.  It looks to be the best out there for my application.  Any comments?

Application is two lists that I keep synchronized by using the same index.

list gsAvatarKeys;  // list of (string)Keys of Avatar UUIDs

list gsAvatarDisplayNames; // list of Avatar DisplayNames pruned with  llBase64ToString(llGetSubString(llStringToBase64(theString), 0, 31))

The display names go on the menu buttons, and the (string)Keys share the same index.  

I've already got the script working okay with a single-page llDialog() box. 

Thanks 

Share this post


Link to post
Share on other sites

That sounds like just what you need. There's no "perfect" menu system that is right for all people in all situations.  I can't count the number of times that I have modified one or another of the systems in this library, or have written an entirely new one.  This one is nice and simple.  If it works for you, I'm pleased.

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...