Difference between revisions of "MultiUser Dialog Handler"

From Second Life Wiki
Jump to navigation Jump to search
Line 14: Line 14:
==The Script==
==The Script==
<lsl>
<lsl>
// ********************************************************************
//
// Menu Display Script
//
// Menu command format
// string = menuname ~ menuparent | display navigate? TRUE/FALSE | menuMaintitle~subtitle1~subtitle2~subtitle3 | button1~button2~button3 {| fixedbutton1~fixedbutton2~fixedbutton3  optional}
// key = menuuser key
//
//
// menusDescription [menuchannel, key, menu & parent, return link, nav?,  titles, buttons, fixed buttons]
// menusActive      [menuchannel, menuhandle, time, page]
//
// by SimonT Quinnell
//
// ********************************************************************
// ********************************************************************
// CONSTANTS
// ********************************************************************
// Link Commands
integer    LINK_MENU_DISPLAY = 300;
integer    LINK_MENU_CLOSE = 310;
integer    LINK_MENU_RETURN = 320;
integer    LINK_MENU_TIMEOUT = 330;
// Main Menu Details
string      BACK = "<<";
string      FOWARD = ">>";
list        MENU_NAVIGATE_BUTTONS = [ " ", "Back", "Exit"];
float      MENU_TIMEOUT_CHECK = 10.0;
integer    MENU_TIMEOUT = 120;
integer    MAX_TEXT = 510;
integer    STRIDE_DESCRIPTION = 8;
integer    STRIDE_ACTIVE = 4;
integer    DEBUG = FALSE;
// ********************************************************************
// Variables
// ********************************************************************
list    menusDescription;
list    menusActive;
// ********************************************************************
// Functions - General
// ********************************************************************
                 
