-- ============= PUBLIC METHODS =========================
-- BeginSprite: "myDelim" and "myDoubleClickSpeed" can be changed
-- if you wish. The default delimiter here is a 4-space TAB.
-- If you set the click speed to 1 tick then the field display
-- will change quickly, but doubleclicks will not send events
-- out to other behaviors on the sprite.
on beginSprite me
set myDelim to " "
set myDoubleClickSpeed to 10
set myField to the member of sprite spriteNum
if the type of myField <> #field then
alert "The Outliner behavior in channel " & spriteNum & " works with system textfields only. Please remove it or reassign it."
halt
end if
CheckToSaveFieldContents me
set myFullText to GetOriginalText(script myField)
set the text of myField to GetCollapsedText (me)
end
on mouseUp me
set clicked to the mouseLine
if clicked < 1 then exit
CheckForDoubleClick me, clicked
case (word 1 of line clicked of the text of myField) of
"+": Expand me, clicked
"-": Collapse me, clicked
otherwise: put "Error clicking: " & line clicked of the text of myField
end case
end
-- Send this optional message to the sprite to put the original
-- text for editing back into the field. Plus and minus signs
-- will not be included.
on Outliner_RestoreFullText me
set the text of myField to GetOriginalText(script myField)
end
-- Send this optional message to the sprite to display only the
-- topmost level of the outline in the field. Plus signs
-- will be included.
on Outliner_CollapseAllText me
set the text of myField to GetCollapsedText (me)
end
-- Send this optional message to the sprite to display all text
-- in expanded outline form. All plus and minus signs will
-- be included.
on Outliner_ExpandAllText me
set rawSource to GetOriginalText(script myField)
set dest to ""
set numLines to the number of lines in rawSource
repeat with i = 1 to numLines
set thisLine to line 1 of rawSource
put "- " before word 1 of thisLine
put thisLine & RETURN after dest
delete line 1 of rawSource
end repeat
delete the last char of dest
set the text of myField to dest
end
-- SENDING MESSAGES WHEN THE DISPLAY CHANGES:
--
-- This behavior will send an event named "Outliner_DoubleClick"
-- to the field sprite if you doubleclick an entry. This event
-- will be safely ignored if you do not choose to handle it in
-- another behavior on this sprite.
--
-- But if you do wish to capture a doubleclicked line in the
-- outliner and do something with it, then make a new behavior
-- for this sprite similar to the following, but doing what
-- you wish instead of this example:
--
--property spriteNum
--
--on Outliner_DoubleClick me, chosenText
-- case (chosenText) of
-- "Introduction": tell the stage to go "intro"
-- "Chapter 1": tell the stage to go movie "chpt1"
-- "Chapter 2": tell the stage to go movie "chpt2"
-- otherwise: put chosenText
-- end case
--end
-- Called by beginSprite to initialize the field's display.
-- Returns only the top level of text.
on GetCollapsedText me
set source to myFullText
set displayText to ""
set numLines to the number of lines of source
repeat with i = 1 to numLines
set thisLine to line 1 of source
delete line 1 of source
if GetDepth(thisLine) = 0 then
if GetDepth(line 1 of source) = 0 then put "- " before thisLine
else put "+ " before thisLine
put thisLine & RETURN after displayText
end if
end repeat
delete the last char of displayText
return displayText
end
-- Called by mouseUp handler. Will delay execution of a click until
-- the doubleClickSpeed has been exceeded. If a single click, then
-- execution reverts to the mouseUp handler. If a doubleclick, then
-- list will not be expanded or collapsed, but an "Outliner_DoubleClick"
-- event will be sent to any behaviors on the sprite which wish to
-- handle a doubleclicked line. (For a simple outliner that does not
-- change the environment you'd not need to handle that event.)
on CheckForDoubleClick me, clicked
if the doubleClick then abort
startTimer
repeat while the timer < myDoubleClickSpeed
if the mouseDown then
set theChoice to line clicked of the text of myField
delete char 1 to offset(word 2 of theChoice, theChoice) - 1 of theChoice
sendSprite(spriteNum, #Outliner_DoubleClick, theChoice)
abort
end if
end repeat
end
-- Called from Collapse and Expand routines. Given a line of
-- text, it will return an integer from zero up of how many
-- TAB units the line is indented.
on GetDepth whatText
return (offset(word 1 of whatText, whatText) - 1) / length(myDelim)
end
on Expand me, clicked
set oldDisplay to the text of myField
set chosenText to line clicked of oldDisplay
delete word 1 of chosenText
-- Set "rawSource" to be all raw text which follows the chosen line:
set rawSource to myFullText
delete char 1 to offset(chosenText, rawSource) of rawSource
delete line 1 of rawSource
put "- " before word 1 of chosenText
if clicked > 1 then
set displayText to line 1 to (clicked - 1) of the text of myField
put RETURN & chosenText after displayText
else set displayText to chosenText
set parentDepth to GetDepth(chosenText)
set childDepth to GetDepth(rawSource)
-- Show any nested lines one layer below the clicked line:
repeat while childDepth > parentDepth
if childDepth = parentDepth + 1 then
set nextChildDepth to GetDepth(line 2 of rawSource)
if nextChildDepth > childDepth then put "+ " before word 1 of rawSource
else put "- " before word 1 of rawSource
put RETURN & line 1 of rawSource after displayText
end if
delete line 1 of rawSource
set childDepth to GetDepth(rawSource)
end repeat
-- And then add the rest of the old display to the end of the new display:
set totalLines to the number of lines in oldDisplay
set remainingLines to line (clicked + 1) to totalLines of oldDisplay
put RETURN & remainingLines after displayText
set the text of myField to displayText
end
on Collapse me, clicked
set oldDisplay to the text of myField
set chosenText to line clicked of oldDisplay
set parentDepth to GetDepth(chosenText)
if GetDepth(line clicked + 1 of oldDisplay) > parentDepth then
delete word 1 of chosenText
put "+ " before word 1 of chosenText
end if
if clicked > 1 then
set displayText to line 1 to (clicked - 1) of oldDisplay
put RETURN & chosenText after displayText
else set displayText to chosenText
delete line 1 to clicked of oldDisplay
set childDepth to GetDepth(oldDisplay)
repeat while childDepth > parentDepth
delete line 1 of oldDisplay
set childDepth to GetDepth(oldDisplay)
end repeat
put RETURN & oldDisplay after displayText
set the text of myField to displayText
end
-- This handler determines whether the field contents need to be
-- saved or not. If there is no stored text, or if there are more
-- lines of text in the field than are stored, then the
-- ContentsToScript handler will be called to stuff the field text
-- into the member script of the field itself.
on CheckToSaveFieldContents me
if the scriptText of myField = "" then ContentsToScript me
set storedLines to the number of lines in GetOriginalText(script myField)
set newLines to the number of lines in the text of myField
if newLines > storedLines then ContentsToScript me
end
-- ContentsToScript will store the regular text within the script of
-- the field itself. Called by CheckToSaveFieldContents, subject
-- to its conditions. This is a tricky handler to read, because it
-- evaluates a field and constructs another script which returns the
-- RETURN-delimited text... a script which turns text to script to text.
-- Only the one line "if '+-' contains..." is unique to this behavior,
-- and this handler as a whole can be used in other behaviors too.
on ContentsToScript me
set source to the text of myField
set theScript to "on GetOriginalText" & RETURN
put "set theString to EMPTY" & RETURN after theScript
set numLines to the number of lines in source
repeat with i = 1 to numLines
-- If field is in outline form, turn it to plain text:
if "+-" contains word 1 of source then delete word 1 of source
put ("put RETURN & " "E& line 1 of source "E& " after theString" & RETURN) after theScript
delete line 1 of source
end repeat
put "delete char 1 of theString" & RETURN after theScript
put "return theString" & RETURN after theScript
put "end" & RETURN & RETURN after theScript
set the scriptText of member myField to theScript
end
-- ============= STANDARD METHODS =========================
on getBehaviorDescription me
set line1 to "Drop this behavior on a field that has a consecutive lines in a TAB-indented outline structure, to display a collapsing and expanding outline of the contents. "
set line2 to "Click on the plus sign to expand a topic; click on a minus sign to collapse a topic." & RETURN & RETURN
set line3 to "The first time this behavior is run it will store a copy of the full text within the castmember script of the field itself. "
set line4 to "The castmember script will be regenerated if you delete the castmember script, or if the field contains more lines than that originally held in the script." & RETURN & RETURN
set line5 to "Doubleclicking on any item will send an 'Outliner_DoubleClick' message, along with the chosen text, to other behaviors on that field sprite for navigational uses."
set line6 to "You do not need to handle this event if you do not wish doubleclicks to trigger actions elsewhere on screen." & RETURN & RETURN
set line7 to "To edit the outline: " & RETURN
set line8 to " 1) If you kept a copy of the source text in a dummy field, then copy this source back into the field. (You can also type in the Message Window 'sendAllSprites(#Outliner_RestoreFullText)' while the movie is running to restore the source text too.) " & RETURN
set line9 to " 2) Edit the text, making sure it remains TAB-indented to maintain your outline structure." & RETURN
set line10 to " 3) If the edited text is shorter than the original text, then open the field's castmember script, select-all, and delete." &RETURN
set line11 to " 4) Run the movie again. The edited text will be safely stored in the field's castmember script, and you will again see the top level of the outline." & RETURN & RETURN
set line12 to "Note that all text in the field will use the same font, style, and size... different formats for different characters are not remembered by this behavior." & RETURN & RETURN
set line13 to "The text should be in consecutive lines... blank lines will be marked as empty lines, with a minus sign. "
set line14 to "(When typing a field it's often common to put an extra RETURN after all text -- this will produce the 'solitary minus' symptom.)"
& RETURN & RETURN