// MenuLineButtonView // Responsble for the appearence of the buttons and interpreting // buttonPressed events for buttons corresponding to lines onscreen. // Copyright (C) 2005-2006 Francisco V. Saldana // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // Francisco V. Saldana can be contacted using his email account // username: dressedinblue, at domain: gmail.com // and in Second Life by IMming Christopher Omega // Upon getting its buttonPressed method triggered, // this component first identifys what line corresponds with the button. // If a line does not correspond with the button, it doesnt do anything. // If it does, it triggeres a lineButtonPressed(integer lineNum) event. // Library functions: integer strStartsWith(string str, string prefix) { return llSubStringIndex(str, prefix) == 0; } string removePrefixFrom(string str, string prefix) { if (!strStartsWith(str, prefix)) return str; integer prefixLen = llStringLength(prefix); return llDeleteSubString(str, 0, prefixLen - 1); } // Replaces the list elements in dest with elements in src, starting at start. // For example, if dest was [A, B, C, D], src was [E, F] and start was 1, // the list returned would be [A, E, F, D]. // If llGetListLength(src) + start > llGetListLength(dest), the returned list length // will be greater then dest list's length. string NULL = " "; list replaceListSlice(list dest, list src, integer start) { if (llGetListEntryType(dest, start - 1) == TYPE_INVALID) { integer len; for(len = llGetListLength(dest); len < start; len++) { dest += NULL; } } integer srcLen = llGetListLength(src); return llListInsertList(llDeleteSubList(dest, start, start + srcLen - 1), src, start); } // Method calling: // ========== For method invocation ========== string randomStr(string chars, integer len) { integer numChars = llStringLength(chars); string ret; integer i; for (i = 0; i < len; i++) { integer randIndex = llFloor(llFrand(numChars)); ret += llGetSubString(chars, randIndex, randIndex); } return ret; } string SEPERATOR_CHARS = "`~!@#$%^&*()-_+[]{}\|'\";/?.>,<"; integer SEPERATOR_LEN = 3; string dumpList2String(list src) { // Generate a seperator not present in any of the // elements in the list. string chars = (string) src; // Squashes all elements together. string seperator; do { seperator = randomStr(SEPERATOR_CHARS, SEPERATOR_LEN); } while (llSubStringIndex(chars, seperator) != -1); return seperator + llDumpList2String(src, seperator); } list parseStringKeepNulls(string src) { // The seperator should be the first SEPERATOR_LEN // characters in the string. return llParseStringKeepNulls(llDeleteSubString(src, 0, SEPERATOR_LEN - 1), [llGetSubString(src, 0, SEPERATOR_LEN - 1)], []); } callMethod(integer callId, string methodName, list parameters) { llMessageLinked(LINK_THIS, callId, dumpList2String(parameters), methodName); } returnValue(string methodName, integer methodIdentifyer, list value) { llMessageLinked(LINK_THIS, methodIdentifyer, dumpList2String(value), methodName + "_ret"); } // ============================================= trigger_getMenuTextAreaPosition() { callMethod(0, "getMenuTextAreaPosition", []); } trigger_addButtonListener(string buttonNamePattern) { callMethod(0, "addButtonListener", [buttonNamePattern]); } trigger_removeButtonListener(string buttonNamePattern) { callMethod(0, "removeButtonListener", [buttonNamePattern]); } trigger_lineButtonPressed(integer lineNum, list detectedData) { callMethod(0, "lineButtonPressed", [lineNum, dumpList2String(detectedData)]); } trigger_pong(string moduleName) { callMethod(0, "pong", [moduleName]); } // Global variables (interCaps) and constants (ALL_CAPS): // Prefix of the name of buttons that correspond to lines. string LINE_BUTTON_NAME_PREFIX = "Line:"; // Related to LINE_BUTTON_NAME_PREFIX // However, this is a specially formatted identifyer // for the ButtonAbstractionLayer. % means wildcard. // so Line:% means "match any string that starts with 'Line:'" string LINE_BUTTON_IDENTIFYER = "Line:%"; // Color button changes to when its pressed. vector COLOR_PRESSED = <1, 0, 0>; // Color button changes to when its released, the button // stays this color after the press->release process. vector COLOR_RELEASED = <0, 1, 0>; // Color button changes to when its disabled, // the button is COLOR_RELEASED when its enabled. vector COLOR_DISABLED = <0.16078, 0.12941, 0.14118>; // The first line on the screen that's a menu button integer firstButtonLine; // The last line on the screen that's a menu button integer lastButtonLine; // A bitfield, llPow(2, lineNum) == bit for button. integer enabledLines = -1; // Black-boxes the math involved with determining // if a line-button is enabled or not. integer isLineButtonEnabled(integer lineNum) { integer bit = (integer) llPow(2, lineNum); return ((enabledLines & bit) == bit); } // A list of the button's link numbers, the index of the link number // in this list corresponds to the line the button is for. list buttonLinkNumbers = []; refreshLinkNumbers() { integer linkNumber = 1; buttonLinkNumbers = []; while(llGetLinkKey(linkNumber) != NULL_KEY) { string name = llGetLinkName(linkNumber); if (strStartsWith(name, LINE_BUTTON_NAME_PREFIX)) { integer lineNumber = (integer) removePrefixFrom(name, LINE_BUTTON_NAME_PREFIX); buttonLinkNumbers = replaceListSlice(buttonLinkNumbers, [linkNumber], lineNumber); } linkNumber++; } } enableLineButton(integer lineNum) { // Only enable it if its already disabled. if (!isLineButtonEnabled(lineNum)) { // If there are no lines enabled, the button listener is // probobly disabled as well. if (enabledLines == 0) { trigger_addButtonListener(LINE_BUTTON_IDENTIFYER); } enabledLines = enabledLines | (integer) llPow(2, lineNum); integer linkNum = llList2Integer(buttonLinkNumbers, lineNum); llSetLinkColor(linkNum, COLOR_RELEASED, ALL_SIDES); } } disableLineButton(integer lineNum) { // Only disable it if its already enabled. if (isLineButtonEnabled(lineNum)) { enabledLines = enabledLines & ~(integer) llPow(2, lineNum); // If there are no enabled lines: if (enabledLines == 0) { trigger_removeButtonListener(LINE_BUTTON_IDENTIFYER); } integer linkNum = llList2Integer(buttonLinkNumbers, lineNum); llSetLinkColor(linkNum, COLOR_DISABLED, ALL_SIDES); } } disableAll() { integer buttonNumber; for(buttonNumber = firstButtonLine; buttonNumber <= lastButtonLine; buttonNumber++) { disableLineButton(buttonNumber); } } enableAll() { integer buttonNumber; for(buttonNumber = firstButtonLine; buttonNumber <= lastButtonLine; buttonNumber++) { enableLineButton(buttonNumber); } } // ============ llDetected* Related ============ integer DETECTED_GROUP = 1001; integer DETECTED_KEY = 1002; integer DETECTED_LINK_NUMBER = 1003; integer DETECTED_NAME = 1004; integer DETECTED_POS = 1006; integer DETECTED_ROT = 1007; list getDetectedValue(list detectedData, integer detectedConstant) { integer index = llListFindList(detectedData, [(string) detectedConstant]); if (index != -1) { return llList2List(detectedData, index + 1, index + 1); } return []; } // ============================================= buttonInit() { //llSay(0, "LineButtonLayer starting up..."); refreshLinkNumbers(); trigger_addButtonListener(LINE_BUTTON_IDENTIFYER); } string this; default { state_entry() { this = llGetScriptName(); buttonInit(); trigger_getMenuTextAreaPosition(); } link_message(integer sender, integer num, string parameters, key methodName) { if (methodName == "getMenuTextAreaPosition_ret") { list paramList = parseStringKeepNulls(parameters); // Return value format: // getMenuTextAreaSize_ret(integer startline, integer endLine) integer startLine = (integer) llList2String(paramList, 0); integer endLine = (integer) llList2String(paramList, 1); firstButtonLine = startLine; lastButtonLine = endLine; disableAll(); state running; } } } state running { state_entry() { } link_message(integer sender, integer num, string parameters, key methodName) { if (methodName == "buttonPressed") { list paramList = parseStringKeepNulls(parameters); // Method signature: // buttonPressed(string buttonName, list detectedData) // Triggered when a registered button is pressed. string buttonName = llList2String(paramList, 0); list detectedData = parseStringKeepNulls(llList2String(paramList, 1)); // If the button released is a line button: if (strStartsWith(buttonName, LINE_BUTTON_NAME_PREFIX)) { // The rest of the string is the number of the line the button corresponds to. integer lineNum = (integer) removePrefixFrom(buttonName, LINE_BUTTON_NAME_PREFIX); if (isLineButtonEnabled(lineNum)) { trigger_lineButtonPressed(lineNum, detectedData); integer linkNum = (integer) llList2String(getDetectedValue(detectedData, DETECTED_LINK_NUMBER), 0); llSetLinkColor(linkNum, COLOR_PRESSED, ALL_SIDES); } } } else if (methodName == "buttonReleased") { list paramList = parseStringKeepNulls(parameters); // Method signature: // buttonReleased(string buttonName, list detectedData) // Triggered when a registered button is released. string buttonName = llList2String(paramList, 0); list detectedData = parseStringKeepNulls(llList2String(paramList, 1)); // If the button released is a line button: if (strStartsWith(buttonName, LINE_BUTTON_NAME_PREFIX)) { // The rest of the string is the number of the line the button corresponds to. integer lineNum = (integer) removePrefixFrom(buttonName, LINE_BUTTON_NAME_PREFIX); if (isLineButtonEnabled(lineNum)) { integer linkNum = (integer) llList2String(getDetectedValue(detectedData, DETECTED_LINK_NUMBER), 0); llSetLinkColor(linkNum, COLOR_RELEASED, ALL_SIDES); } } } else if (methodName == "disableLineButton") { list paramList = parseStringKeepNulls(parameters); // Method signature: // disableLineButton(integer lineNum) integer lineNum = (integer) llList2String(paramList, 0); disableLineButton(lineNum); } else if (methodName == "disableAllLineButtons") { // Method signature: // disableAllLineButtons() disableAll(); } else if (methodName == "enableLineButton") { list paramList = parseStringKeepNulls(parameters); // Method signature: // enableLineButton(integer lineNum) integer lineNum = (integer) llList2String(paramList, 0); enableLineButton(lineNum); } else if (methodName == "enableAllLineButtons") { // Method signature: // enableAllLineButtons() enableAll(); } else if (methodName == "reregisterButtons") { // Method signature: // reregisterButtons() buttonInit(); } else if (methodName == "ping") { list paramList = parseStringKeepNulls(parameters); // Method signature: // ping(string moduleName) string moduleName = llList2String(paramList, 0); if (moduleName == this) trigger_pong(this); } } changed(integer change) { if (change & CHANGED_LINK) { refreshLinkNumbers(); } } }