// MenuLineButtonScrolling // Responsible for ensuring that scrolling the screen still // keeps corresponding buttons and corresponding lines together. // stores enableMenuLineButton and disableMenuLineButton data // and enables/disables line buttons // in responce to scrollUp and scrollDown events // 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 // Number of lines to scroll down for each press of a scroll button. integer LINES_PER_SCROLL = 12; // Library functions: // replaceListSlice() // 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); } // ========== 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_menuLineButtonPressed(integer line, list detectedData) { callMethod(0, "menuLineButtonPressed", [line, dumpList2String(detectedData)]); } trigger_enableLineButton(integer line) { callMethod(0, "enableLineButton", [line]); } trigger_disableLineButton(integer line) { callMethod(0, "disableLineButton", [line]); } trigger_enableAllLineButtons() { callMethod(0, "enableAllLineButtons", []); } trigger_disableAllLineButtons() { callMethod(0, "disableAllLineButtons", []); } integer getMenuTextAreaPositionRetVal; trigger_getMenuTextAreaPosition() { getMenuTextAreaPositionRetVal = (integer) llFrand(13831); callMethod(getMenuTextAreaPositionRetVal, "getMenuTextAreaPosition", []); } trigger_pong(string moduleName) { callMethod(0, "pong", [moduleName]); } // Global variables manipulated by method calls (link messages): // Stores data about the status of the buttons corresponding to lines. // since each line has a button associated with it, the scroll module // needs to store if the buttons are enabled or not. // It does this as efficiantly as possible, by storing buttons statuses // as bitfields in the textButtons list; the first index (0) in textButtons // stores the bitfield containing info for the first 32 buttons. list textButtons = []; integer buttonCount; integer isLineButtonEnabled(integer line) { // Since there are 32 line-button statuses stored in each index, // divide the line number by 32, and round the result. integer bitfieldIndex = line / 32; // Get the bitfield at the index. integer bitfield = llList2Integer(textButtons, bitfieldIndex); // Get the position of the line-button bit in the bitfield. integer bitPosition = line - (bitfieldIndex * 32); // Get the value of the bit. integer bit = llFloor(llPow(2, bitPosition)); return (bitfield & bit) == bit; } disableLineButton(integer line) { // See isLineButtonEnabled for more comments about this process. integer bitfieldIndex = line / 32; integer bitfield = llList2Integer(textButtons, bitfieldIndex); integer bitPosition = line - (bitfieldIndex * 32); integer bit = llFloor(llPow(2, bitPosition)); // AND the masked-out bit to the bitfield. bitfield = bitfield & ~bit; // Store the updated bitfield back into the list. textButtons = replaceListSlice(textButtons, [bitfield], bitfieldIndex); } enableLineButton(integer line) { // See isLineButtonEnabled for more comments about this process. integer bitfieldIndex = line / 32; integer bitfield = llList2Integer(textButtons, bitfieldIndex); integer bitPosition = line - (bitfieldIndex * 32); integer bit = llFloor(llPow(2, bitPosition)); // OR the bit in the bitfield. bitfield = bitfield | bit; // Store the updated bitfield back into the list. textButtons = replaceListSlice(textButtons, [bitfield], bitfieldIndex); } // The index in textStrings that is displayed on the topmost line of the screen. integer indexAtTop = 0; integer firstMenuLine; integer lastMenuLine; integer menuLineTotal; string buttonWriter; string screenWriter; // A boolean telling if the scroller should ignore writes to the screen // or not. Since the scroller calls the same method other components would // to write to the screen, it should ignore itself. //integer ignoreScreenWrites = FALSE; string this; default { state_entry() { this = llGetScriptName(); trigger_getMenuTextAreaPosition(); } link_message(integer sender, integer num, string parameters, key methodName) { if (methodName == "getMenuTextAreaPosition_ret" && num == getMenuTextAreaPositionRetVal) { list paramList = parseStringKeepNulls(parameters); // Return value format: // getMenuTextAreaPosition_ret(integer startline, integer endLine) integer startLine = (integer) llList2String(paramList, 0); integer endLine = (integer) llList2String(paramList, 1); firstMenuLine = startLine; lastMenuLine = endLine; menuLineTotal = (lastMenuLine - firstMenuLine) + 1; state main; } } } state main { link_message(integer sender, integer num, string parameters, key methodName) { if (methodName == "menuScrollUp") { indexAtTop -= LINES_PER_SCROLL; // If we've reached the first index of the text data: if (indexAtTop <= 0) { indexAtTop = 0; } // Notify the other modules of a menu screen-position change: //trigger_setStartLineOffset(indexAtTop); // Set the screen to the scrolled values: integer screenLine; for (screenLine = firstMenuLine; screenLine <= lastMenuLine; screenLine++) { integer textIndex = screenLine + indexAtTop; if (isLineButtonEnabled(textIndex)) { trigger_enableLineButton(screenLine); } else { trigger_disableLineButton(screenLine); } } } else if (methodName == "menuScrollDown") { indexAtTop += LINES_PER_SCROLL; // Make sure indexAtTop is valid: // If there's not enough text data to fill the screen when we've scrolled down: if (buttonCount - indexAtTop < menuLineTotal) { // Adjust so that the last text data element is on the last line // of the screen. indexAtTop = buttonCount - menuLineTotal; } //trigger_setStartLineOffset(indexAtTop); integer screenLine; for (screenLine = firstMenuLine; screenLine <= lastMenuLine; screenLine++) { integer textIndex = screenLine + indexAtTop; if (isLineButtonEnabled(textIndex)) { trigger_enableLineButton(screenLine); } else { trigger_disableLineButton(screenLine); } } } else if (methodName == "lineButtonPressed") { list paramList = parseStringKeepNulls(parameters); // Method signature: // lineButtonPressed(integer lineNumber, list detectedData) integer lineNumber = (integer) llList2String(paramList, 0); list detectedData = parseStringKeepNulls(llList2String(paramList, 1)); trigger_menuLineButtonPressed(lineNumber + indexAtTop, detectedData); } else if (methodName == "enableMenuLineButton") { list paramList = parseStringKeepNulls(parameters); // Method signature: // enableMenuLineButton(string moduleName, integer lineNumber) string moduleName = llList2String(paramList, 0); integer lineNumber = (integer) llList2String(paramList, 1); if (moduleName != buttonWriter) return; enableLineButton(lineNumber); if (lineNumber >= firstMenuLine + indexAtTop && lineNumber <= lastMenuLine + indexAtTop) { trigger_enableLineButton(lineNumber - indexAtTop); } } else if (methodName == "enableAllMenuLineButtons") { list paramList = parseStringKeepNulls(parameters); // Method signature: // enableAllMenuLineButtons(string moduleName) // Note: Was ignorable. string moduleName = llList2String(paramList, 0); if (moduleName != buttonWriter) return; integer i; integer numBitfields = llGetListLength(textButtons); for (i = 0; i < numBitfields; i++) { // Replace the bitfield at the index with -1, this will effectively // make all bits 1. textButtons = replaceListSlice(textButtons, [-1], i); } trigger_enableAllLineButtons(); } else if (methodName == "disableMenuLineButton") { list paramList = parseStringKeepNulls(parameters); // Method signature: // disableLineButton(string moduleName, integer lineNumber) string moduleName = llList2String(paramList, 0); integer lineNumber = (integer) llList2String(paramList, 1); if (moduleName != buttonWriter) return; // Note: Was ignorable. disableLineButton(lineNumber); if (lineNumber >= firstMenuLine + indexAtTop && lineNumber <= lastMenuLine + indexAtTop) { trigger_disableLineButton(lineNumber - indexAtTop); } } else if (methodName == "disableAllMenuLineButtons") { list paramList = parseStringKeepNulls(parameters); // Method signature: // disableAllMenuLineButtons(string moduleName) // Note: Was ignorable. // should I set indexAtTop to 0? string moduleName = llList2String(paramList, 0); if (moduleName != buttonWriter) return; indexAtTop = 0; buttonCount = 0; textButtons = []; trigger_disableAllLineButtons(); } else if (methodName == "setMenuLineText") { list paramList = parseStringKeepNulls(parameters); // Method signature: // setMenuLineText(string moduleName, integer line, string text) string moduleName = llList2String(paramList, 0); integer line = (integer) llList2String(paramList, 1); string text = llList2String(paramList, 2); if (moduleName != screenWriter) return; if (buttonCount - 1 < line) buttonCount = line + 1; } else if (methodName == "setButtonWriter") { list paramList = parseStringKeepNulls(parameters); // Method signature: // setScreenWriter(string moduleName) string moduleName = llList2String(paramList, 0); buttonWriter = moduleName; } else if (methodName == "setScreenWriter") { list paramList = parseStringKeepNulls(parameters); // Method signature: // setScreenWriter(string moduleName) string moduleName = llList2String(paramList, 0); screenWriter = moduleName; } else if (methodName == "ping") { list paramList = parseStringKeepNulls(parameters); // Method signature: // ping(string moduleName) string moduleName = llList2String(paramList, 0); if (moduleName == this) trigger_pong(this); } } }