-- MULTIPLE SELECT -- -- -- © June 2002 - July 2004, OpenSpark Interactive Ltd -- -- ---------------------------------------------------------------------- -- -- Drop this behavior onto a text sprite to allow the user to select -- multiple, discontiguous lines. The selection hilite is created in -- a separate bitmap member in the following sprite channel: the -- behavior will fail to work if the sprite in the next channel is not -- a bitmap. -- -- Shift-click, click+Shift+drag and Control-click are all supported -- techniques for modifying the selection. -- -- NOTE: On Windows, in D8.5.0, mouse events will not be detected -- while authoring if the Ctrl key is held down. This is corrected in -- D8.5.1, and does not occur in other versions. -- -- The position of the hilites is automatically adjusted when the user -- scrolls the text. -- ---------------------------------------------------------------------- -- -- To set the selection from a different script, use a call such as: -- -- sendSprite(1, #SetSelection, [2, 3]) -- sendAllSprites(#SetSelection, [2, 3], "List") -- -- The latter example will only set the selection in sprites where the -- member is called "List". Other possible calls are: -- -- sendAllSprites(#SetSelection, [2, 3], member("List")) -- sendAllSprites(#SetSelection, [2, 3], member("List").number) -- ---------------------------------------------------------------------- -- -- To get the selection, use a call such as one of the following: -- -- tSelection = sendSprite(1, #GetSelection) -- -- tSelection = (#GetSelection, "List") -- -- tSelection = (#GetSelection, member("List")) -- -- tSelection = (#GetSelection, member("List").number) -- -- tSelection = [:] -- tSelection = (#GetSelection, tSelection) -- tSelection = tSelection.getaProp(member("List")) -- -- tSelection = [:] -- tSelection = (#GetSelection, tSelection, [member("List")]) -- tSelection = tSelection.getLast() -- ---------------------------------------------------------------------- -- 040729 JN: Alt-click proposed as an author-time alternative to -- Ctrl-click for discontiguous selections. -- 040704 JN: Now works correctly with text members taller than -- 16383 pixels ---------------------------------------------------------------------- -- PROPERTY DECLARATIONS -- property hiliteColor -- property blendLevel -- -- property psText -- sprite(me.spriteNum) containing text member property pmText -- text member of psText property pLineHeight -- assumes fixed line height -- property pmBitmap -- bitmap member in sprite in following channel property pWidth -- width of bitmap member and of text member property plSelection -- [list of selected items] property pFirstLine -- isolated line the user first clicked on -- property pLine -- positive integer while user drags the mouse property pScrollTop -- the last detected value of the scrolltop property pScrollDown -- mouseV above which the text autoscrolls down property pScrollUp -- mouseV below which the text autoscrolls up property pLastScroll -- ticks when last autoscroll occurred property pRatio -- power of 2 by which the scrollTop is -- inaccurate for members taller than 16383 -- pixels -- EVENT HANDLERS -- on beginSprite(me) me.mInitialize() end beginSprite on mouseDown(me) pLine = psText.pointToLine(the mouseLoc) me.mSelectLine(pLine) end mouseDown on exitFrame(me) if pLine then if the mouseDown then -- The user is dragging the mouse tLoc = me.mAutoScroll() me.mShiftSelection(tLoc) else -- The user has just stopped dragging pLine = FALSE end if else if pScrollTop <> pmText.scrollTop then -- The user has scrolled the member: reset the selection pScrollTop = pmText.scrollTop pmBitmap.image.setAlpha(0) me.SetSelection(plSelection) end if end exitFrame -- PUBLIC METHODS -- on GetSelection(me, aList, aMember) ---------------------------------- -- INPUT: can be a linear or property list. If it is a -- member reference -- can be any Lingo value. If it is a member -- reference, name or number, it will be used to filter for -- the correct instance of this behavior. A list of member -- references can also be used. -- OUTPUT: if is a list, a pointer to plSelection will be -- added to it. In any case, plSelection will be returned. -- If the #GetSelection event is sent to a number of sprites -- the returned value will only contain the value of the -- last instance called. -------------------------------------------------------------------- tIlk = ilk(aList) case tIlk of #member, #string, #integer: if ilk(aMember) <> tIlk then aMember = aList end if end case case ilk(aMember) of #member: if pmText <> aMember then exit end if #string: if pmText.name <> aMember then exit end if #integer: if pmText.number <> aMember then exit end if #list: if not aMember.getPos(pmText) then exit end if end case case tIlk of #list: aList.add(plSelection) #propList: aList.addProp(pmText, plSelection) end case return plSelection end GetSelection on SetSelection(me, aList, aMember) --------------------------------- -- INPUT: should be a list of integer values -- can be any Lingo value. If it is a member -- reference, name or number, it will be used to filter for -- the correct instance of this behavior in a sendAllSprites -- call: -- -- sendAllSprites(#SetSelection, [2, 3], member("List")) -- -- ACTION: if contains no invalid items, each of the lines -- defined in will be selected and the sorted -- contents of will replace the contents of -- plSelection -------------------------------------------------------------------- case ilk(aMember) of #member: if pmText <> aMember then exit end if #string: if pmText.name <> aMember then exit end if #integer: if pmText.number <> aMember then exit end if end case if not listP(aList) then return #invalidList else -- Reject a list if any items are not valid integer line numbers tCount = pmText.line.count aList = aList.duplicate() i = aList.count repeat while i tItem = aList[i] if integerP(tItem) then if tItem < 1 or tItem > tCount then -- tItem does not refer to a valid line return #invalidLineNumber end if else -- tItem is not a line number return #invalidNumber end if i = i - 1 end repeat end if i = aList.count -- Retain pFirstLine if possible if not aList.getPos(pFirstLine) then if i then pFirstLine = aList[1] else pFirstLine = 0 end if end if -- Adopt the selection plSelection.deleteAll() pmBitmap.image.setAlpha(0) pRatio = me.mGetScrollRatio() repeat while i me.mToggleOneLine(aList[i], TRUE) i = i - 1 end repeat end SetSelection -- PRIVATE METHODS -- on mInitialize(me) --------------------------------------------------- -- SENT BY beginSprite() -------------------------------------------------------------------- psText = sprite(me.spriteNum) pmText = psText.member pScrollTop = pmText.scrollTop -- Assume fixed line height in pmText pLineHeight = pmText.charPosToLoc(1).locV pScrollDown = psText.top --+ pLineHeight / 2 pScrollUp = psText.bottom --- pLineHeight / 2 -- Prepare the bitmap member in the following sprite tSprite = sprite(me.spriteNum + 1) pmBitmap = tSprite.member pWidth = psText.width tHeight = pmText.pageHeight tHilite = image(pWidth, tHeight, 32) tHilite.fill(tHilite.rect, hiliteColor) tHilite.setAlpha(0) -- transparent tHilite.useAlpha = TRUE pmBitmap.image = tHilite pmBitmap.regPoint = point(0, 0) tSprite.rect = rect(0, 0, pWidth, tHeight) tSprite.loc = point(psText.left, psText.top) -- Create a list of selected items plSelection = [] plSelection.sort() end mInitialize on mSelectLine(me, aLine) -------------------------------------------- -- SENT BY mouseDown() -- INPUT: is the number of the line under the mouse -- ACTION: Selects the line the user clicked on, taking into account -- the state of the Control and Shift keys -------------------------------------------------------------------- pRatio = me.mGetScrollRatio() if aLine < 1 then -- The user did not click on any text exit else if the shiftDown then if pFirstLine then -- Add all lines from first click to current click return me.mSelectRange(aLine) end if else if the commandDown then if pFirstLine then -- Add the current line to the selection if me.mToggleOneLine(aLine) then pFirstLine = aLine -- use this line as anchor for Shift-click end if exit end if end if -- No lines are currently selected: select the current line only pFirstLine = aLine me.mSelectRange(aLine) end mSelectLine on mAutoScroll(me) --------------------------------------------------- -- SENT BY exitFrame() while the user is dragging the mouse -- ACTION: Scrolls the text member automatically if the mouse is -- near the bottom or the top -- OUTPUT: returns the point over the text sprite that should be -- used to select a line -------------------------------------------------------------------- tMouseV = the mouseV tMouseLoc = point(psText.left, tMouseV) tScrollTop = pScrollTop if the ticks < pLastScroll + 2 then -- Don't scroll again yet else if tMouseLoc.locV < pScrollDown then if pScrollTop <> 0 then tScrollTop = max(0, pScrollTop - pLineHeight / 2) tMouseLoc.locV = pScrollDown end if else if tMouseLoc.locV > pScrollUp then tMaxScroll = pmText.height - pmText.pageHeight if pScrollTop <> tMaxScroll then tScrollTop = min(tMaxScroll, pScrollTop + pLineHeight / 2) tMouseLoc.locV = pScrollUp end if end if if pScrollTop <> tScrollTop then pLastScroll = the ticks pScrollTop = tScrollTop pmText.scrollTop = pScrollTop pmBitmap.image.setAlpha(0) me.SetSelection(plSelection) end if return tMouseLoc end mAutoScroll on mShiftSelection(me, aLoc) ----------------------------------------- -- SENT BY exitFrame() while the user is dragging the mouse -- INPUT: is the mouseLoc, corrected constrained to the -- text sprite. -- ACTION: Extends the selection if the shift key is down, or -- selects the line currently under the mouse if no -- modifier keys are being pressed. -------------------------------------------------------------------- if the shiftDown then -- The user is dragging the mouse with the shift key down tLine = psText.pointToLine(aLoc) if pLine <> tLine then pLine = tLine me.mSelectRange(tLine) end if else if not the commandDown then -- The user is dragging without holding any modifier keys down pFirstLine = psText.pointToLine(aLoc) me.mSelectRange(pFirstLine) end if end mShiftSelection on mSelectRange(me, aLine) ------------------------------------------- -- SENT BY mSelectLine(), mSelectOneLine() -- INPUT: is the number of the line under the mouse -- ACTION: Selects all lines from the first one clicked on to aLine -------------------------------------------------------------------- if aLine < 1 then exit end if plSelection.deleteAll() pmBitmap.image.setAlpha(0) tMin = min(pFirstLine, aLine) tMax = max(pFirstLine, aLine) pRatio = me.mGetScrollRatio() repeat with i = tMin to tMax me.mToggleOneLine(i) end repeat end mSelectRange on mToggleOneLine(me, aLine, aHilite) -------------------------------- -- SENT BY mSelectLine(), mSelectRange(), SetSelection() -- INPUT: is the number of the line under the mouse -- will only be TRUE if the call comes from -- SetSelection() -- ACTION: Adds to the selection, independently of any -- other lines that may already be selected -------------------------------------------------------------------- -- Determine the last char to select tChar2 = pmText.text.line[1..aLine].char.count -- Determine the first char to select if aLine = 1 then tChar1 = 1 else tChar1 = tChar2 - pmText.text.line[aLine].char.count + 1 end if -- Determine the rect of the hilite area tScrollTop = pScrollTop * pRatio tTop = pmText.charPosToLoc(tChar1).locV - pLineHeight tTop = max(0, tTop - tScrollTop) tBottom = pmText.charPosToLoc(tChar2 + 1).locV + 1 - tScrollTop tRect = rect(0, tTop, pWidth, tBottom) -- Apply the hilite to (or remove it from) the bitmap alpha channel tAlpha = pmBitmap.image.extractAlpha() -- Check if line is already hilited if voidP(aHilite) then aHilite = (tAlpha.getPixel(0, tTop) = paletteIndex(0)) end if if aHilite then -- Hilite the word tColor = paletteIndex(blendLevel) plSelection.add(aLine) else -- A subsequent click removes the hilite tColor = paletteIndex(0) plSelection.deleteOne(aLine) end if tAlpha.fill(tRect, tColor) pmBitmap.image.setAlpha(tAlpha) return aHilite end mToggleOneLine on mGetScrollRatio(me) ------------------------------------------------ -- CALLED by mInitialize() and exitFrame() -- OUTPUT: Returns the number of pixels hidden above the top of the -- visible section of the text member. A bug in text -- members in Director 7.0 through DMX 2004 returns the -- wrong value for .scrollTop if the height of the member is -- greater than 16383 pixels. This handler works around -- that bug, and allows for the possibility that the bug -- will have been fixed in later versions of Director. --------------------------------------------------------------------- tHeight = pmText.height if tHeight < 16384 then return 1 end if -- Set the scrollTop of the member to its maximum, then determine -- whether it reached that value. tScrollTop = pmText.scrollTop tHeight = tHeight - pmText.pageHeight pmText.scrollTop = tHeight tRatio = tHeight / pmText.scrollTop pmText.scrollTop = tScrollTop return tRatio end mGetScrollRatio -- BEHAVIOR PARAMETERS AND DESCRIPTION -- on isOKToAttach(me, aSpriteType, aSpriteNumber) if aSpriteType = #graphic then return sprite(aSpriteNumber).member.type = #text end if return FALSE end isOKToAttach on getPropertyDescriptionList(me) tPropertyList = [:] tPropertyList[ \ #hiliteColor] = [ \ #comment: "Hilite color:", \ #format: #color, \ #default: rgb(0, 0, 255) \ ] tPropertyList[ \ #blendLevel] = [ \ #comment: "Blend level (less is lighter):", \ #format: #integer, \ #range: [#min: 0, #max: 255], \ #default: 64 \ ] return tPropertyList end getPropertyDescriptionList