A text bahavior to display TreeView data. Support multiple selection and Drag and drop
-- TreeView behavior 1.1 with Drag And Drop
-- Created by Alexandre GHEZ (aghez@iname.com)
-- Visit my home page for full documentation and samples : http://come.to/aghez
-- Variables for the Tree
property ma_Tree, mi_TreeIndex, ma_GhostTree, mi_LastSelectedLine, mi_LastDisplayedLine, mb_CurrentlyDragging
property mi_RolledLine, ms_TreeMember, mi_TreeSprite, mi_TreeDisplayedLength
property mi_TreeLength, mb_DropAllowed, mb_DropOnMe, mb_RollOutDone
la_BehaviorProperty = [:]
-- Create the Tree information
setaProp la_BehaviorProperty, #ps_DataMember, [#comment: "Text data source",#default:false,format: #field]
setaProp la_BehaviorProperty, #ps_ScriptWhenClicked, [#comment: "Lingo code when clicked ", format: #string,#default:""]
-- Tree behavior--
setaProp la_BehaviorProperty, #pb_IsToggable, [#comment: "Tree can be deployed / Contracted ", format: #boolean,#default:false]
setaProp la_BehaviorProperty, #pi_CursorAction, [#comment: "Cursor for toggle", format: #cursor,#default:280]
setaProp la_BehaviorProperty, #pb_SelectMultiple, [#comment: "Multiple selection enabled", format: #boolean,#default:true]
-- Tree rendering
setaProp la_BehaviorProperty, #pb_DisplayIcon, [#comment: "Display icons for file/folder", format: #boolean,#default:true]
setaProp la_BehaviorProperty, #pb_DisplayChecked, [#comment: "Check viewed items", format: #boolean,#default:true]
setaProp la_BehaviorProperty, #ps_FontRender, [#comment: "Font used for display", format: #font,#default:0]
setaProp la_BehaviorProperty, #ps_FontRenderSelected, [#comment: "Font used for selected", format: #font,#default:0]
setaProp la_BehaviorProperty, #pi_LineHeight, [#comment: "Line Height", format: #number,#default:16]
setaProp la_BehaviorProperty, #pi_CursorDefault, [#comment: "Default cursor", format: #cursor,#default:280]
--- Parameters for the Drag
setaProp la_BehaviorProperty, #pb_AllowDrag, [#comment: "Drag from the tree", format: #boolean,#default:false]
setaProp la_BehaviorProperty, #pi_CursorOver, [#comment: "Cursor when mouse is over", format: #cursor,#default:289]
setaProp la_BehaviorProperty, #pi_CursorDown, [#comment: "Cursor when mouse is down", format: #cursor,#default:290]
setaProp la_BehaviorProperty, #pi_CursorStop, [#comment: "Cursor when roll do not accept drop", format: #cursor,#default:291]
setaProp la_BehaviorProperty, #ps_DragMember, [#comment: "Member filled by the selection", format: #text,#default:""]
setaProp la_BehaviorProperty, #pi_DragSprite, [#comment: "Sprite used by the drag selection", format: #sprite,#default:120]
setaProp la_BehaviorProperty, #pa_DropTarget, [#comment: "List of sprites that accept the drop from this la_TreeItem", format: #String,#default:"[]"]
setaProp la_BehaviorProperty, #ps_DropBehavior, [#comment: "Behavior for the drop", format: #Symbol,#default:#move, range:[#move,#copy,#Check]]
--Parameters for the drop
setaProp la_BehaviorProperty, #pb_AllowDrop, [#comment: "Allow Dropped items", format: #boolean,#default:false]
---Hilight parameters
setaProp la_BehaviorProperty, #pb_Frame, [#comment: "Has rect when selected", format: #boolean,#default:false]
setaProp la_BehaviorProperty, #pa_FrameSprite, [#comment: "Sprite rect ", format: #sprite,#default:20]
setaProp la_BehaviorProperty, #ps_FrameMember, [#comment: "Rect member ", format: #member,#default:false]
if ps_ScriptWhenClicked <> "" then
do ps_ScriptWhenClicked && la_Item
end if
DisplayTree()
End CreateFolder
On GetItemLabel me
li_Position = getLastSelectedItem("DontSkip")
If li_Position And li_Position <= mi_TreeLength Then
Return ma_Tree[li_Position].label
else
return ""
End If
End GetItemLabel
On RollElement me, ls_Type
-- Called when the mouse is over the Tree and the Tree accept Drop
if not pb_AllowDrop Then
Return
End If
If ls_Type = #Out Then
If Not mb_RollOutDone Then
mi_RolledLine = 0
mb_RollOutDone = True
If mi_TreeIndex > 0 Then
ma_tree[mi_TreeIndex].selected = 0
Else If mi_LastDisplayedLine > 0 Then
ma_tree[mi_LastDisplayedLine].selected = 0
End If
DisplayTree()
End If
return
End If
If li_RollLine <> mi_RolledLine Or li_RollLine = 1 Then
mi_RolledLine = li_RollLine
If li_RollChar <= mi_TreeDisplayedLength Then
mi_TreeIndex = GetTreeItemPosition(li_RollLine)
Else
mi_TreeIndex = mi_LastDisplayedLine
lb_AfterTree = True
End If
-- Reset the selection
repeat with la_TreeItem in ma_Tree
la_TreeItem.selected = 0
End repeat
-- If the item is a closed folder, we select it, otherwise, we underline the element
If mb_DropAllowed then
if mi_TreeIndex and (mi_TreeIndex <= mi_TreeLength) Then
If ma_Tree[mi_TreeIndex].parent and Not ma_Tree[mi_TreeIndex].Status And Not lb_AfterTree Then
ma_Tree[mi_TreeIndex].selected = 1
else
ma_Tree[mi_TreeIndex].selected = 2
--- If we are on the last line and she is underlined
If mi_TreeIndex = mi_LastDisplayedLine Then
set lb_AfterTree = True
End if
End If
End if
End If
DisplayTree()
End If
If mb_DropAllowed Then
set the cursor of sprite pi_DragSprite to pi_CursorDown
else
set the cursor of sprite pi_DragSprite to pi_CursorStop
end if
-- If we are After the Tree, Reset the position
If lb_AfterTree Then
mi_TreeIndex = 0
End If
End RollElement
On DropElement me, la_DroppedData
-- Called when Drop occurs on the Tree
if not pb_AllowDrop Then
Return
End If
li_LevelShift = 0
If mi_TreeIndex = 0 Then
--- Drop after the Tree --
mi_TreeIndex = mi_LastDisplayedLine
lb_DeselectDrop = False
If mi_TreeLength <> 0 Then
-- If last item is a closed folder, we put it at the same level after his childs
-- If last item is open folder, we put it just under the folder
-- Il last item is a file, we append it after the file with the same level
If ma_Tree[mi_TreeIndex].parent And Not ma_Tree[mi_TreeIndex].status And lb_Selected = 2 Then
li_LevelShift = 0
ma_Tree[mi_TreeIndex].Selected = 1
lb_DeselectDrop = True
lb_MustDecrement = 1
mi_TreeIndex = mi_TreeIndex + 1
Repeat While (mi_TreeIndex <= mi_TreeLength)
If (ma_Tree[mi_TreeIndex].level > li_Level) Then
mi_TreeIndex = mi_TreeIndex + 1
lb_MustDecrement = 1 --- Or 0 if it is a empty folder ???
Else
exit Repeat
End If
End Repeat
mi_TreeIndex = mi_TreeIndex - lb_MustDecrement
mi_TreeIndex = Min(mi_TreeIndex, mi_TreeLength)
Else If ma_Tree[mi_TreeIndex].parent And ma_Tree[mi_TreeIndex].status Then
-- One more shift
li_LevelShift = li_LevelShift + 1
Else
-- No Shift
li_LevelShift = 0
End If
End If
Else
-- Drop inside the Tree --
If mi_TreeLength <> 0 Then
li_Level = ma_Tree[mi_TreeIndex].level
-- Reset the selection
ma_Tree[mi_TreeIndex].selected = 0
lb_DeselectDrop = False
--- closed folder, Tree is appended
if ma_Tree[mi_TreeIndex].parent and Not ma_Tree[mi_TreeIndex].status Then
--- Drop inside a closed folder
--- the closed folder will be the selection, the dropped data are deselected
lb_DeselectDrop = True
ma_Tree[mi_TreeIndex].selected = 1
lb_MustDecrement = 1
mi_TreeIndex = mi_TreeIndex + 1
Repeat While (mi_TreeIndex <= mi_TreeLength)
If (ma_Tree[mi_TreeIndex].level > li_Level) Then
mi_TreeIndex = mi_TreeIndex + 1
lb_MustDecrement = 1
Else
exit Repeat
End If
End Repeat
mi_TreeIndex = mi_TreeIndex - lb_MustDecrement
mi_TreeIndex = Min(mi_TreeIndex, mi_TreeLength)
-- Save the Shift level of the last item of the childrens
li_LevelShift = li_Level
Else
li_LevelShift = ma_Tree[mi_TreeIndex].level - Not ma_Tree[mi_TreeIndex].parent
End If
Else
mi_TreeIndex = 0
li_LevelShift = 0
End If
End If
la_TreeItem.level = la_TreeItem.Level + li_LevelShift
If lb_DeselectDrop Then
la_TreeItem.selected = 0
End If
AddAt ma_Tree, li_Insert_Index, la_TreeItem
li_Insert_Index = li_Insert_Index + 1
End Repeat
mi_RolledLine = 0
mi_TreeLength = Count(Ma_Tree)
DisplayTree()
return 1
End DropElement
on DrawItemFrame me,li_Line
-- Frame the displayed line if li_Line > 0 then
set ma_FrameRect=GetLineRect(li_Line)
else
set ma_FrameRect=rect(-100,-100,-98,-98)
end if
if ma_FrameRect <> [] then
set the rect of sprite pa_FrameSprite to ma_FrameRect
updatestage
end if
end DrawItemFrame
On CheckItem me,li_Line
-- Check the displayed line li_TreeIndex = GetTreeItemPosition(li_Line)
ma_Tree[li_TreeIndex].viewed = 1
DisplayTree()
End CheckItem
On CloseTree lb_DontDisplay
-- Close all folders
Repeat With li_TreeIndex = 1 To mi_TreeLength
ma_Tree[li_TreeIndex].status = 0
End Repeat
If voidP(lb_DontDisplay) Then
DisplayTree()
End If
End CloseTree
On OpenTree lb_DontDisplay
-- Expand all folders
Repeat With li_TreeIndex = 1 To mi_TreeLength
ma_Tree[li_TreeIndex].status = 1
End Repeat
If voidP(lb_DontDisplay) Then
DisplayTree()
End If
End OpenTree
On ToggleItem me,li_Line
-- Toggle Tree Folder
li_TreeIndex = GetTreeItemPosition(li_Line)
ma_Tree[li_TreeIndex].status = Not (ma_Tree[li_TreeIndex].status)
DisplayTree()
end ToggleItem
On ResetTree
---Close all folders, and change the selection to 0
Repeat with la_TreeItem in Ma_Tree
la_TreeItem.selected = 0
la_TreeItem.status = 0
End repeat
End ResetTree
On SelectItem me,li_Line, lb_ForceEmpty
-- Select the item
If Not pb_SelectMultiple Then
lb_ForceEmpty = "True"
end if
lb_shiftKey = the shiftDown and VoidP(lb_ForceEmpty)
lb_ControlKey = the controlDown And VoidP(lb_ForceEmpty)
li_TreeIndex = GetTreeItemPosition(li_Line)
If Not li_TreeIndex Then
-- We click outside the Tree
Return
End If
--No modifiers keys, reset the selection
If Not lb_ShiftKey And Not lb_ControlKey Then
Repeat With la_TreeItem In ma_Tree
la_TreeItem.selected = 0
End Repeat
ma_Tree[li_TreeIndex].selected = 1
End If
if lb_ControlKey then
-- Discontinue selection, toggle the selection
ma_Tree[li_TreeIndex].selected = Not ma_Tree[li_TreeIndex].selected
end if
if lb_ShiftKey Then
-- Continue Selection
if voidP(mi_LastSelectedLine) Then
mi_LastSelectedLine = li_Line
End If
repeat with li_Line_Index = li_StartLine To li_EndLine
ma_Tree[GetTreeItemPosition(li_Line_Index)].selected = 1
end repeat
end if
mi_LastSelectedLine = li_Line
DisplayTree()
End SelectItem
-- Events Handler
On BeginSprite me
mi_TreeSprite=the spritenum of me
ms_TreeMember=the member of sprite mi_TreeSprite
pa_DropTarget = value(pa_DropTarget)
mb_CurrentlyDragging = False
mb_DropAllowed = True
mb_DropOnMe = False
mb_RollOutDone = True
if pb_Rollover then
PuppetSprite pi_RolloverSprite,true
set the ink of sprite pi_RolloverSprite to 9 ----MASK
set the member of sprite pi_RolloverSprite to member ps_RolloverMember
set the loc of sprite pi_RolloverSprite to point(-1000,-1000)
end if
if pb_Frame then
PuppetSprite pa_FrameSprite,true
set the ink of sprite pa_FrameSprite to 9 ----MASK
set the member of sprite pa_FrameSprite to member ps_FrameMember
set the loc of sprite pa_FrameSprite to point(-1000,-1000)
end if
InitTree()
end BeginSprite
On EndSprite me
if pb_Rollover then
PuppetSprite pi_RolloverSprite,false
end if
if pb_Frame then
PuppetSprite pa_FrameSprite,false
end if
end EndSprite
on Mouseleave
if pb_Rollover then
set the loc of sprite pi_RolloverSprite to point(-1000,-1000)
end if
end MouseLeave
on mousewithin
set li_Cursor = pi_CursorDefault
li_RolledChar = PointToChar(sprite mi_TreeSprite,the mouseLoc)
If li_RolledChar > 0 Then
ls_Font = member(ms_TreeMember).char[li_RolledChar].font
Case ls_Font of
"Webdings *" : li_Cursor = pi_CursorAction
"Wingdings *" : li_Cursor = pi_CursorOver
end case
End If
set the cursor of sprite mi_TreeSprite to li_Cursor
if pb_Rollover then
la_RollRect=getLineRect()
if la_RollRect<>[] then
set the rect of sprite pi_RolloverSprite to la_RollRect
updatestage
end if
end if
end mousewithin
On mouseDown me
la_clickLoc = the MouseLoc
--- We use the mousedown handler to manage the Drag and Drop
if pb_AllowDrag Then
--If modifiers keys, exit
if the shiftDown or the ControlDown then
return
end if
li_StartTimer = the timer
repeat while the mouseDown And ((the timer - li_StartTimer) < 30)
nothing
end repeat
If the mousedown then
-- Retrieve the clicked line (so it will be selected in the ghost tree)
li_Line= GetTreeItemPosition(PointToLine(sprite mi_TreeSprite,la_clickLoc))
li_Char = PointToChar(sprite mi_TreeSprite, la_clickLoc)
-- Clic outside the Tree, exit
if li_Char > mi_TreeDisplayedLength Then
return
End If
-- If click occurs on the toggle elements , exit
If member(ms_TreeMember).Char[li_Char].font = "Webdings *" then
return
end if
-- If line is not selected, reset the selection and use this line as the selection
If ma_Tree[li_Line].selected = 0 Then
repeat with li_Tree_Index = 1 to Count(ma_Tree)
ma_Tree[li_Tree_Index].selected = 0
End repeat
-- We move the ghost Tree under the mouse
puppetSprite pi_DragSprite, True
set the cursor of sprite mi_TreeSprite to pi_CursorDown
set the ink of sprite pi_DragSprite to 8
set the member of sprite pi_DragSprite to member ps_DragMember
set the loc of sprite pi_DragSprite to Point(1000,1000)
UpdateStage
-- The selected Icon and line is under the mouse
li_Shift_x = la_Selected_Info[#Selectedlevel] * 12 + 12
li_Shift_y = la_Selected_Info[#Selectedline] * pi_LineHeight - 8
la_SelectedItems = la_Selected_Info[#selectedItems]
mi_RolledLine = -1
mb_DropAllowed = True
Repeat While the mousedown
lb_SendRoll = False
-- Stop Icon if the mouse is not over a droppable element
Repeat with li_Sprite in pa_DropTarget
If Rollover(li_Sprite) Then
lb_SendRoll = True
exit Repeat
End if
End Repeat
If Not lb_SendRoll Then
set the Cursor of sprite pi_DragSprite to pi_CursorStop
li_Sprite = -1
Else If li_Sprite <> mi_TreeSprite Then
set the Cursor of sprite pi_DragSprite to pi_CursorDown
End If
la_MousePosition = the mouseLoc
-- Determine if we are on his childrens --
mb_DropOnMe = ((li_Sprite = mi_TreeSprite) and ps_DropBehavior = #move)
If mb_DropOnme then
If GetPos(la_SelectedItems, li_RolledItem) > 0 Then
-- Roll over is childrens
mb_DropAllowed = False
else
mb_DropAllowed = True
end if
End If
Repeat with li_SpriteRoll in pa_DropTarget
If li_SpriteRoll <> li_Sprite Then
SendSprite li_SpriteRoll, #RollElement,#out
End if
end repeat
If lb_SendRoll Then
-- Mouse over a droppable element, send him code
SendSprite li_Sprite, #RollElement,#in
Else
SendSprite li_Sprite, #RollElement,#Out
End If
set li_x = la_MousePosition[1] - li_Shift_X
set li_y = la_MousePosition[2] - li_Shift_Y
set the rect of sprite pi_DragSprite to Rect( li_x, li_y , li_x + 400, li_y + 150)
UpdateStage
end repeat
PuppetSprite pi_DragSprite, False
set the cursor of sprite mi_TreeSprite to -1
-- Mouse is Up, send the Drop event
If mb_DropAllowed Then
FlagDropDone = False
repeat with li_Sprite in pa_DropTarget
If Rollover(li_Sprite) Then
SendSprite li_Sprite, #DropElement, ma_GhostTree
flagDropDone = The result
end if
end repeat
--depending of the behavior, we make some stuff on the original Tree
If FlagDropDone Then
case (ps_DropBehavior) of
#Check:
--- Just Check the dropped items
repeat with li_TreeIndex In la_SelectedItems
ma_Tree[li_TreeIndex].viewed = 1
End Repeat
DisplayTree()
#copy:
---Nothing
DisplayTree()
#Move :
-- Move elements, delete dropped items from the source
li_DeletedIndex = 0
If mb_DropOnMe Then
-- Warning ! the data are added before deleted, so the tree change
-- If the selection was after the roll, we must delete this elements
If li_RolledItem >0 And li_RolledItem < la_selectedItems[1] Then
li_DeletedIndex = 0
la_SelectedItems = la_SelectedItems + count(ma_GhostTree)
End If
End If
repeat with li_TreeIndex In la_SelectedItems
DeleteAt ma_Tree, li_TreeIndex + li_DeletedIndex
DisplayTree()
end case
End If
End If
end if
end if
End MouseDown
on mouseUp me
--If we have done a drag and drop, exit
if mb_CurrentlyDragging or Not Rollover(mi_TreeSprite) Then
set mb_CurrentlyDragging = False
return
end if
-- If tree is empty, exit
if Not mi_TreeLength Then
Return
End If
If li_ClickedLine = -1 or li_ClickedChar = -1 Then
return
End If
---Draw Item rect
if pb_Frame then
DrawItemFrame(me)
end if
-- Toggle the Status
if pb_IsToggable then
--Check the font ( + and - icons are in Webdings font)
If member(ms_TreeMember).char[li_ClickedChar].font = "Webdings *" Then
ToggleItem me,li_ClickedLine
return
end if
end if
-- Select the line
If member(ms_TreeMember).char[li_ClickedChar].font <> "Webdings *" Then
SelectItem me, li_ClickedLine
End If
-- Execute Lingo Code
if ps_ScriptWhenClicked <> "" then
li_ItemPosition = GetTreeItemPosition(li_ClickedLine)
If li_ItemPosition > 0 Then
la_Item = ma_Tree[li_ItemPosition]
do ps_ScriptWhenClicked && la_Item
end if
end if
end mouseup
-- Main functions --
on initTree me, la_Tree
-- Populate the list
If Not VoidP(la_tree) Then
ma_Tree = la_Tree
Else
set ls_Data =the text of member ps_DataMember
if char 1 of ls_Data = "[" Then
-- If this is a list, we assume it have the good format
ma_Tree = Value(ls_Data)
Else
-- Not a list, the following style is required (Tab as FieldSeparator and Return as RecordSeparator)
--
Contact
MMI
36 South Court Sq
Suite 300
Newnan, GA 30263
USA