Dialog Control v1.8

// ********** SIMPLE DIALOG MODULE ********** //
// By Nargus Asturias
// Version 1.86
//
// Support only one dialog at a time. DO NOT request multiple dialog at once!
// Use of provided functions are recommented. Instruction here are for hardcore programmers only!
//
// Request: Send MessageLinked to the script. There are 3 dialog modes:
// lnkDialog : Normal dialog with message and buttons
// lnkDialogNumericAdjust : A dialog with buttons can be used to adjust numeric value
// lnkDialogTextBox : A dialog with a text input field
// lnkDialogNotify : Just a simple notification dialog. No return value and no buttons.
//
// Send MessageLinked with code lnkDialogReshow to force active dialog to reappear.
// Send MessageLinked with code lnkDialogCancel to force cancel active dialog
//
// If a lnkDialog is requested with more than 12 buttons, multi-pages dialog is used to show the buttons.
//
// [ FOR lnkDialog ]
// MessageLinked Format:
// String part: List dumped into string, each entry seperated by '||'
// Field 1: Dialog message (512 bytes maximum)
// Field 2: Time-out data (integer)
// Field 3-4: Button#1 and return value pair
// Field 5-6: Button#2 and return value pair
// And go on...
// Key part: Key of AV who attend this dialog
//
// Response: MessageLinked to the prim that requested dialog (but no where else)
// num == lnkDialogResponse: AV click on a button. The buttons value returned as a string
// num == lnkDialogTimeOut: Dialog timeout.
//
// [ FOR lnkDialogTextbox ]
// MessageLinked Format:
// String part: List dumped into string, each entry seperated by '||'
// Field 1: Dialog message (512 bytes maximum)
// Field 2: Time-out data (integer)
// Field 3: Return string prefix. If empty, only user's input will be returned.
//
// [ FOR lnkDialogNumericAdjust ]
// MessageLinked Format:
// String part: List dumped into string, each entry seperated by '||'
// Field 1: Dialog message (512 bytes maximum)
// Put {VALUE} where you want the current value to be displayed)
// Field 2: Time-out data (integer)
// Field 3: Most significant value (ie. 100, for +/-100)
// Field 4: String-casted numeric value to be adjusted
// Field 5: 2nd most significant value (ie. 10, for +/-10)
// Field 6: Use '1' to enable "+/-" button, '0' otherwise.
// Field 7: 3nd significant value (ie. 1, for +/-1)
// Field 8: Use '1' for integer, or '0' for float
// Field 9: Least significant value (ie. 0.1, for +/-0.1)
// Field 10: Reserved. Not used.
// Key part: Key of AV who attend this dialog
//
// Response: MessageLinked to the prim that requested dialog (but no where else)
// num == lnkDialogResponse: OK or Cancel button is clicked. The final value returned as string.
// num == lnkDialogTimeOut: Dialog timeout.
//
// ******************************************* //
// Constants
integer lnkDialog = 14001;
integer lnkDialogNumericAdjust = 14005;
integer lnkDialogNotify = 14004;
integer lnkDialogTextBox = 14007;
integer lnkDialogReshow = 14011;
integer lnkDialogCancel = 14012;
integer lnkDialogResponse = 14002; // A button is hit, or OK is hit for lnkDialogNumericAdjust
integer lnkDialogCanceled = 14006; // Cancel is hit for lnkDialogNumericAdjust
integer lnkDialogTimeOut = 14003; // No button is hit, or Ignore is hit
integer lnkMenuClear = 15001;
integer lnkMenuAdd = 15002;
integer lnkMenuShow = 15003;
integer lnkMenuNotFound = 15010;
integer lnkMenuSetSound = 15021;
string seperator = "||";
// Menus variables
list menuNames = []; // List of names of all menus
list menus = []; // List of packed menus command, in order of menuNames
integer lastMenuIndex = 0; // Latest called menu's index
string menuSound; // Sound to me played when a menu is shown
float menuSoundVol = 1;
// Dialog variables
integer dialogChannel; // Channel number used to spawn this dialog
string message; // Message to be shown with the dialog
integer timerOut; // Dialog time-out
key keyId; // Key of user who attending this dialog
integer requestedNum; // Link-number of the requested prim
list buttons; // List of dialog buttons
list returns; // List of results from dialog buttons
float numericValue;
integer useInteger;
// Other variables
integer buttonsCount;
integer startItemNo;
integer listenId;
string redirectState;
integer responseInt = -1;
string responseStr;
key responseKey;
// ********** String Functions **********
string replaceString(string pattern, string replace, string source){
 integer index = llSubStringIndex(source, pattern);
 if(index < 0) return source;
 
 source = llDeleteSubString(source, index, (index + llStringLength(pattern) - 1));
 return llInsertString(source, index, replace);
}
// ********** Dialog Functions **********
// Function: createDialog
// Create dialog with given message and buttons, direct to user with give id
integer createDialog(key id, string message, list buttons){
 integer channel = -((integer)llFrand(8388608))*(255) - (integer)llFrand(8388608) - 11;
 llListenRemove(listenId);
 listenId = llListen(channel, "", keyId, "");
 if(buttonsCount > 0)
 llDialog(keyId, message, buttons, channel);
 else llTextBox(keyId, message, channel);
 return channel;
}
// Function: createMultiDialog
// Create dialog with multiple pages. Each page has Back, Next, and a Close button. Otherwise same functionality as createDialog() function.
integer createMultiDialog(key id, string message, list buttons, integer _startItemNo){
 integer channel = -llRound(llFrand( llFabs(llSin(llGetGMTclock())) * 1000000 )) - 11;
 if(_startItemNo < 0) _startItemNo = 0;
 if(_startItemNo >= buttonsCount - 1) _startItemNo -= 9;
 startItemNo = _startItemNo;
 
 integer vButtonsCount = buttonsCount - 2;
 
 // Generate list of buttons to be shown
 string closeButton = llList2String(buttons, buttonsCount - 1);
 
 integer stopItemNo = startItemNo + 8;
 if(stopItemNo >= buttonsCount - 1) stopItemNo = vButtonsCount;
 list thisButtons = llList2List(buttons, startItemNo, stopItemNo);
 // Generate dialog navigation buttons
 integer i = stopItemNo - startItemNo + 1;
 i = i % 3;
 if(i > 0) while(i < 3){
 thisButtons += [" "];
 ++i;
 }
 
 if(startItemNo > 0)
 thisButtons += ["<< BACK"];
 else thisButtons += [" "];
 
 thisButtons += [closeButton];
 
 if(stopItemNo < vButtonsCount)
 thisButtons += ["NEXT >>"];
 else thisButtons += [" "];
 
 // Append page number to the message
 integer pageNumber = (integer)(stopItemNo / 9) + 1;
 integer pagesCount = llCeil(vButtonsCount / 9.0);
 string vMessage = "PAGE: " + (string)pageNumber + " of " + (string)pagesCount + "\n" +
 message;
 // Display dialog
 llListenRemove(listenId);
 listenId = llListen(channel, "", keyId, "");
 llDialog(keyId, vMessage, thisButtons, channel);
 return channel;
}
// Function: generateNumericAdjustButtons
// Generate numeric adjustment dialog which adjustment values are in given list.
// If useNegative is TRUE, "+/-" button will be available.
list generateNumericAdjustButtons(list adjustValues, integer useNegative){
 list dialogControlButtons;
 list positiveButtons;
 list negativeButtons;
 list additionButtons;
 dialogControlButtons = ["OK", "Cancel"];
 // Config adjustment buttons
 integer count = llGetListLength(adjustValues);
 integer index;
 for(index = 0; (index < count) && (index < 3); index++){
 string sValue = llList2String(adjustValues, index);
 if((float)sValue != 0){
 positiveButtons += ["+" + sValue];
 negativeButtons += ["-" + sValue];
 }
 }
 // Check positive/negative button
 if(useNegative)
 additionButtons = ["+/-"];
 else additionButtons = [];
 // If there is fourth adjustment button
 if(count > 3){
 if(llGetListLength(additionButtons) == 0) additionButtons = [" "];
 string sValue = llList2String(adjustValues, index);
 additionButtons += ["+" + sValue, "-" + sValue];
 }else if(additionButtons != []) additionButtons += [" ", " "];
 
 // Return list dialog buttons
 return additionButtons + negativeButtons + positiveButtons + dialogControlButtons;
}
setResponse(integer int, string str, key id){
 responseInt = int;
 responseStr = str;
 responseKey = id;
}
checkDialogRequest(integer sender_num, integer num, string str, key id){
 // Common dialog requests
 if((num == lnkDialogNotify) || (num == lnkDialogNumericAdjust) || (num == lnkDialog)){
 list data = llParseStringKeepNulls(str, [seperator], []);
 
 message = llList2String(data, 0);
 timerOut = llList2Integer(data, 1);
 keyId = id;
 requestedNum = sender_num;
 buttons = [];
 returns = [];
 
 if(message == "") message = " ";
 if(timerOut > 7200) timerOut = 7200;
 
 // Generate buttons list
 integer i=2;
 integer count = llGetListLength(data);
 if(count > 2){
 buttonsCount = 0;
 for(; i<count;){
 buttons += [llList2String(data, i++)];
 returns += [llList2String(data, i++)];
 ++buttonsCount;
 }
 }else{
 buttons = ["OK"];
 returns = [];
 buttonsCount = 1;
 }
 
 // Determine the type of dialog
 if(num == lnkDialogNotify){
 dialogChannel = -((integer)llFrand(8388608))*(255) - (integer)llFrand(8388608) - 11;
 llDialog(keyId, message, buttons, dialogChannel);
 return;
 }else if(num == lnkDialogNumericAdjust)
 redirectState = "NumericAdjustDialog";
 else if(num == lnkDialog){
 if(buttonsCount > 12)
 redirectState = "MultiDialog";
 else redirectState = "Dialog";
 }
 
 if(TRUE) state StartDialog;
 
 // Text box request
 }else if(num == lnkDialogTextBox){
 list data = llParseStringKeepNulls(str, [seperator], []);
 message = llList2String(data, 0);
 timerOut = llList2Integer(data, 1);
 keyId = id;
 requestedNum = sender_num;
 buttons = [];
 returns = [llList2String(data, 2)];
 buttonsCount = 0;
 if(timerOut > 7200) timerOut = 7200;
 
 redirectState = "Dialog";
 if(TRUE) state StartDialog;
 
 // Otherwise; Check for menu requests
 }else checkMenuRequest(sender_num, num, str, id);
}
// ********** Menu Functions **********
clearMenusList(){
 menuNames = [];
 menus = [];
 lastMenuIndex = 0;
}
addMenu(string name, string message, list buttons, list returns){
 // Reduced menu request time by packing request commands
 string packed_message = message + seperator + "0";
 
 integer i;
 integer count = llGetListLength(buttons);
 for(i=0; i<count; i++) packed_message += seperator + llList2String(buttons, i) + seperator + llList2String(returns, i);
 // Add menu to the menus list
 integer index = llListFindList(menuNames, [name]);
 if(index >= 0)
 menus = llListReplaceList(menus, [packed_message], index, index);
 else{
 menuNames += [name];
 menus += [packed_message];
 }
}
integer showMenu(string name, key id){
 if(llGetListLength(menuNames) <= 0) return FALSE;
 
 integer index;
 if(name != ""){
 index = llListFindList(menuNames, [name]);
 if(index < 0) return FALSE;
 }else index = lastMenuIndex;
 
 lastMenuIndex = index;
 
 // Load menu command and execute
 string packed_message = llList2String(menus, index);
 if(menuSound != "") llPlaySound(menuSound, menuSoundVol);
 llMessageLinked(LINK_THIS, lnkDialog, packed_message, id);
 return TRUE;
}
checkMenuRequest(integer sender_num, integer num, string str, key id){
 // Menu response commands
 if(num == lnkDialogResponse){
 if(llGetSubString(str, 0, 4) == "MENU_"){
 str = llDeleteSubString(str, 0, 4);
 showMenu(str, id);
 }
 }
 
 // Menu management commands
 else if(num == lnkMenuClear)
 clearMenusList();
 else if(num == lnkMenuAdd){
 list data = llParseString2List(str, [seperator], []);
 string message = llList2String(data, 0);
 list buttons = [];
 list returns = [];
 integer i;
 integer count = llGetListLength(data);
 for(i=2; i<count;){
 buttons += [llList2String(data, i++)];
 returns += [llList2String(data, i++)];
 }
 addMenu((string)id, message, buttons, returns);
 }else if(num == lnkMenuShow){
 if(!showMenu(str, id)) llMessageLinked(sender_num, lnkMenuNotFound, str, NULL_KEY);
 }else if(num == lnkMenuSetSound){
 menuSound = str;
 menuSoundVol = (float)((string)id);
 }
}
// ********** States **********
default{
 state_entry(){
 if(responseInt > 0) llMessageLinked(requestedNum, responseInt, responseStr, responseKey);
 }
 
 link_message(integer sender_num, integer num, string str, key id){
 checkDialogRequest(sender_num, num, str, id);
 }
}
state StartDialog{
 state_entry(){
 if(redirectState == "Dialog") state Dialog;
 else if(redirectState == "MultiDialog") state MultiDialog;
 else if(redirectState == "NumericAdjustDialog") state NumericAdjustDialog;
 else state default;
 }
}
state Dialog{
 state_entry(){
 responseInt = -1;
 dialogChannel = createDialog(keyId, message, buttons);
 llSetTimerEvent(timerOut);
 }
 
 state_exit(){
 llSetTimerEvent(0);
 }
 
 on_rez(integer start_param){
 state default;
 }
 
 timer(){
 setResponse(lnkDialogTimeOut, "", keyId);
 state default;
 }
 
 link_message(integer sender_num, integer num, string str, key id){
 if(num == lnkDialogReshow){
 dialogChannel = createDialog(keyId, message, buttons);
 llSetTimerEvent(timerOut);
 }else if(num == lnkDialogCancel) state default;
 else checkDialogRequest(sender_num, num, str, id);
 }
 listen(integer channel, string name, key id, string msg){
 if((channel != dialogChannel) || (id != keyId)) return;
 
 if(buttonsCount > 0){
 channel = llListFindList(buttons, [msg]);
 msg = llList2String(returns, channel);
 }else msg = llList2String(returns, 0) + msg;
 setResponse(lnkDialogResponse, msg, keyId);
 state default;
 }
}
state MultiDialog {
 state_entry(){
 responseInt = -1;
 startItemNo = 0;
 dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo);
 llSetTimerEvent(timerOut);
 }
 
 state_exit(){
 llSetTimerEvent(0);
 }
 
 on_rez(integer start_param){
 state default;
 }
 
 timer(){
 setResponse(lnkDialogTimeOut, "", keyId);
 state default;
 }
 
 link_message(integer sender_num, integer num, string str, key id){
 if(num == lnkDialogReshow){
 dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo);
 llSetTimerEvent(timerOut);
 }else if(num == lnkDialogCancel) state default;
 else checkDialogRequest(sender_num, num, str, id);
 }
 listen(integer channel, string name, key id, string msg){
 if((channel != dialogChannel) || (id != keyId)) return;
 // Dialog control buttons
 if(msg == "<< BACK"){
 dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo - 9);
 llSetTimerEvent(timerOut);
 }else if(msg == "NEXT >>"){
 dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo + 9);
 llSetTimerEvent(timerOut);
 }else if(msg == " "){
 dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo);
 llSetTimerEvent(timerOut);
 
 // Response buttons
 }else{
 integer index = llListFindList(buttons, [msg]);
 setResponse(lnkDialogResponse, llList2String(returns, index), keyId);
 state default;
 }
 }
}
state NumericAdjustDialog {
 state_entry(){
 responseInt = -1;
 numericValue = llList2Float(returns, 0);
 useInteger = llList2Integer(returns, 2);
 buttons = generateNumericAdjustButtons(buttons, llList2Integer(returns, 1));
 
 string vMessage;
 if(useInteger)
 vMessage = replaceString("{VALUE}", (string)((integer)numericValue), message);
 else vMessage = replaceString("{VALUE}", (string)numericValue, message);
 
 dialogChannel = createDialog(keyId, vMessage, buttons);
 llSetTimerEvent(timerOut);
 }
 
 state_exit(){
 llSetTimerEvent(0);
 }
 
 on_rez(integer start_param){
 state default;
 }
 
 timer(){
 setResponse(lnkDialogTimeOut, "", keyId);
 state default;
 }
 
 link_message(integer sender_num, integer num, string str, key id){
 if(num == lnkDialogReshow){
 dialogChannel = createDialog(keyId, message, buttons);
 llSetTimerEvent(timerOut);
 }else if(num == lnkDialogCancel) state default;
 else checkDialogRequest(sender_num, num, str, id);
 }
 listen(integer channel, string name, key id, string msg){
 if((channel != dialogChannel) || (id != keyId)) return;
 
 // Dialog control button is hit
 if(msg == "OK"){
 setResponse(lnkDialogResponse, (string)numericValue, keyId);
 state default;
 }else if(msg == "Cancel"){
 setResponse(lnkDialogCanceled, (string)numericValue, keyId);
 llMessageLinked(requestedNum, lnkDialogCanceled, (string)numericValue, keyId);
 state default;
 
 // Value adjustment button is hit
 }else if(msg == "+/-")
 numericValue = -numericValue;
 else if(llSubStringIndex(msg, "+") == 0)
 numericValue += (float)llDeleteSubString(msg, 0, 0);
 else if(llSubStringIndex(msg, "-") == 0)
 numericValue -= (float)llDeleteSubString(msg, 0, 0);
 
 // Spawn another dialog if no OK nor Cancel is hit
 string vMessage;
 if(useInteger)
 vMessage = replaceString("{VALUE}", (string)((integer)numericValue), message);
 else vMessage = replaceString("{VALUE}", (string)numericValue, message);
 dialogChannel = createDialog(keyId, vMessage, buttons);
 llSetTimerEvent(timerOut);
 }
}