Difference between revisions of "LlDialog"

From Second Life Wiki
Jump to navigation Jump to search
m
m (added small example)
Line 20: Line 20:
*This function '''only''' opens a dialog box. The script must then also register a listener on the same ''channel'' using [[llListen]] and have a [[listen]] event handler to receive the response.
*This function '''only''' opens a dialog box. The script must then also register a listener on the same ''channel'' using [[llListen]] and have a [[listen]] event handler to receive the response.
*There is no way by script to kill a dialog box.
*There is no way by script to kill a dialog box.
*There is no way for the script to detect if the user clicked the small "Ignore" button (no chat is generated as a result of pressing this button).
*There is no way for the script to detect if the user clicked the small <code>["Ignore"]</code> button (no chat is generated as a result of pressing this button).
*There is no way to distinguish the input from a dialog box and regular chat made by the same user.
*There is no way to distinguish the input from a dialog box and regular chat made by the same user.
**It is important to expect that the response may not be one of the buttons.
**It is important to expect that the response may not be one of the buttons.
Line 33: Line 33:


===<tt>message</tt> limits===
===<tt>message</tt> limits===
*{{LSLP|message}} must be fewer than 512 bytes in length and be not empty. If it is empty, llDialog will shout "llDialog: must supply a message" on the [[DEBUG_CHANNEL]]. If you want to create an empty message, however, you can do it legally by using a line feed as your message, as in  <lsl>llDialog(avatar_key," \n",button_list,dialog_channel);</lsl> If the message length is greater than or equal to 512 bytes, it shouts (again on the [[DEBUG_CHANNEL|debug channel]]): "llDialog: message too long, must be less than 512 characters"; in both instances, the dialog box will not be created for {{LSLP|avatar}}.
*{{LSLP|message}} must be fewer than 512 bytes in length and be not empty. If it is empty, [[llDialog]] will shout <code>"[[llDialog]]: must supply a message"</code> on the [[DEBUG_CHANNEL]]. If you want to create an empty message, however, you can do it legally by using a line feed as your message, as in  <lsl>llDialog(avatar_key," \n",button_list,dialog_channel);</lsl> If the message length is greater than or equal to 512 bytes, it shouts (again on the [[DEBUG_CHANNEL|debug channel]]): <code>"[[llDialog]]: message too long, must be less than 512 characters"</code>; in both instances, the dialog box will not be created for {{LSLP|avatar}}.
*The client only displays 8 lines of {{LSLP|message}}. If it is longer, the dialog has a scroll bar. See [[#Appearance]].
*The client only displays 8 lines of {{LSLP|message}}. If it is longer, the dialog has a scroll bar. See [[#Appearance]].
Note: this should be 7 lines in message; one of the 8 lines is the owner and name of the object.
Note: this should be 7 lines in message; one of the 8 lines is the owner and name of the object.
Line 39: Line 39:
===<tt>buttons</tt> limits===
===<tt>buttons</tt> limits===
*If {{LSLP|buttons}} is an empty list, it will default to as if it were <code>["OK"]</code>
*If {{LSLP|buttons}} is an empty list, it will default to as if it were <code>["OK"]</code>
*If a button is named "Ignore", it will behave like the small "Ignore" button of the menu (i.e. pressing it will *not* get "Ignore" sent to the menu channel). If you need an "Ignore" button in your menu and wish to have its name sent back to the script when you press it, then use spaces in the button name (e.g. " Ignore ").
*If a button is named <code>["Ignore"]</code>, it will behave like the small <code>["Ignore"]</code> button of the menu (i.e. pressing it will '''not''' get <code>["Ignore"]</code> sent to the menu channel). If you need an <code>["Ignore"]</code> button in your menu and wish to have its name sent back to the script when you press it, then use spaces in the button name (e.g. <code>[" Ignore "]</code>).
*An error will be shouted on [[DEBUG_CHANNEL]], if...
*An error will be shouted on [[DEBUG_CHANNEL]], if...
**there are more than 12 buttons.
**there are more than 12 buttons.
Line 45: Line 45:
**any list item string length (measured in bytes, using UTF-8 encoding) is zero or greater than 24.
**any list item string length (measured in bytes, using UTF-8 encoding) is zero or greater than 24.
***In other words, a button's text when encoded as UTF-8 cannot be longer than 24 bytes or a empty string.
***In other words, a button's text when encoded as UTF-8 cannot be longer than 24 bytes or a empty string.
***This snippet can be used to truncate the string without giving an error: <code>llBase64ToString(llGetSubString(llStringToBase64(theString), 0, 31))</code>
***This snippet can be used to truncate the string without giving an error: <code>[[llBase64ToString]]([[llGetSubString]]([[llStringToBase64]](theString), 0, 31))</code>
*The client will not display all the characters of a button if the text is wider than the text space of the button. See [[#Appearance]].
*The client will not display all the characters of a button if the text is wider than the text space of the button. See [[#Appearance]].
*If the script generates button labels from outside sources like inventory or object names, take care to avoid the special string "!!llTextBox!!". This text, in button 0, will cause llDialog to behave as [[llTextBox]] instead.
*If the script generates button labels from outside sources like inventory or object names, take care to avoid the special string <code>"!!llTextBox!!"</code>. This text, in button 0, will cause [[llDialog]] to behave as [[llTextBox]] instead.
|examples=
|examples=
<lsl>
<lsl>
Line 84: Line 84:
         llListenRemove(gListener);
         llListenRemove(gListener);
         // Stop the timer now that its job is done
         // Stop the timer now that its job is done
         llSetTimerEvent(0.0);
         llSetTimerEvent(0.0);// you can use 0 as well to save memory
     }
     }
}
}
Line 114: Line 114:
close_menu()
close_menu()
{
{
     llSetTimerEvent(0);
     llSetTimerEvent(0.0);// you can use 0 as well to save memory
     llListenRemove(dialogHandle);
     llListenRemove(dialogHandle);
}
}
Line 205: Line 205:
     listen(integer _chan, string _name, key _id, string _option)
     listen(integer _chan, string _name, key _id, string _option)
     {
     {
         llSay(0, _name + " selected option " + _option);
         llSay(PUBLIC_CHANNEL, _name + " selected option " + _option);
     }
     }
}</lsl>
}</lsl>
<lsl>
list make_ordered_buttons(integer input)
{
    string output;
    if      (input == 12) output = "10, 11, 12, 7, 8, 9, 4, 5, 6, 1, 2, 3";
    else if (input == 11) output = "10, 11, 7, 8, 9, 4, 5, 6, 1, 2, 3";
    else if (input == 10) output = "10, 7, 8, 9, 4, 5, 6, 1, 2, 3";
    else if (input == 9)  output = "7, 8, 9, 4, 5, 6, 1, 2, 3";
    else if (input == 8)  output = "7, 8, 4, 5, 6, 1, 2, 3";
    else if (input == 7)  output = "7, 4, 5, 6, 1, 2, 3";
    else if (input == 6)  output = "4, 5, 6, 1, 2, 3";
    else if (input == 5)  output = "4, 5, 1, 2, 3";
    else if (input == 4)  output = "4, 1, 2, 3";
    else if (input == 3)  output = "1, 2, 3";
    else if (input == 2)  output = "1, 2";
    else if (input == 1)  output = "1";
//  when we want to return [] avoid returning [""] here
    if (output == STRING_EMPTY) return [];
/*  else convert output */      return llCSV2List(output);
}
//  Usage:
llDialog(id, "dialog message", make_ordered_buttons(5), -37812);
//  Output:
//    -    -    -
//    -    -    -
//    -  [ 4 ] [ 5 ]
//  [ 1 ] [ 2 ] [ 3 ]
</lsl>
=== Helper Functions ===
=== Helper Functions ===
{{{!}}
{{{!}}

Revision as of 06:55, 31 December 2013

Summary

Function: llDialog( key avatar, string message, list buttons, integer channel );

Shows a dialog box in the lower right corner of the avatar's screen (upper right in Viewer 1.x) with a message and choice buttons, as well as an ignore button. This has many uses ranging from simple message delivery to complex menu systems.

• key avatar avatar UUID that is in the same region
• string message message to be displayed in the dialog box
• list buttons button labels
• integer channel output chat channel, any integer value

When a button is pressed, the avatar says the text of the button label on channel.
The position where the chat is generated is where the root prim of the dialog generating object was when the dialog button was pressed.

Button Order
9   10 11
6 7 8  
3 4 5
0 1 2

Caveats

  • This function causes the script to sleep for 1.0 seconds.
  • This function only opens a dialog box. The script must then also register a listener on the same channel using llListen and have a listen event handler to receive the response.
  • There is no way by script to kill a dialog box.
  • There is no way for the script to detect if the user clicked the small ["Ignore"] button (no chat is generated as a result of pressing this button).
  • There is no way to distinguish the input from a dialog box and regular chat made by the same user.
    • It is important to expect that the response may not be one of the buttons.
  • In 99.9% of cases, the listener will be in the same script as the llDialog, however in the vary rare case, if the distance between the root prim of the listening object and the dialog generating prim is greater than 20 meters when a button is pressed, the response will not be heard. See #Limits.
    • This limitation affects attachments too if the wearer moves more than 20 meters from where the listener is located. See #Limits.
    • If the listener resides in the same script that created the dialog, then the dialog button is heard sim-wide.

  • The dialog response (the generated chat) has its in world location at the root prim's global position.
It can generate a listen event within 20 meters from that position.
  • The listening location for a child prim in the object is either at the child prim's location or at the root prim's location
see bugtrace JIRA SCR-43

message limits

  • message must be fewer than 512 bytes in length and be not empty. If it is empty, llDialog will shout "llDialog: must supply a message" on the DEBUG_CHANNEL. If you want to create an empty message, however, you can do it legally by using a line feed as your message, as in <lsl>llDialog(avatar_key," \n",button_list,dialog_channel);</lsl> If the message length is greater than or equal to 512 bytes, it shouts (again on the debug channel): "llDialog: message too long, must be less than 512 characters"; in both instances, the dialog box will not be created for avatar.
  • The client only displays 8 lines of message. If it is longer, the dialog has a scroll bar. See #Appearance.

Note: this should be 7 lines in message; one of the 8 lines is the owner and name of the object.

buttons limits

  • If buttons is an empty list, it will default to as if it were ["OK"]
  • If a button is named ["Ignore"], it will behave like the small ["Ignore"] button of the menu (i.e. pressing it will not get ["Ignore"] sent to the menu channel). If you need an ["Ignore"] button in your menu and wish to have its name sent back to the script when you press it, then use spaces in the button name (e.g. [" Ignore "]).
  • An error will be shouted on DEBUG_CHANNEL, if...
    • there are more than 12 buttons.
    • any list item is not a string.
    • any list item string length (measured in bytes, using UTF-8 encoding) is zero or greater than 24.
      • In other words, a button's text when encoded as UTF-8 cannot be longer than 24 bytes or a empty string.
      • This snippet can be used to truncate the string without giving an error: llBase64ToString(llGetSubString(llStringToBase64(theString), 0, 31))
  • The client will not display all the characters of a button if the text is wider than the text space of the button. See #Appearance.
  • If the script generates button labels from outside sources like inventory or object names, take care to avoid the special string "!!llTextBox!!". This text, in button 0, will cause llDialog to behave as llTextBox instead.

Important Issues

~ All Issues ~ Search JIRA for related Bugs
   Only the latest of multiple dialogs remains available (Viewer 2.0 Beta)

Examples

<lsl> // When the prim is touched, give the toucher the option of killing the prim.

integer gListener; // Identity of the listener associated with the dialog, so we can clean up when not needed

default {

   touch_start(integer total_number)
   {
       // Kill off any outstanding listener, to avoid any chance of multiple listeners being active
       llListenRemove(gListener);
       // get the UUID of the person touching this prim
       key user = llDetectedKey(0);
       // Listen to any reply from that user only, and only on the same channel to be used by llDialog
       // It's best to set up the listener before issuing the dialog
       gListener = llListen(-99, "", user, "");
       // Send a dialog to that person. We'll use a fixed negative channel number for simplicity
       llDialog(user, "\nDo you wish this prim to die?", ["Yes", "No" ] , -99);
       // Start a one-minute timer, after which we will stop listening for responses
       llSetTimerEvent(60.0);
   }
   listen(integer chan, string name, key id, string msg)
   {
       // If the user clicked the "Yes" button, kill this prim.
       if (msg == "Yes")
           llDie();
       // The user did not click "Yes" ...
       // Make the timer fire immediately, to do clean-up actions
       llSetTimerEvent(0.1);        
   }
   timer()
   {
       // Stop listening. It's wise to do this to reduce lag
       llListenRemove(gListener);
       // Stop the timer now that its job is done
       llSetTimerEvent(0.0);// you can use 0 as well to save memory
   }

} </lsl>

KBcaution.png Important: Please make sure that you close open listeners where possible. You'll make the Second Life experience so much better when paying attention to details here.
KBcaution.png Important: There are no built-in submenus nor pagination (like in a list with "previous" and "next") when using llDialog. You simply get one page with a max of 12 buttons, that's it. If you want a different dialog menu layout (other info and/or other buttons), you'll have to build that functionality into your script as the example below demonstrates.

<lsl> string mainMenuDialog = "\nWhich settings would you like to access?\nClick \"Close\" to close the menu.\n\nYou are here:\nMainmenu"; list mainMenuButtons = ["sub 01", "sub 02", "Close"];

string subMenu_01_Dialog = "\nClick \"Close\" to close the menu.\nClick \"-Main-\" to return to the main menu.\n\nYou are here:\nMainmenu > sub 01"; list subMenu_01_Buttons = ["action 01a", "action 01b", "Close", "-Main-"];

string subMenu_02_Dialog = "\nClick \"Close\" to close the menu.\nClick \"-Main-\" to return to the main menu.\n\nYou are here:\nMainmenu > sub 02";

list subMenu_02_Buttons = ["action 02a", "action 02b", "Close", "-Main-"];

integer dialogChannel; integer dialogHandle;

open_menu(key inputKey, string inputString, list inputList) {

   dialogChannel = (integer)llFrand(DEBUG_CHANNEL)*-1;
   dialogHandle = llListen(dialogChannel, "", inputKey, "");
   llDialog(inputKey, inputString, inputList, dialogChannel);
   llSetTimerEvent(30.0);

}

close_menu() {

   llSetTimerEvent(0.0);// you can use 0 as well to save memory
   llListenRemove(dialogHandle);

}

default {

   on_rez(integer start_param)
   {
       llResetScript();
   }
   touch_start(integer total_number)
   {
       key id = llDetectedKey(0);
       // Ensure any outstanding listener is removed before creating a new one
       close_menu();
       open_menu(id, mainMenuDialog, mainMenuButtons);
   }
   listen(integer channel, string name, key id, string message)
   {
       if(channel != dialogChannel)
           return;
       close_menu();
       if(message == "-Main-")
           open_menu(id, mainMenuDialog, mainMenuButtons);
       else if(message == "sub 01")
           open_menu(id, subMenu_01_Dialog, subMenu_01_Buttons);
       else if(message == "sub 02")
           open_menu(id, subMenu_02_Dialog, subMenu_02_Buttons);
       else if (message == "action 01a")
       {
           //do something
           open_menu(id, subMenu_01_Dialog, subMenu_01_Buttons);
       }
       else if (message == "action 01b")
       {
           //do something else
           //maybe not re-open the menu for this option?
           //open_menu(id, subMenu_01_Dialog, subMenu_01_Buttons);
       }
       else if (message == "action 02a")
       {
           //do something
           open_menu(id, subMenu_02_Dialog, subMenu_02_Buttons);
       }
       else if (message == "action 02b")
       {
           //do something else
           open_menu(id, subMenu_02_Dialog, subMenu_02_Buttons);
       }
   }
   timer()
   {
       close_menu();
   }

}

</lsl>

Useful Snippets

<lsl>//Compact function to put buttons in "correct" human-readable order integer channel;

list order_buttons(list buttons) {

   return llList2List(buttons, -3, -1) + llList2List(buttons, -6, -4)
        + llList2List(buttons, -9, -7) + llList2List(buttons, -12, -10);

}

default {

   state_entry()
   {   // Create random channel within range [-1000000000,-2000000000]

channel = (integer)(llFrand(-1000000000.0) - 1000000000.0);

llListen(channel,"", "","");

   }
   touch_start(integer total_number)
   {
       llDialog(llDetectedKey(0),"\nPlease choose an option:\n",

order_buttons(["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]),channel);

   }
   listen(integer _chan, string _name, key _id, string _option)
   {
       llSay(PUBLIC_CHANNEL, _name + " selected option " + _option);
   }

}</lsl> <lsl> list make_ordered_buttons(integer input) {

   string output;
   if      (input == 12) output = "10, 11, 12, 7, 8, 9, 4, 5, 6, 1, 2, 3";
   else if (input == 11) output = "10, 11, 7, 8, 9, 4, 5, 6, 1, 2, 3";
   else if (input == 10) output = "10, 7, 8, 9, 4, 5, 6, 1, 2, 3";
   else if (input == 9)  output = "7, 8, 9, 4, 5, 6, 1, 2, 3";
   else if (input == 8)  output = "7, 8, 4, 5, 6, 1, 2, 3";
   else if (input == 7)  output = "7, 4, 5, 6, 1, 2, 3";
   else if (input == 6)  output = "4, 5, 6, 1, 2, 3";
   else if (input == 5)  output = "4, 5, 1, 2, 3";
   else if (input == 4)  output = "4, 1, 2, 3";
   else if (input == 3)  output = "1, 2, 3";
   else if (input == 2)  output = "1, 2";
   else if (input == 1)  output = "1";

// when we want to return [] avoid returning [""] here

   if (output == STRING_EMPTY) return [];

/* else convert output */ return llCSV2List(output); }

// Usage:

llDialog(id, "dialog message", make_ordered_buttons(5), -37812);

// Output:

// - - - // - - - // - [ 4 ] [ 5 ] // [ 1 ] [ 2 ] [ 3 ] </lsl>

Helper Functions

•  uDlgBtnPagList Compact Pagination for llDialog lists, remenu, and multi-user support

Notes

To use dialog boxes to make menu systems, see Dialog Menus: A step by step guide (aimed at learners).

Tips

It is a good idea to use a very negative channel (if never more negative than the most negative 32-bit integer that is -2,147,483,648), e.g., <lsl>// Create random channel within range [-1000000000,-2000000000] integer channel = (integer)(llFrand(-1000000000.0) - 1000000000.0);

llDialog(llDetectedKey(0), "Please choose one of the below options:",

   ["Yes", "No", "0", "1"], channel);</lsl>

Negative channels are popular for script communications because the standard SL client is unable to chat directly on those channels. The only way to do so prior to llTextBox was to use llDialog which was limited to 24 bytes. (Several third party viewers can access them from the chat bar.)

You can be reasonably confident that all of your scripted objects have a unique chat channel with this small function:

<lsl>integer dialog_channel; // top of script in variables

integer channel() { // top of script in functions

   return (integer)("0x"+llGetSubString((string)llGetKey(),-8,-1));

}

dialog_channel = channel(); // somewhere in actual script execution, such as state_entry()</lsl>

Note: Since this function uses public information to generate the channel number it should by no means considered secret.

The preceding code can produce both positive and negative channels, depending on the 8th to last character of the key. The following examples will always produce negative channels:- <lsl>

   gChannel = 0x80000000

See Also

Events

•  listen

Functions

•  llListen
•  llTextBox
•  llRegionSay
•  llWhisper Sends chat limited to 10 meters
•  llSay Sends chat limited to 20 meters
•  llShout Sends chat limited to 100 meters
•  llInstantMessage Sends chat to the specified user
•  llOwnerSay Sends chat to the owner only

Articles

•  Dialog Menus: A step by step guide A walk through of the entire dialog menu process (aimed at learners).

Deep Notes

All Issues

~ Search JIRA for related Issues
   Only the latest of multiple dialogs remains available (Viewer 2.0 Beta)
   llDialog user button with text "Ignore" is treated like client "Ignore" button and is dropped when clicked.
   llDialog menus can fail when an object is deeded to a group

Footnotes

  1. ^ Channel zero is also known as: PUBLIC_CHANNEL, open chat, local chat and public chat

Signature

function void llDialog( key avatar, string message, list buttons, integer channel );