-- MUI ALERT -- -- -- © April 2002, OpenSpark Interactive Ltd -- -- ---------------------------------------------------------------------- -- 050208 JN: member(tIcon) calls made compatible with Director 10 -- 040226 JN: Border increased around custom buttons on Mac OS X -- 020801 JN: More flexible use of the Localization script added -- ---------------------------------------------------------------------- -- USES: Some forms of syntax make use of the GetLocalizedString() -- and SubstitueStrings() handlers of the Localization script. -- ---------------------------------------------------------------------- -- This Movie Script provides a generic MUIAlert() function which -- displays an alert dialog with one or more buttons. It returns an -- integer corresponding to the position of the button the user -- pressed. -- -- At the simplest level this script acts as a wrapper script for the -- xtra("mui").new().alert() function. It performs a series of syntax -- checks and adjusts for any invalid properties before calling the -- MUI xtra. -- -- The built-in MUIInstance.alert() function only handles a limited -- set of button names, with a maximum of three buttons, and gives a -- choice of three different icons. The MUIAlert() allows you to -- extend and customize this. You can: -- -- * Provide an OK alert or a Yes|No dialog with a "Don't show again" -- checkbox. Simply use #DontAskAgain or #DontShowAgain as the -- value for the #buttons property. -- * Create any number of buttons with any names, by providing a list -- of string button names as the value for the #buttons property -- * Use any bitmap member for the alert icon. On Macintosh, the -- background color is always rgb("#DDDDD"), so you can use this -- color to give the impression of irregularly shaped icons. On -- Windows, the background color may vary according to the -- Appearance settings, so icons will always appear rectangular. -- * Set the width of the dialog automatically to suit your text -- * Use a symbol to define a localized string for the Title and -- Message of the alert. You can also provide a list of data to -- merge into this localized string. See the GetLocalizedString() -- handler for more details. -- -- If you use either of these custom button options, the text and -- button layout will be somewhat different from the layout for -- the built-in button sets: -- -- * The buttons will be offset to the right, rather than centered -- * The text area will be sized correctly, with no cut-off or -- blank space. -- -- If you include RETURN characters in your alert message with a -- custom set of buttons, the text will not be soft-wrapped. You -- should provide RETURN characters wherever you want lines to wrap. -- You can, however, override this by using the string "#softwrap" as -- the first word in your alert message. This will make the alert -- softwrap all lines at a width of around 300 pixels. -- -- All dialogs created with custom buttons are movable: the movable -- property only applies to dialogs created using the built-in -- button sets. -- -- Dialogs are centered on the main monitor. -- -- If the user presses the Escape key, the last pushbutton will be -- selected. It is thus best to make the first button the default -- button and the last button the one that maintains the status quo. -- -- WARNING: -- If you use one of the built-in button arrangements, such as #OK, -- #OKCancel or #YesNoCancel, and set the default to be the last -- button pressing the Escape key will halt Director. -- -- Examples: -- put MUIAlert([#title: "Alert", #icon: #caution, -- #message: "Turbo-encabulation activated", -- #buttons: #DontShowAgain]) -- -- put MUIAlert([#title: "Alert", #icon: #question, -- #message: "Activate turbo-encabulation?", -- #buttons: ["Activate", "Cancel"], #default: 2]) -- -- tCurrentFile = QUOTE&"Current.txt""E -- tMergeList = ["^0": tCurrentFile, "^1": "creating a new file"] -- put MUIAlert([#title: #saveTitle, #icon: #stop, #default: 1, -- #message: #saveBefore, #merge: tMergeList, -- #buttons: ["Save", "Don't Save", "Cancel"]]) -- ---------------------------------------------------------------------- -- GLOBAL DECLARATIONS -- global gTemp -- contains the value to be returned by the dialog global gMuiInstance -- instance of "Mui" dialog xtra -- Both these globals are set to after use. -- PUBLIC METHOD -- on MUIAlert(aDataList, aChildStringList) ----------------------------- -- INPUT: should be a property list with the format: -- [#icon: <#stop | #note | #caution | #question | #error -- bitmap member number | bitmap member name | -- bitmap member> -- #title: , -- #message: , -- #merge: , -- #buttons: <#OK | #OKCancel | #YesNo | #YesNoCancel | -- #RetryCancel | #AbortRetryIgnore | #DontShow | -- #DontAsk | #DontAskAgain | [list of strings]>, -- #default: , -- #movable: ] -- should be a string or a property list -- with the format ["^x": "replacement string", ...] -- NOTE: Bitmap member references are only compatible with the -- custom symbols #DontShowAgain | #DontAskAgain or a list -- of button names. The movable property is ignored if such -- custom buttons are used. -- OUTPUT: An integer corresponding to the number of buttons in the -- alert that the user presses. -------------------------------------------------------------------- -- Check that Mui xtra exists... if XtrasMissing([#mui]) then return ErrorAlert("Mui Xtra missing.") else -- ... and can be instanciated tMuiInstance = xtra("Mui").new() if ilk(tMuiInstance) <> #instance then return ErrorAlert("Instance of Mui Xtra could not be created.") end if end if -- Start of syntax check -- if ilk(aDataList) <> #propList then aDataList = [#message: aDataList] end if tTemp = aDataList[#title] case ilk(tTemp) of #string: -- continue #symbol: aDataList[#title] = GetLocalizedString(tTemp) otherwise: aDataList[#title] = "Alert" end case if voidP(aChildStringList) then aChildStringList = aDataList[#merge] end if case ilk(aChildStringList) of #propList: -- continue #string: aChildStringList = ["^0": aChildStringList] otherwise: aChildStringList = [:] end case tTemp = aDataList[#message] case ilk(tTemp) of #string: -- continue if aChildStringList.count then aDataList[#message] =SubstituteStrings(tTemp,aChildStringList) end if #symbol: -- Convert to a localized string tTemp = GetLocalizedString(tTemp, aChildStringList) aDataList[#message] = tTemp otherwise: aDataList[#message] = "Alert" end case -- End of syntax check -- -- the mouseDownScript = \ "stopEvent"&RETURN&\ "the mouseDownScript = EMPTY" tButtons = aDataList[#buttons] case tButtons of #OK,#OKCancel,#YesNo,#YesNoCancel,#RetryCancel,#AbortRetryIgnore: -- Use standard dialog with an icon to ensure that the #default -- property works case aDataList[#icon] of #stop, #note, #caution, #question, #error: -- continue otherwise: aDataList[#icon] = #caution end case return tMuiInstance.alert(aDataList) otherwise: -- Create a custom dialog with the appropriate buttons case tButtons of #DontAsk, #DontAskAgain: -- Yes|No dialog with a "Don't ask again" checkbox tButtons = [] tButtons.append(GetLocalizedString(#Yes)) tButtons.append(GetLocalizedString(#No)) aDataList[#buttons] = tButtons tDefault = aDataList[#default] aDataList[#default] = max(0, min(tDefault, 2)) return MUICustomAlert(aDataList, tMuiInstance, #DontAsk) #DontShowAgain, #DontShow, #DontWarnAgain, #DontWarn: -- OK dialog with a "Don't warn again" checkbox tButtons = [] tButtons.append(GetLocalizedString(#OK)) aDataList[#buttons] = tButtons aDataList[#default] = 1 return MUICustomAlert(aDataList, tMuiInstance, #DontShow) otherwise: if listP(tButtons) then -- Ensure that tButtons is a list of strings tCount = tButtons.count if tCount then tButtons = tButtons.duplicate() -- to allow modification -- Remove any items which are not strings i = tCount repeat while i if not stringP(tButtons[i]) then tButtons.deleteAt(i) tCount = tCount - 1 end if i = i - 1 end repeat if tCount then -- Create up to 3 buttons with the appropriate labels aDataList[#buttons] = tButtons -- adopt checked list tDefault = aDataList[#default] aDataList[#default] = max(0, min(tDefault, tCount)) return MUICustomAlert(aDataList, tMuiInstance) end if end if end if -- Buttons are not correctly defined: use OK by default aDataList[#buttons] = #OK return tMuiInstance.alert(aDataList) end case end case end MUIAlert -- PRIVATE METHODS -- on MUICustomAlert(aDataList, aMuiInstance, aCheckBox) ---------------- -- INPUT: must be a property list with the format: -- [#icon: <#stop | #note | #caution | #question | #error -- bitmap member number | bitmap member name | -- bitmap member> -- #title: , -- #message: , -- #buttons: <#OK | #OKCancel | #YesNo | #YesNoCancel | -- #RetryCancel | #AbortRetryIgnore | #DontShow | -- #DontAsk | #DontAskAgain | [list of strings]>, -- #default: , -- #movable: ] -- must be an instance of the MUI xtra -- may be #DontAsk | #DontShow | VOID -- OUTPUT: An integer in the range 1 | 2 | 3, depending on the -- number of buttons in the alert -------------------------------------------------------------------- -- Place the default return value in a temporary global gTemp = aDataList.default gMuiInstance = aMuiInstance -- Determine margin and button dimensions tMargin = 10 tButtonWidth = 93 -- may be increased tButtonHeight = 20 tTextWidth = 300 -- Prepare an icon... if the bitmap is available tIcon = aDataList[#icon] case ilk(tIcon) of #symbol: -- Use bitmap members provided in MUIAlert.cst tIcon = member(tIcon&&(the platform).char[1..3]) if ilk(tIcon, #member) then if tIcon.type <> #bitmap then tIcon = 0 end if end if #integer, #string: -- Use custom bitmap identified by name tIcon = member(tIcon) if ilk(tIcon, #member) then if tIcon.type <> #bitmap then tIcon = 0 end if end if #member: -- Use custom bitmap identified by member reference if tIcon.type <> #bitmap then tIcon = 0 end if otherwise: tIcon = 0 end case -- Make room for the icon if not tIcon then tIconWidth = 0 tIconHeight = 0 else tIconWidth = tIcon.width + tMargin tIconHeight = tIcon.height end if -- Determine the width and height of the text tTextMember = new(#text) -- temporary text member is erased later tSystem = (the environment).osVersion if tSystem starts "Macintosh OS 10." then -- Mac OS X tStyle = [#bold] tFont = "Lucinda Grande" else tStyle = [#plain] if tSystem starts "Macintosh" then -- mac Classic tFont = "Charcoal" else -- Windows tFont = "MS Sans Serif" end if end if tTextMember.font = tFont tTextMember.fontSize = 12 tTextmember.fontStyle = [#plain] tMessage = aDataList.message tRect = GetTextRect(tTextMember, tMessage, tTextWidth) tTextWidth = tRect.width -- window min = tMargin+tTextWidth+tMargin tHeight = tRect.height if tMessage starts "#softwrap" then delete tMessage.word[1] end if -- Define the callback handler tHasCheckBox = symbolP(aCheckBox) if tHasCheckBox then tCallback = #MUIDontAlertCallback aCheckBoxText = GetlocalizedString(aCheckBox) else tCallback = #MUICustomCallback aCheckBoxText = "" end if -- Determine the width of the push buttons by estimating text width tButtons = aDataList.buttons tCount = tButtons.count tString = "" repeat with i = 1 to tCount put RETURN&tButtons[i] after tString end repeat delete tString.char[1] -- initial RETURN tTextMember.fontStyle = tStyle tButtonWidth = GetTextRect(tTextMember, tString, tButtonWidth).width tButtonWidth = tButtonWidth + 12 -- minimum margin around text -- Add in width of checkbox and decide if text needs to be widened if tHasCheckBox then tRect = GetTextRect(tTextMember, aCheckBoxText) -- Make allowances for the checkbox itself and for any icon tCheckWidth = tRect.width + tMargin + 16 - tIconWidth end if tFullWidth = (tButtonWidth+tMargin) * tCount - tMargin + tCheckWidth if tFullWidth > tTextWidth then -- Modify the width of the alert text to accomodate wider buttons tTextWidth = tFullWidth tTextMember.text = tMessage tTextMember.width = tTextWidth tHeight = tTextMember.height end if -- We've finished with the temporary text member tTextMember.erase() -- Determine window dimensions and center position tHeight = max(tHeight, tIconHeight) -- tWindowWidth = tTextWidth + tIconWidth + tMargin * 2 tWindowHeight = tHeight + (tMargin * 3) + tButtonHeight -- Create the window properties tPropList = gMuiInstance.getWindowPropList() -- Override default values tPropList[#modal] = TRUE -- aDataList[#modal] tPropList[#name] = aDataList.title tPropList[#callback] = tCallback tPropList[#mode] = #pixel -- | dialogUnit | data tPropList[#width] = tWindowWidth tPropList[#height] = tWindowHeight tPropList[#xPosition] = -1 tPropList[#yPosition] = -1 tPropList[#closeBox] = FALSE -- Create template for window items tMuiItemList = [] tDefaultItem = gMuiInstance.getItemPropList() -- Window begin tMuiItem = tDefaultItem.duplicate() tMuiItem[#type] = #windowBegin tMuiItemList.append(tMuiItem) -- Add a message label tMuiItem = tDefaultItem.duplicate() tMuiItem[#type] = #label tMuiItem[#value] = tMessage tMuiItem[#locH] = tMargin + tIconWidth tMuiItem[#locV] = tMargin tMuiItem[#width] = tTextWidth tMuiItem[#height] = tHeight tMuiItemList.append(tMuiItem) -- Add buttons tHeight = tHeight + tMargin * 2 tDefault = aDataList.default tWidth = tWindowWidth - (tButtonWidth + tMargin) * i if tHasCheckBox then -- Add a "Dont show again" checkbox taking all available space tMuiItem = tDefaultItem.duplicate() tMuiItem[#type] = #checkBox tMuiItem[#title] = aCheckBoxText tMuiItem[#locH] = tMargin tMuiItem[#locV] = tHeight tMuiItem[#width] = tCheckWidth + tIconWidth tMuiItem[#height] = tButtonHeight tMuiItem[#textSize] = #tiny tMuiItemList.append(tMuiItem) gTemp = 0 -- check box is initially unchecked end if repeat with i = 1 to tCount tMuiItem = tDefaultItem.duplicate() if i = tDefault then tMuiItem[#type] = #defaultPushButton else tMuiItem[#type] = #cancelPushButton end if tMuiItem[#title] = tButtons[i] tMuiItem[#locH] = tWidth + (tMargin + tButtonWidth) * i tMuiItem[#locV] = tHeight tMuiItem[#width] = tButtonWidth tMuiItem[#height] = tButtonHeight tMuiItemList.append(tMuiItem) end repeat -- Add the icon if there is one if not tIcon then -- No bitmap is available else tMuiItem = tDefaultItem.duplicate() tMuiItem[#type] = #bitmap tMuiItem[#value] = tIcon tMuiItem[#locH] = 10 tMuiItem[#locV] = 10 tMuiItem[#width] = tIcon.width tMuiItem[#height] = tIcon.height tMuiItemList.append(tMuiItem) end if -- Window end tMuiItem = tDefaultItem.duplicate() tMuiItem[#type] = #windowEnd tMuiItemList.append(tMuiItem) -- The dialog is ready to run return MuiRunDialog([\ #windowPropList: tPropList, \ #windowItemList: tMuiItemList \ ]) end MUICustomAlert on MuiRunDialog(aDialogList) ------------------------------------------ -- CALLED by MUICustomAlert() -- INPUT: is a nested property list in the format -- required by the MUI instance. -- ACTION: Initializes the Mui dialog. Since the dialog is modal, -- the only interactions possible, until the user dismisses -- the dialog, will be via the MUI...Callback() handler -------------------------------------------------------------------- -- WORKAROUND -- The bug: calling gMuiInstance.run() will send #stepFrame to the -- actorList. If there is a call to this script in a stepFrame -- handler, that dialog will be displayed first, and gMuiInstance -- will be set to VOID. When the user then closes the current -- alert, gMuiInstance.stop(0) will fail, and we will end up in an -- infinite loop. The workaround is to disable the actorList to -- prevent the #stepFrame call from having any effect. tActorList = the actorList the actorList = [] -- gMuiInstance.initialize(aDialogList) gMuiInstance.run() gMuiInstance = VOID -- Tidy up after WORKAROUND: restore the actorList the actorList = tActorList -- tData = gTemp gTemp = VOID return tData end MuiRunDialog -- PRIVATE CALLBACK HANDLER -- on MUICustomCallback(anEvent, anItemNumber) -------------------------- -- CALLED by the Mui Xtra instance whenever a change is made to the -- dialog when only pushbuttons are used (no checkbox). -- INPUT: is #itemClicked | ... -- is the number of the item the user clicked -- on. The item counter starts with two non-interactive -- items: window begin and the alert text label. The -- pushbuttons start at item 3, so 2 is subtracted from the -- anItemNumber to give 1 for the first push button. -- ACTION: Sets gTemp to the value of the button pressed, and closes -- the dialog. -------------------------------------------------------------------- case anEvent of #itemClicked: -- One of the buttons was clicked gMuiInstance.stop(0) gTemp = anItemNumber - 2 -- window begin, alert text label end case end MUICustomCallback on MUIDontAlertCallback(anEvent, anItemNumber, anItemPropList) ------- -- CALLED by the Mui Xtra instance whenever a change is made to the -- dialog when a "Don't show again" checkbox is used -- INPUT: is #itemChanged | #itemClicked | ... -- is the number of the item the user clicked -- on. The item counter starts with two non-interactive -- items: window begin and the alert text label. The third -- item is the "Don't show again" checkbox. The OK|Yes|No -- buttons start at item 4, so 3 is subtracted from the -- anItemNumber to give 1 for the first push button. -- is a list of properties associated with -- the item. We are only interested in the #value property -- of the checkbox -- ACTION: Uses gTemp to remember if the "Don't show again" checkbox -- is selected. If the user presses one of the pushbuttons, -- it sets gTemp to the number of the button pressed, -- multiplied by -1 if the "Don't show ..." checkbox is -- selected. The MUI dialog is then closed. -------------------------------------------------------------------- case anEvent of #itemChanged: -- The user toggled the checkbox gTemp = anItemPropList.value #itemClicked: -- One of the buttons was clicked gMuiInstance.stop(0) if gTemp then -- The "don't show" box is checked: return a negative value gTemp = 3 - anItemNumber else -- return a positive value gTemp = anItemNumber - 3 end if end case end MUIDontAlertCallback -- LOCALIZATION -- -- See separate Localization script -- -- UTILITIES -- on XtrasMissing(anXtrasList) ----------------------------------------- -- INPUT: should be a list of string or symbol xtra -- names -- OUTPUT: Returns TRUE if any of the xtras in are -- missing. Once the handler has finished executing, -- contains only the names of the missing -- xtras. -------------------------------------------------------------------- if not listP(anXtrasList) then return FALSE end if i = the number of xtras repeat while i anXtrasList.deleteOne(xtra(i).name) i = i - 1 end repeat return anXtrasList.count() end XtrasMissing on GetTextRect(aTextMember, aMessage, aMinWidth) --------------------- -- CALLED BY: MUICustomAlert() -- -- INPUT: -- is a text member -- is the text to display -- is the rect of the main monitor -- -- RETURNS: the smallest rect that will fit around the text message. -------------------------------------------------------------------- aTextMember.text = aMessage tScreenWidth = the deskTopRectList[1].width tMaxWidth = min(1200, tScreenWidth) - 90 if aMessage starts "#softwrap" then delete aMessage.word[1] else if (aMessage contains RETURN) or voidP(aMinWidth) then -- Check the width of the text. Assume that the message is laid -- out exactly as it should appear aTextMember.width = tMaxWidth -- Maximum width of the member tMaxWidth = 0 i = aMessage.word.count repeat while i tChar = aMessage.word[1..i].char.count + 1 tLocH = aTextMember.charPosToLoc(tChar).locH if tMaxWidth < tLocH then tMaxWidth = tLocH end if i = i - 1 end repeat tWidth = max(aMinWidth, tMaxWidth + 2) aTextMember.width = tWidth else -- aMinWidth is given and there is only a single line of text aTextMember.width = aMinWidth -- standard width end if tRect = aTextMember.rect return tRect end mGetTextRect on ErrorAlert(anError, aDetail) -------------------------------------- -- INPUT: should be a list of string or symbol xtra -- names -- OUTPUT: Returns TRUE if any of the xtras in are -- missing. Once the handler has finished executing, -- contains only the names of the missing -- xtras. -------------------------------------------------------------------- if symbolP(anError) then -- Convert the symbol to a localized string case ilk(aDetail) of #propList: -- continue #string: aDetail = ["^0": aDetail] otherwise: aDetail = [:] end case tErrorString = GetLocalizedString(anError, aDetail) else -- Create a symbol from the error string tErrorString = anError anError = symbol(anError) end if -- Inform the user of the error if the runMode = "Author" then alert tErrorString else put tErrorString end if return anError end ErrorAlert -- DESCRIPTION -- on getBehaviorDescription (me) --------------------------------------- -- Uses the Automatic Script Description movie script -- -- The handler name is followed by a space: this handler will not -- appear in the Behavior Inspector -------------------------------------------------------------------- scriptName = sendSprite(0, #getScriptName, me) if stringP(scriptName) then code = MXNamedMember(scriptName).scriptText return \ getInitialComments(code)&RETURN&RETURN&\ getHandlerData(code) end if end getBehaviorDescription