debug(string debug)
{
    if (DEBUG) llWhisper(0,"DEBUG:"+llGetScriptName()+":"+debug+" : Free("+(string)llGetFreeMemory()+")");
               
             
integer string2Bool (string test)
{
    if (test == "TRUE") return TRUE;
    else return FALSE;
}
// ********************************************************************
// Functions - Menu Helpers
// ********************************************************************
integer NewChannel()
{    // generates unique channel number
    integer channel;
    do channel = -(llRound(llFrand(999999)) + 99999);
    while (~llListFindList(menusDescription, [channel]));
    return channel;   
}
string  CheckTitleLength(string title)
{
    if (llStringLength(title) > MAX_TEXT) title = llGetSubString(title, 0, MAX_TEXT-1);
   
    return title;
}
list FillMenu(list buttons)
{  //adds empty buttons until the list length is multiple of 3, to max of 12
    integer i;
    list    listButtons;
   
    for(i=0;i<llGetListLength(buttons);i++)
    {
        string name = llList2String(buttons,i);
        if (llStringLength(name) > 24) name = llGetSubString(name, 0, 23);
        listButtons = listButtons + [name];
    }
   
    while (llGetListLength(listButtons) != 3 && llGetListLength(listButtons) != 6 && llGetListLength(listButtons) != 9 && llGetListLength(listButtons) < 12)
    {
        listButtons = listButtons + [" "];
    }
   
    buttons = llList2List(listButtons, 9, 11);
    buttons = buttons + llList2List(listButtons, 6, 8);
    buttons = buttons + llList2List(listButtons, 3, 5);   
    buttons = buttons + llList2List(listButtons, 0, 2);
   
    return buttons;
}
RemoveUser(key id)
{
    integer index_id = llListFindList(menusDescription, [id]);
   
    while (~index_id)
    {
        integer channel = llList2Integer(menusDescription, index_id-1);
        RemoveMenu(channel, FALSE);
    }
}
RemoveMenu(integer channel, integer echo)
{
    integer index = llListFindList(menusDescription, [channel]);
    if (index != -1)
    {
        key id = llList2Key(menusDescription, index+1);
        menusDescription = llDeleteSubList(menusDescription, index, index + STRIDE_DESCRIPTION -1);
        RemoveListen(channel);
        if (echo) llMessageLinked(LINK_THIS, LINK_MENU_TIMEOUT, "Timeout", id);
    }
}
RemoveListen(integer channel)
{
    integer index = llListFindList(menusActive, [channel]);
    if (index != -1)
    {   
        llListenRemove(llList2Integer(menusActive, index + 1));
        menusActive = llDeleteSubList(menusActive, index, index + STRIDE_ACTIVE - 1);
    }
}
// ********************************************************************
// Functions - Menu Main
// ********************************************************************
NewMenu(string message, integer senderNum, key id)
{  // Setup New Menu
    // menusDescription [menuchannel, key, menu & parent, return link, nav?,  titles, buttons, fixed buttons]
    list    temp = llParseString2List(message, ["|"], []);
    integer channel = NewChannel();
       
    if (llGetListLength(temp) > 2)
    {
        menusDescription = [channel, id, llList2String(temp, 0), senderNum,  string2Bool(llList2String(temp, 1)), llList2String(temp, 2), llList2String(temp, 3), llList2String(temp, 4)] + menusDescription;
        DisplayMenu(id, channel, 0);
    }
    else llOwnerSay ("ERROR: Dialog Script. Incorrect menu format");
}
DisplayMenu(key id, integer channel, integer page)
{
    string  menuTitle;
    list    menuSubTitles;
    list    menuButtonsAll;
    list    menuButtons; 
    list    menuNavigateButtons;
    list    menuFixedButtons;
    integer max = 12;
    // Populate values
    integer index = llListFindList(menusDescription, [channel]);
    menuButtonsAll = llParseString2List(llList2String(menusDescription, index+6), ["~"], []);
    if (llList2String(menusDescription, index+7) != "") menuFixedButtons = llParseString2List(llList2String(menusDescription, index+7), ["~"], []);
    // Set up the menu buttons
    if (llList2Integer(menusDescription, index+4)) menuNavigateButtons= MENU_NAVIGATE_BUTTONS;
    else if (llGetListLength(menuButtonsAll) > (max-llGetListLength(menuFixedButtons))) menuNavigateButtons = [" ", " ", " "];
   
    // FIXME: add sanity check for menu page
   
    max = max - llGetListLength(menuFixedButtons) - llGetListLength(menuNavigateButtons);
    integer    start = page*max;
    integer    stop = (page+1)*max - 1;
    menuButtons = FillMenu(menuFixedButtons + llList2List(menuButtonsAll, start, stop));
    // Generate the title
    list tempTitle = llParseString2List(llList2String(menusDescription, index+5), ["~"], []);
    menuTitle = llList2String(tempTitle,0);
    if (llGetListLength(tempTitle) > 1) menuSubTitles = llList2List(tempTitle, 1, -1);
    if (llGetListLength(menuSubTitles) > 0)
    {
        integer i;
        for(i=start;i<(stop+1);++i)
        {
            if (llList2String(menuSubTitles, i) != "") menuTitle += "\n"+llList2String(menuSubTitles, i);
        }
    }
    menuTitle = CheckTitleLength(menuTitle);
   
    // Add navigate buttons if necessary
    if (page > 0) menuNavigateButtons = llListReplaceList(menuNavigateButtons, [BACK], 0, 0);
    if (llGetListLength(menuButtonsAll) > (page+1)*max) menuNavigateButtons = llListReplaceList(menuNavigateButtons, [FOWARD], 2, 2);
   
    // Set up listen and add the row details
    integer menuHandle = llListen(channel, "", id, "");
    menusActive = [channel, menuHandle, llGetUnixTime(), page] + menusActive;
    llSetTimerEvent(MENU_TIMEOUT_CHECK);
    // Display menu
    llDialog(id, menuTitle, menuNavigateButtons + menuButtons, channel);
}
   
// ********************************************************************
// Event Handlers
// ******************************************************************** 
default
{
    listen(integer channel, string name, key id, string message)
    {
        if (message == BACK)
        {
            integer index = llListFindList(menusActive, [channel]);
            integer page = llList2Integer(menusActive, index+3)-1;
            RemoveListen(channel);           
            DisplayMenu(id, channel, page);
        }
        else if (message == FOWARD)
        {
            integer index = llListFindList(menusActive, [channel]);
            integer page = llList2Integer(menusActive, index+3)+1;
            RemoveListen(channel);
            DisplayMenu(id, channel, page);
        }
        else if (message == " ")
        {
            integer index = llListFindList(menusActive, [channel]);
            integer page = llList2Integer(menusActive, index+3);
            RemoveListen(channel);
            DisplayMenu(id, channel, page);
        }
        else
        {
            integer index = llListFindList(menusDescription, [channel]);
            llMessageLinked(llList2Integer(menusDescription, index+3), LINK_MENU_RETURN, llList2String(menusDescription, index+2)+"|"+message, id);
            RemoveMenu(channel, FALSE);
        }
    }
   
    link_message(integer senderNum, integer num, string message, key id)
    {
        if (num == LINK_MENU_DISPLAY) NewMenu(message, senderNum, id);
        else if (num == LINK_MENU_CLOSE) RemoveUser(id);
    }
   
    timer()
    {  // Check through timers and close if necessary
        integer i;
        list toRemove;
        integer currentTime = llGetUnixTime();
        integer length = llGetListLength(menusActive); 
   
        for(i=0;i<length;i+=STRIDE_ACTIVE)
        {
            if (currentTime - llList2Integer(menusActive, i+2) > MENU_TIMEOUT) toRemove = [llList2Integer(menusActive, i)] + toRemove;
        }
        length = llGetListLength(toRemove);
        if (length > 0)
        {
            for(i=0;i<length;i++)
            {
                RemoveMenu(llList2Integer(toRemove, i), TRUE);
            }
        }       
    }
}


</lsl>
</lsl>

Revision as of 20:53, 31 August 2010

Introduction

I found that i was constantly rewriting a pile of dialog handling functions in my scrips so i decided to write a single dialog handler script. It needed to have the following features;

  • Support multiple simultaneous menus from different users.
  • Support fixed top buttons that will persist on lists.
  • Support fixed bottom buttons with include Back and Exit buttons.
  • Handle item lists > 12 and automatically insert << and >> buttons to scroll through the lists.
  • Handle timeouts and communicate timeout back to calling script.

Using The Script

The Script

<lsl> // ******************************************************************** // // Menu Display Script // // Menu command format // string = menuname ~ menuparent | display navigate? TRUE/FALSE | menuMaintitle~subtitle1~subtitle2~subtitle3 | button1~button2~button3 {| fixedbutton1~fixedbutton2~fixedbutton3 optional} // key = menuuser key // // // menusDescription [menuchannel, key, menu & parent, return link, nav?, titles, buttons, fixed buttons] // menusActive [menuchannel, menuhandle, time, page] // // by SimonT Quinnell // // ********************************************************************


// ******************************************************************** // CONSTANTS // ********************************************************************

// Link Commands integer LINK_MENU_DISPLAY = 300; integer LINK_MENU_CLOSE = 310; integer LINK_MENU_RETURN = 320; integer LINK_MENU_TIMEOUT = 330;

// Main Menu Details string BACK = "<<"; string FOWARD = ">>"; list MENU_NAVIGATE_BUTTONS = [ " ", "Back", "Exit"]; float MENU_TIMEOUT_CHECK = 10.0; integer MENU_TIMEOUT = 120; integer MAX_TEXT = 510;


integer STRIDE_DESCRIPTION = 8; integer STRIDE_ACTIVE = 4; integer DEBUG = FALSE;

// ******************************************************************** // Variables // ********************************************************************

list menusDescription; list menusActive;


// ******************************************************************** // Functions - General // ********************************************************************

debug(string debug) {

   if (DEBUG) llWhisper(0,"DEBUG:"+llGetScriptName()+":"+debug+" : Free("+(string)llGetFreeMemory()+")");

}


integer string2Bool (string test) {

   if (test == "TRUE") return TRUE;
   else return FALSE;

}

// ******************************************************************** // Functions - Menu Helpers // ********************************************************************

integer NewChannel() { // generates unique channel number

   integer channel;
   do channel = -(llRound(llFrand(999999)) + 99999);
   while (~llListFindList(menusDescription, [channel]));
   return channel;    

}


string CheckTitleLength(string title) {

   if (llStringLength(title) > MAX_TEXT) title = llGetSubString(title, 0, MAX_TEXT-1);
   
   return title;

}


list FillMenu(list buttons) { //adds empty buttons until the list length is multiple of 3, to max of 12

   integer i;
   list    listButtons;
   
   for(i=0;i<llGetListLength(buttons);i++)
   {
       string name = llList2String(buttons,i);
       if (llStringLength(name) > 24) name = llGetSubString(name, 0, 23);
       listButtons = listButtons + [name];
   }
   
   while (llGetListLength(listButtons) != 3 && llGetListLength(listButtons) != 6 && llGetListLength(listButtons) != 9 && llGetListLength(listButtons) < 12)
   {
       listButtons = listButtons + [" "];
   }
   
   buttons = llList2List(listButtons, 9, 11);
   buttons = buttons + llList2List(listButtons, 6, 8);
   buttons = buttons + llList2List(listButtons, 3, 5);    
   buttons = buttons + llList2List(listButtons, 0, 2); 
   
   return buttons;

}

RemoveUser(key id) {

   integer index_id = llListFindList(menusDescription, [id]);
   
   while (~index_id) 
   {
       integer channel = llList2Integer(menusDescription, index_id-1);
       RemoveMenu(channel, FALSE);
   }

}

RemoveMenu(integer channel, integer echo) {

   integer index = llListFindList(menusDescription, [channel]);
   if (index != -1)
   {
       key id = llList2Key(menusDescription, index+1);
       menusDescription = llDeleteSubList(menusDescription, index, index + STRIDE_DESCRIPTION -1);
       RemoveListen(channel);
       if (echo) llMessageLinked(LINK_THIS, LINK_MENU_TIMEOUT, "Timeout", id);
   }

}

RemoveListen(integer channel) {

   integer index = llListFindList(menusActive, [channel]);
   if (index != -1)
   {    
       llListenRemove(llList2Integer(menusActive, index + 1));
       menusActive = llDeleteSubList(menusActive, index, index + STRIDE_ACTIVE - 1);
   }

}

// ******************************************************************** // Functions - Menu Main // ********************************************************************

NewMenu(string message, integer senderNum, key id) { // Setup New Menu

   // menusDescription [menuchannel, key, menu & parent, return link, nav?,  titles, buttons, fixed buttons]
   list    temp = llParseString2List(message, ["|"], []);
   integer channel = NewChannel();
       
   if (llGetListLength(temp) > 2)
   {
       menusDescription = [channel, id, llList2String(temp, 0), senderNum,  string2Bool(llList2String(temp, 1)), llList2String(temp, 2), llList2String(temp, 3), llList2String(temp, 4)] + menusDescription;
       DisplayMenu(id, channel, 0);
   }
   else llOwnerSay ("ERROR: Dialog Script. Incorrect menu format");

}

DisplayMenu(key id, integer channel, integer page) {

   string  menuTitle;
   list    menuSubTitles;
   list    menuButtonsAll;
   list    menuButtons;   
   list    menuNavigateButtons;
   list    menuFixedButtons;
   integer max = 12;
   // Populate values
   integer index = llListFindList(menusDescription, [channel]);
   menuButtonsAll = llParseString2List(llList2String(menusDescription, index+6), ["~"], []);
   if (llList2String(menusDescription, index+7) != "") menuFixedButtons = llParseString2List(llList2String(menusDescription, index+7), ["~"], []);
   // Set up the menu buttons
   if (llList2Integer(menusDescription, index+4)) menuNavigateButtons= MENU_NAVIGATE_BUTTONS;
   else if (llGetListLength(menuButtonsAll) > (max-llGetListLength(menuFixedButtons))) menuNavigateButtons = [" ", " ", " "];
   
   // FIXME: add sanity check for menu page
   
   max = max - llGetListLength(menuFixedButtons) - llGetListLength(menuNavigateButtons);
   integer     start = page*max;
   integer     stop = (page+1)*max - 1;
   menuButtons = FillMenu(menuFixedButtons + llList2List(menuButtonsAll, start, stop));
   // Generate the title
   list tempTitle = llParseString2List(llList2String(menusDescription, index+5), ["~"], []);
   menuTitle = llList2String(tempTitle,0);
   if (llGetListLength(tempTitle) > 1) menuSubTitles = llList2List(tempTitle, 1, -1);
   if (llGetListLength(menuSubTitles) > 0)
   {
       integer i;
       for(i=start;i<(stop+1);++i)
       {
           if (llList2String(menuSubTitles, i) != "") menuTitle += "\n"+llList2String(menuSubTitles, i);
       }
   }
   menuTitle = CheckTitleLength(menuTitle);
   
   // Add navigate buttons if necessary
   if (page > 0) menuNavigateButtons = llListReplaceList(menuNavigateButtons, [BACK], 0, 0);
   if (llGetListLength(menuButtonsAll) > (page+1)*max) menuNavigateButtons = llListReplaceList(menuNavigateButtons, [FOWARD], 2, 2); 
   
   // Set up listen and add the row details
   integer menuHandle = llListen(channel, "", id, "");
   menusActive = [channel, menuHandle, llGetUnixTime(), page] + menusActive;
   llSetTimerEvent(MENU_TIMEOUT_CHECK);
   // Display menu
   llDialog(id, menuTitle, menuNavigateButtons + menuButtons, channel);

}


// ******************************************************************** // Event Handlers // ********************************************************************

default {

   listen(integer channel, string name, key id, string message)
   {
       if (message == BACK) 
       { 
           integer index = llListFindList(menusActive, [channel]);
           integer page = llList2Integer(menusActive, index+3)-1;
           RemoveListen(channel);            
           DisplayMenu(id, channel, page);
       }
       else if (message == FOWARD)
       { 
           integer index = llListFindList(menusActive, [channel]);
           integer page = llList2Integer(menusActive, index+3)+1;
           RemoveListen(channel);
           DisplayMenu(id, channel, page);
       }
       else if (message == " ")
       { 
           integer index = llListFindList(menusActive, [channel]);
           integer page = llList2Integer(menusActive, index+3);
           RemoveListen(channel);
           DisplayMenu(id, channel, page);
       }
       else 
       {
           integer index = llListFindList(menusDescription, [channel]);
           llMessageLinked(llList2Integer(menusDescription, index+3), LINK_MENU_RETURN, llList2String(menusDescription, index+2)+"|"+message, id);
           RemoveMenu(channel, FALSE);
       }
   }
   
   link_message(integer senderNum, integer num, string message, key id) 
   {
       if (num == LINK_MENU_DISPLAY) NewMenu(message, senderNum, id);
       else if (num == LINK_MENU_CLOSE) RemoveUser(id);
   }
   
   timer()
   {   // Check through timers and close if necessary
       integer i;
       list toRemove;
       integer currentTime = llGetUnixTime();
       integer length = llGetListLength(menusActive);   
   
       for(i=0;i<length;i+=STRIDE_ACTIVE)
       {
           if (currentTime - llList2Integer(menusActive, i+2) > MENU_TIMEOUT) toRemove = [llList2Integer(menusActive, i)] + toRemove;
       }
       length = llGetListLength(toRemove);
       if (length > 0)
       {
           for(i=0;i<length;i++)
           {
               RemoveMenu(llList2Integer(toRemove, i), TRUE);
           }
       }        
   }

}

</lsl>

See also