-- BUG BROKER -- -- -- © February 2004, OpenSpark Interactive Ltd -- -- ---------------------------------------------------------------------- -- This broker contains a set of debugging handlers: -- * xPut() for printing data to the message window -- * xCeption() for showing a warning alert when an unexpected -- situation occurs, and -- * xTrace() for creating a runtime trace log. -- * xHook() to set the alertHook to handle runtime error alerts. -- -- Calls to xPut() and xCeption() can be filtered by passing a symbol -- ID as the first parameter. You can use xToggleEnabled() to -- customize the list of IDs which will be ignored. You can thus give -- a different ID for each different feature, and only see messages or -- alerts which concern the feature you are currently working on. -- -- The list of disabled IDs is saved automatically and restored at the -- next session. Developers working on -different machines can thus -- ignore each others messages and alerts. -- ---------------------------------------------------------------------- -- PUBLIC METHODS -- -------------- -- * xPut() -- An alternative to put() that can be toggled -- on and off, either globally or for specific -- contexts. If the first parameter is a -- symbol then no output is printed to the -- message window if the given symbol is on the -- disabled list. -- * xCeption() -- Shows an MUI Alert with a chosen message, -- and gives the user the chance to open the -- Debugger window at authortime. For this -- feature to work, there must be a breakpoint -- in the handler. As with xPut(), if the first -- parameter is a symbol on the disabled list, -- the call will be ignored. -- * xTrace() -- Allows the user to create a trace log file -- at runtime. -- * xHook() -- Sets the alertHook to the broker instance -- * xToggleEnabled() -- Allows you to toggle this broker on and off, -- or to toggle individual ID symbols. -- * xGetDisabled() -- Returns the list of disabled ID symbols. ---------------------------------------------------------------------- -- DEPENDENCIES -- ------------ -- * FileIO and MUI xtras (FileXtra is optional) -- * File Management script -- * MUI Alert script -- * Time and Date script -- * (indirectly) Localization and Xtra Brokers scripts ---------------------------------------------------------------------- on xPut(anID) -------------------------------------------------------- -- INPUT: Accepts any number of parameters. If the first parameter -- is a symbol, then this will be considered to be an ID. If -- the ID in question is disactivated, nothing will happen. -------------------------------------------------------------------- tBroker = mGetBugBroker(anID) if not tBroker then -- The broker is disactivated for the given id exit end if -- Add all the parameters to a list so that we can pass them on tParamList = [] tParamCount = the paramCount repeat with i = 1 to tParamCount tParamList.append(param(i)) end repeat return tBroker.mPut(tParamList) end xPut on xCeption(anIDOrString, aString) ------------------------------------ -- INPUT: may be an ID symbol (in which case it will -- be used for filtering) or a string warning message (in -- which case no filtering will occur). If it any other -- value, no filtering will occur, so long as is a -- string. -- should be a string if is not. If -- this is not the case, the call will be ignored. -- ACTION: Checks whether anIDOrString is a symbol on the -- pSaveData.disabled list. If so, this call is ignored. -- If not, and a string warning message is provided, an -- alert is shown while authoring. This allows the user to -- open the debugger and check the state of the methods -- stack and current variables. -- At runtime, the end user will be invited to create a -- trace log file to record the actions leading up to the -- error. -- REQUIRES MUI Alert (Movie Scripts) --------------------------------------------------------------------- tBroker = mGetBugBroker() return tBroker.mThrowException(anIDOrString, aString) end xCeption on xTrace(aStopMovie) ------------------------------------------------ -- SENT BY the menu item Tools | Trace and by the stopMovie() -- handler in which case aStopMovie will be TRUE -- INPUT: will be VOID if the user chooses the menu -- item and TRUE if this handler is called from stopMovie)() -- ACTION: Switches the trace on or off -- REQUIRES File Management + MUI Alert (Movie Scripts) -------------------------------------------------------------------- tBroker = mGetBugBroker() return tBroker.mTrace(aStopMovie) end xTrace on xHook() ----------------------------------------------------------- -- ACTION: Ensures that any runtime errors are handled by the -- alertHook() handler of the broker instance. -------------------------------------------------------------------- tBroker = mGetBugBroker() the alertHook = tBroker end xHook on xToggleEnabled(anID, aValue) -------------------------------------- -- INPUT: should be a symbol, or list of symbols. -- may be TRUE or FALSE. All other values will -- invert the current value. -- ACTION: Adds to the pSaveData.disabled list, or removes it -- from the list. Calls to xPut() will be ignored if the -- first parameter is a symbol which appears in -- pSaveData.disabled. -------------------------------------------------------------------- tBroker = mGetBugBroker() return tBroker.mToggleEnabled(anID, aValue) end xToggleEnabled on xGetDisabled() ---------------------------------------------------- -- OUTPUT: Returns the list of disabled symbols: pSaveData.disabled. -------------------------------------------------------------------- tBroker = mGetBugBroker() return tBroker.pSaveData.disabled end xGetDisabled -- SCRIPT PROPERTY AND METHOD -- property broker on mGetBugBroker(anID) ----------------------------------------------- -- OUTPUT: a pointer to the private instance of this script, or 0 if -- the given ID is disabled -------------------------------------------------------------------- tScript = script("Bug Broker") tBroker = tScript.broker if not tBroker then tBroker = tScript.new() tBroker.mInitialize() tScript.broker = tBroker end if if tBroker.mIDIsDisabled(anID) then -- Don't react to this call tBroker = 0 end if return tBroker end mGetBugBroker -- INSTANCE PROPERTIES -- property pPrefFile -- "Bug_Broker.txt": name of prefs file property pSaveData -- [#active: , -- #explainTrace: , -- #disabled: [, ...]] -- * If #active is FALSE, all xPut() or xCeption() calls are ignored -- * If #explainTrace is TRUE, a dialog appears when the user chooses -- to use the Trace feature -- * #disabled is a list of IDs to ignore for xPut() and xCeption() -- INSTANCE METHODS -- on alertHook(me, anErrorType, aMessage, aGenerator) ------------------- -- INPUT: will be one of four strings: -- "Script runtime error" -- "Script syntax error" -- "File error" -- "File read error" -- will be a string such as "Handler not defined" -- may be #alert or #script (D8.0 and later) -- OUTPUT: This handler should return an integer: -- 0, any non-integer value: -- authortime - Cancel/OK dialog (both buttons have the -- same effect), halts movie and opens Script -- window* -- runtime - Cancel/OK dialog (both buttons have the -- same effect), ignores errors and continues -- if possible -- Any non-zero integer: -- authortime - no dialog, halts movie and opens Script -- window* -- runtime - no dialog, ignores error and continues if -- possible -- -- * Errors in do() commands or in the primary event handlers, such -- as the mouseDownScript will not result in the Script window -- opening if the error occured in a string rather than in a -- script. -- -- * alert() commands are considered to be error messages and -- are intercepted by the alertHook script. If the return value -- of the alertHook handler is not 0, no alerts will appear. -- Authortime use of alert() commands will not halt the movie even -- if the command is intercepted by the alertHook script. -- -- * alert() commands used inside the alertHook() handler do not -- cause recursion. -- -- * The MUI xtra's alert() function does not get intercepted, but -- it cannot be used in Shockwave. -- -- * In Shockwave or a Projector, the alertHook() will give more -- information than the standard Director Player Error, but -- script member references will not be given. -------------------------------------------------------------------- if aGenerator = #alert then return FALSE end if -- Do customized stuff tTime = GetDateAndTime(the systemTime, #float) --in Time and Date tFile = the applicationPath&"ErrorLog_"&tTime&".txt" tTrace = me.mGetString(#traceHeader) put anErrorType&": "&aMessage after tTrace tError = FileWrite(tFile, tTrace) if not tError then tMessage = me.mGetString(#alertHook) tOffset = offset("^0", tMessage) put tFile into char tOffset to tOffset + 1 of tMessage else -- No log file could be created tMessage = \ anErrorType&": "&aMessage&RETURN&RETURN&\ "No error log file could be created." end if MUIAlert([ \ #title: "Error", \ #message: tMessage, \ #icon: #stop,\ #buttons: ["Quit"]]) -- Prevent the user from continuing stopMovie halt return TRUE -- no further alerts end alertHook on mInitialize(me) --------------------------------------------------- -- SENT BY mGetBugBroker() -- ACTION: Reads in saved settings from the prefs file, or creates -- default values. -------------------------------------------------------------------- pPrefFile = me.mGetString(#prefFile) pSaveData = GetPref(pPrefFile) pSaveData = value(pSaveData) if ilk(pSaveData) <> #propList then pSaveData = [:] pSaveData[#active] = TRUE pSaveData[#disabled] = [] pSaveData[#explainTrace] = TRUE end if end mInitialize on mIDIsDisabled(me, anID) ------------------------------------------- -- CALLED by mGetBugBroker() -- INPUT: may be a symbol ID -- OUTPUT: Returns TRUE if anID is a symbol which appears on the -- disabled ID list, FALSE if not. -------------------------------------------------------------------- if not symbolP(anID) then return FALSE else return pSaveData.disabled.getPos(anID) end if end mIDIsDisabled on mToggleEnabled(me, anID, aValue) ---------------------------------- -- INPUT: should be a symbol, TRUE or FALSE. All other -- values will invert the active state of the entire broker. -- may be TRUE or FALSE. All other values will -- invert the current value for the given ID symbol. -- ACTION: Adds to the pSaveData.disabled list, or removes it -- from the list. Calls to xPut() will be ignored if the -- first parameter is a symbol which appears in -- pSaveData.disabled. -------------------------------------------------------------------- case ilk(anID) of #list: -- Toggle all items in the list according to aValue i = anID.count() repeat while i me.mToggleEnabled(anID[i], aValue) i = i - 1 end repeat #propList: -- Toggle all properties in the list according to their value i = anID.count() repeat while i me.mToggleEnabled(anID.getPropAt(i), anID[i]) i = i - 1 end repeat #integer: -- Toggle the entire broker on or off as required pSaveData.active = (anID <> 0) #symbol: -- We are toggling one specific id tDisabled = pSaveData.disabled if integerP(aValue) then if aValue then -- Remove all mention of anID in pSaveData.disabled repeat while TRUE tIndex = tDisabled.getPos(anID) if tIndex then tDisabled.deleteAt(tIndex) else exit end if end repeat else -- Make sure that anID appears on tDisabled tIndex = tDisabled.getPos(anID) if not tIndex then tDisabled.append(anID) end if end if else -- Remove anID from tDisabled, if it is present, or add it if -- it is not. tIndex = tDisabled.getPos(anID) if tIndex then tDisabled.deleteAt(tIndex) else tDisabled.append(anID) end if end if otherwise: -- Toggle the entire broker to its opposite value pSaveData.active = not pSaveData.active end case SetPref(pPrefFile, string(pSaveData)) end mToggleEnabled on mPut(me, anOutputList) -------------------------------------------- -- INPUT: will be a list of parameters sent to xPut() -- ACTION: Prints the contents of anOutputList to the message window -------------------------------------------------------------------- if not pSaveData.active then exit end if tString = "" tCount = anOutputList.count if tCount = 1 then -- Simply put the single parameter put anOutputList.getLast() exit end if -- if we get here there are multiple parameters. We could use -- do("put "&"a, string, of, parameters"), but that is not -- efficient. As an alternative, we put a string with RETURN -- characters at each end, so that it is displayed over 3 lines. -- This means that the " characters at each end of the string are -- separated from the output content itself. repeat with i = 1 to tCount tItem = anOutputList[i] case ilk(tItem) of #string: tItem = QUOTE&tItem"E #symbol: tItem = "#"&tItem #void: tItem = "" end case put " "&tItem after tString end repeat delete char 1 of tString put RETURN&tString&RETURN end mPut on mThrowException(me, anIDOrString, aString) ------------------------ -- INPUT: may be an ID symbol (in which case it will -- be used for filtering) or a string warning message (in -- which case no filtering will occur). If it any other -- value, no filtering will occur, so long as is a -- string. -- should be a string if is not. If -- this is not the case, the call will be ignored. -- ACTION: Checks whether anIDOrString is a symbol on the -- pSaveData.disabled list. If so, this call is ignored. -- If not, and a string warning message is provided, an -- alert is shown while authoring. This allows the user to -- open the debugger and check the state of the methods -- stack and current variables. -- At runtime, the end user will be invited to create a -- trace log file to record the actions leading up to the -- error. -- NOTE: The "Breakpoint" button in the alert dialog will only -- work if a breakpoint is set on the line... -- -- case tResult of -- 2: -- * nothing -- -- When the debugger opens, it will be in this handler. You -- will need to move two handlers back up the stack to see -- the situation that provoked the xCeption() call. -------------------------------------------------------------------- case the runMode of "Projector": if the trace then exit end if tTitle = me.mGetString(#runTimeTitle) tMessage = me.mGetString(#runTimeError) tIgnore = me.mGetString(#ignore) tTraceNow = me.mGetString(#traceNow) tQuit = me.mGetString(#quit) tOffset = offset("^1", tMessage) put aString into char tOffset to tOffset + 1 of tMessage tOffset = offset("^0", tMessage) put anIDorString into char tOffset to tOffset + 1 of tMessage tList = [ \ #icon: #caution, \ #title: tTitle, \ #message: tMessage, \ #default: 3, \ #buttons: [tIgnore, tTraceNow, tQuit]] tResult = MUIAlert(tList) case tResult of 1: -- ignore id me.mToggleEnabled(anIDOrString) 2: -- create trace now me.mSaveTrace() put anIDOrString -- into traceLog 3: -- quit halt end case "Author": -- Filter for a disabled ID symbol case ilk(anIDOrString) of #symbol: if pSaveData.disabled.getPos(anIDOrString) then -- Ignore warnings for this ID exit else tCanIgnore = TRUE put "ID: #"&anIDOrString&RETURN before aString end if #string: -- -- Check that the order of the parameters has -- -- not been reversed -- if symbolP(aString) then -- if pSaveData.disabled.getPos(aString) then -- -- Ignore warnings for this ID -- exit -- end if -- end if -- Consider that there is only parameter: the message -- string. If this is the case, no filtering is possible. aString = anIDOrString otherwise: if not stringP(aString) then -- No warning message string is available exit end if end case tTitle = me.mGetString(#exception) tContinue = me.mGetString(#continue) tBreakPoint = me.mGetString(#breakPoint) if tCanIgnore then tIgnore = me.mGetString(#ignore) tButtons = [tIgnore, tContinue, tBreakPoint] tDefault = 3 else tButtons = [tContinue, tBreakPoint] tDefault = 2 end if tList = [ \ #icon: #caution, \ #title: tTitle, \ #message: aString, \ #default: tDefault, \ #buttons: tButtons] tResult = MUIAlert(tList) case tResult of tDefault: -- 2 or 3 nothing -- there must be a breakpoint here (tDefault - 2): -- Only reached if tDefault = 3. Ignore all future warnings -- for the current ID. me.mToggleEnabled(anIDOrString) end case -- result of Breakpoint alert end case -- the runMode end mThrowException on mTrace(me, aStopMovie) -------------------------------------------- -- SENT BY the menu item Tools | Trace and by the stopMovie() -- handler in which case aStopMovie will be TRUE -- INPUT: will be VOID if the user chooses the menu -- item and TRUE if this handler is called from stopMovie)() -- ACTION: Switches the trace on or off -- OUTPUT: Returns TRUE if the user switched the trace on, FALSE if -- not. -- REQUIRES File Management + MUI Alert (Movie Scripts) -------------------------------------------------------------------- if the trace then -- Switch off the trace the trace = FALSE tTraceLogFile = the traceLogFile the traceLogFile = "" -- Create a header for the trace log tTrace = me.mGetString(#traceHeader) put FileRead(tTraceLogFile) after tTrace FileWrite(tTraceLogFile, tTrace) return FALSE else if aStopMovie then -- Do not start the trace on stopMovie return FALSE end if -- If we get here, the trace is not yet on and the movie is running if pSaveData.explainTrace then -- Inform the user what switching the trace on entails tTitle = me.mGetString(#title) tMessage = me.mGetString(#explanation) tList = [ \ #icon: #caution,\ #title: tTitle, \ #message: tMessage,\ #buttons: #DontAskAgain, \ #default: 1] tResult = MUIAlert(tList) if tResult < 0 then -- Don't show again pSaveData.explainTrace = FALSE SetPref(pPrefFile, string(pSaveData)) tResult = -tResult end if if tResult - 1 then -- The user clicked on 'No return FALSE end if end if tResult = me.mSaveTrace() return tResult end mTrace on mSaveTrace(me) ---------------------------------------------------- -- SENT BY mTrace() and mThrowException() -- ACTION: Ask the user where to save the trace log file, then -- start the trace log -- OUTPUT: Returns TRUE if the user selected a file for the traceLog -- or FALSE if the Save As... dialog was canceled. --------------------------------------------------------------------- tTitle = me.mGetString(#traceTitle) tFileName = me.mGetString(#fileName) tList = [ \ #action: #save, \ #mask: #text, \ #title: tTitle, \ #filePath: the applicationPath, \ #default: tFileName \ ] tTraceLogFile = FileSavePath(tList) if not stringP(tTraceLogFile) then -- The user cancelled the save file dialog return FALSE end if -- If we get here, the user chose to switch on the trace the trace = TRUE the traceLogFile = tTraceLogFile put the environment return TRUE end mSaveTrace on mGetString(me, aStringSymbol) ------------------------------------- -- CALLED by mInitialize(), mThrowException(), mTrace() and -- mSaveTrace() -- INPUT: should be a symbol -- OUTPUT: Returns the string associated with the given symbol. -- This handler centralises all the strings used in this -- script, and therefore simplifies modification and -- localization. -------------------------------------------------------------------- case aStringSymbol of -- Main contact #contact: tString = "" -- Name of Pref file #prefFile: tString = "Bug_Broker" -- Throw exception dialog #exception: tString = "Exception" #ignore: tString = "Ignore ID" #continue: tString = "Continue" #breakPoint: tString = "Breakpoint" #runTimeTitle:tString = "Unexpected event" #traceNow: tString = "Create Log Now" --Trace #quit: tString = "Quit" #runTimeError:tString = \ "#softWrap The following unexpected event has occurred which may affect the performance of this application:"&RETURN&RETURN&\ "^0: ^1"&RETURN&RETURN&\ "The best course of action would be to:"&RETURN&\ "1. Quit and restart the application"&RETURN&\ "2. Select the menu item Help | Trace to produce a trace log"&RETURN&\ "3. Repeat the steps that lead to this alert"&RETURN&\ "4. Send the resulting file to "&me.mGetString(#contact)&"." -- Trace log file name #traceTitle: tString = "Trace Log" #fileName: tString = "VCMTraceLog.txt" -- Explanation of trace #title: tString = "Trace" #explanation: tString = \ "#softwrap Start debug trace?"&RETURN&RETURN&\ "If you click Yes you will be invited to create a trace log for this application. This will considerably slow down performance."&\ RETURN&RETURN&\ "Once the trace is running, you can switch the feature off by deselecting the menu item:"&RETURN&\ "Help | Trace." -- Trace log header #traceHeader: if the platform starts "Win" then CRLF = RETURN&numToChar(10) else CRLF = RETURN end if tString = \ "-- TRACE LOG FILE"&CRLF& \ "-- "&the long time&" on "&the long date&CRLF&CRLF& \ "-- Please send this file, together with a description of"&CRLF& \ "-- the problem you are having with this application, to:"&CRLF&CRLF&\ "-- "&me.mGetString(#contact)&CRLF&CRLF -- alertHook #alertHook: tString = \ "#softwrap An error has occurred and a log has been created at:"&\ RETURN&RETURN&"^0"&RETURN&RETURN&\ "Please send this file, along with a description of the actions that led up to this error, to "&me.mGetString(#contact)&"." end case return tString end mGetString