; ------------------------------------------------------------------
;
;   Drag & Drop example with 
;   ExplorerListGadget and ExplorerTreeGadget
;
;   By Timo 'fr34k' Harter
;
; ------------------------------------------------------------------
;
; This example shows how to implement drag & drop between
; Explorer type gadgets in PureBasic. It is done very similar
; to the other drag & drop exmaples i wrote, so if you read them,
; you might find that this one uses basically all the same techniques.
;
; However, the explorer gadgets are a little more complicated than a
; basic ListIcon or TreeGadget, so this code is longer, but it
; should not be much more complicated than the others.
;
; As always, feel free to use any of the code/information given
; here in whichever way you want.
;
; ------------------------------------------------------------------

; These are the 3 Constants for the 3 needed objects: the main window and
; the two gadgets. If you modify this code to fit into your project,
; make sure you replace all occourances of these with your own constants.
; It should work with no problem with #PB_Any created onjects, you just
; have to replace all the constants with the variables then.
#Window       = 0
#ExplorerTree = 0
#ExplorerList = 1


; One note first for better understanding:
; The explorergadgets have like some of the other more
; complicated PB Gadgets an own invisible parent Window for their
; event procession. This is no problem, it just means, that you
; can't get their messages from the main WindowCallback, because
; they never end up there. You need to subclass that invisible
; parent window as described later to get these messages.

; Ok, here's the required global stuff. The callback variables
; are needed for the subclassing and explained below. IsDraging
; is #true when drag&drop is in progress and DragImageList stores
; the imagelist that contains the drag image.
; The other variables should be clear.

; You have to know though that 'SourceItem' and 'TargetItem' do contain
; the item index if the Gadget is an ExplorerListGadget and contain 
; an Item handle, when it is an ExplorerTreeGadget. This is because
; a treeview control internally only uses handles, and no index.

Global RealCallback_ExplorerList, RealCallback_ExplorerTree
Global IsDraging, SourceGadget, SourceItem, TargetGadget, TargetItem
Global DragImageList


; Ok, now about the subclassing. Each window and gadget in windows
; has a callback procedure that handles its messages. Subclassing
; just means that you replace that procedure with your own, and at the
; end of your procedure call the original one. This way, there can be
; a chain of subclasses, each procedure calling the next, and everybody
; can process the messages he needs.
; So the global values above are used to store the adress of the real
; callbacks of the invisible parent window of the gadgets.

; So here we have our replacement procedure for the ExplorerListGadget's
; parent window. We only need to catch one message here. The one that
; starts the draging, so this procedure is quite short.

Procedure Callback_ExplorerList(Window, Message, wParam, lParam)

  ; LVN_BEGINDRAG is the message inicating a d&d start. It is a #WM_NOTIFY
  ; message. So we get #WM_NOTIFY, and then there is a structure pointer
  ; in lParam, that contains the actual message data:
  
  If Message = #WM_NOTIFY
    *nmv.NMLISTVIEW = lParam
    
    If *nmv\hdr\code = #LVN_BEGINDRAG
      
      ; no target hilighted yet:
      TargetGadget = -1
      TargetItem   = -1    

      ; since this procedure subclasses only the one gadget, there is no
      ; need to ckeck for gadget type, as they can only come from this one.
      SourceGadget = #ExplorerList
      SourceItem   = GetGadgetState(#ExplorerList)
      
      ; This is a little check to make the ".." item undragable.
      ; you can ckeck for other types of items here, that you don't want to
      ; be draged. Just don't execute the following code then.
      If GetGadgetItemText(#ExplorerList, SourceItem, 0) <> ".."
        
        ; this message creates the drag image:        
        DragImageList = SendMessage_(GadgetID(#ExplorerList), #LVM_CREATEDRAGIMAGE, SourceItem, @UpperLeft.POINT)
        
        ; check for success:
        If DragImageList 
        
          ; ok, this starts the d&d operation
          If ImageList_BeginDrag_(DragImageList , 0, 0, 0)
            
            ; show the image
            ImageList_DragShowNolock_(#TRUE)
            
            ; send all mouse messages to our main window (and thus main window callback)
            SetCapture_(WindowID(#Window))
            
            ; hide the normal cursor
            ShowCursor_(#FALSE)

            ; indicate that d&d is in progress
            IsDraging = #TRUE
            
          EndIf ; ImageList_BeginDrag_()
        
        EndIf ; check DragImageList
        
      EndIf ; check ".." item
    EndIf ; check #LVN_BEGINDRAG
  EndIf ; check #WM_NOTIFY

  ; Now we need to call the original procedure. This is really important,
  ; as otherwise the gadget won't work at all.
  ProcedureReturn CallWindowProc_(RealCallback_ExplorerList, Window, Message, wParam, lParam)
EndProcedure



; This is exactly the same for the ExplorerTreeGadget. The way of doing things is
; the same, just the structures and messages are a little different.

Procedure Callback_ExplorerTree(Window, Message, wParam, lParam)

  If Message = #WM_NOTIFY
    *nmtv.NMTREEVIEW = lParam 
    
    If *nmtv\hdr\code = #TVN_BEGINDRAG    

      TargetGadget = -1
      TargetItem   = -1    

      SourceGadget = #ExplorerTree
      
      ; The source item handle is stored in that structure:
      SourceItem   = *nmtv\itemNew\hItem
      
      ; note that this message functions a little different from the listicon one.
      ; have a look at MS documentation on LVM_CREATEDRAGIMAGE and TVM_CREATEDRAGIMAGES
      ; for the exact description.
      DragImageList = SendMessage_(GadgetID(#ExplorerTree), #TVM_CREATEDRAGIMAGE, 0, SourceItem)
      
      ; this part is exactly the same:
      If DragImageList 
        If ImageList_BeginDrag_(DragImageList , 0, 0, 0)

          ImageList_DragShowNolock_(#TRUE)
          SetCapture_(WindowID(#Window))
          ShowCursor_(#FALSE)

          IsDraging = #TRUE
        EndIf    
      
      EndIf
           
    EndIf
  EndIf
  
  ; also call the real procedure:  
  ProcedureReturn CallWindowProc_(RealCallback_ExplorerTree, Window, Message, wParam, lParam)
EndProcedure

; Ok, the code to start the d&d operation is done. Now we need to implement
; the mouse moves, the target hilightning and the end of the operation.
; This is done in the main callback, because with "SetCapture_(WindowID(#Window))",
; we directed all mouse messages there.

; The next 2 procedures are just seperated, because this code needs to be called
; several times from the main callback.

; This procedure checks, which Gadget the mouse is currently over, and hilights
; the target. it also updates the TargetGadget and TargetItem variables.
; Note that MouseX and MouseY are client coordinates of the main window, not like
; WindowMouseY() and WindowMouseY() which include the window border.
; Note that while this procedure is called, the drag image must be hidden to aboid
; some ugly graphics.

Procedure HilightTarget(MouseX, MouseY)

  ; get the handle of the 'window' under the mouse:
  ; note that for explorer type gadgets, you get the handle of the invisible
  ; parent window here, so we have to compare this value to "GetParent_(GadgetID(#Gadget))"
  TargetID = ChildWindowFromPoint_(WindowID(#Window), MouseX, MouseY)
  
  ; because both gadgets can be both target or source (you can even drag within one gadget)
  ; so the code must always work for both gadgets:
  
  If TargetID = GetParent_(GadgetID(#ExplorerList))
    TargetGadget = #ExplorerList
    
    ; this checks, which item in the gadget the mouse is over:  
    lv_hittestinfo.LV_HITTESTINFO
    lv_hittestinfo\pt\x = MouseX - GadgetX(#ExplorerList)
    lv_hittestinfo\pt\y = MouseY - GadgetY(#ExplorerList)    
    
    ; note that the result here is an item index, that works with the PB gadget functions:
    TargetItem = SendMessage_(GadgetID(#ExplorerList), #LVM_HITTEST, 0, @lv_hittestinfo) 
    
    ; now we hilight the item:    
    lv_item.LV_ITEM
    lv_item\mask = #LVIF_STATE
    lv_item\iItem = TargetItem
    lv_item\state = #LVIS_DROPHILITED
    lv_item\stateMask = #LVIS_DROPHILITED    
    SendMessage_(GadgetID(#ExplorerList), #LVM_SETITEM, 0, @lv_item)  
    
    ; redraw the window
    RedrawWindow_(GadgetID(#ExplorerList), 0, 0, #RDW_UPDATENOW)      
    
    
  ; it is again the same for ExplorerTree, only with slightly different
  ; structures and messages:
  ElseIf TargetID = GetParent_(GadgetID(#ExplorerTree))
    TargetGadget = #ExplorerTree
    
    tv_hittestinfo.TV_HITTESTINFO
    tv_hittestinfo\pt\x = MouseX - GadgetX(#ExplorerTree)
    tv_hittestinfo\pt\y = MouseY - GadgetY(#ExplorerTree)    
    
    TargetItem = SendMessage_(GadgetID(#ExplorerTree), #TVM_HITTEST, 0, @tv_hittestinfo) 
    If TargetItem = 0
      TargetItem = -1
    EndIf
    
    tv_item.TV_ITEM
    tv_item\mask = #TVIF_STATE
    tv_item\hItem = TargetItem
    tv_item\state = #TVIS_DROPHILITED
    tv_item\stateMask = #TVIS_DROPHILITED    
    SendMessage_(GadgetID(#ExplorerTree), #TVM_SETITEM, 0, @tv_item)  
    
    RedrawWindow_(GadgetID(#ExplorerTree), 0, 0, #RDW_UPDATENOW)    
  
  ; if the mouse is not over any gadget, indicate that through the -1     
  Else
    TargetGadget = -1
    TargetItem = -1
  
  EndIf

EndProcedure


; This procedure unhilights the last hilighted gadgets, so a new one can
; be hilighted.

Procedure UnHilightTarget()

  If TargetGadget = #ExplorerList
           
    lv_item.LV_ITEM
    lv_item\mask = #LVIF_STATE
    lv_item\iItem = TargetItem
    lv_item\state = 0
    lv_item\stateMask = #LVIS_DROPHILITED
         
    SendMessage_(GadgetID(#ExplorerList), #LVM_SETITEM, 0, @lv_item)
    RedrawWindow_(GadgetID(#ExplorerList), 0, 0, #RDW_UPDATENOW)  
  
  ElseIf TargetGadget = #ExplorerTree

    tv_item.TV_ITEM
    tv_item\mask = #TVIF_STATE
    tv_item\hItem = TargetItem
    tv_item\state = 0
    tv_item\stateMask = #TVIS_DROPHILITED
    
    SendMessage_(GadgetID(#ExplorerTree), #TVM_SETITEM, 0, @tv_item)  
    RedrawWindow_(GadgetID(#ExplorerTree), 0, 0, #RDW_UPDATENOW)    
  
  EndIf

EndProcedure



; Finally there is the main window callback. This is a normal
; PB windowcallback, as you can see by the #PB_ProcessPureBasicEvents way

Procedure Callback_MainWindow(Window, Message, wParam, lParam)
  result = #PB_ProcessPureBasicEvents
    
  ; the following messages are only processed when draging is in progress
  If IsDraging
  
    ; the mouse is moved, so the image needs to be moved, and maybe a new
    ; target hilighted
    If Message = #WM_MOUSEMOVE
      
      ; this moves the drag image. Screen coordinates are needed here.
      ; for this we need screen coordinates
      GetCursorPos_(@MousePos.POINT)
      ImageList_DragMove_(MousePos\x, MousePos\y)
      
      ; now hide the image for the hilightning of the target
      ImageList_DragShowNolock_(#FALSE)         
      
      ; unhilight any previously hilighted target, and hilight the new one
      UnHilightTarget()
      HilightTarget(WindowMouseX(), WindowMouseY())
      
      ; show the drag image again.
      ImageList_DragShowNolock_(#TRUE)
    
    
    ; mouse button up, so the d&d is finished.
    ElseIf Message = #WM_LBUTTONUP
      
      ; release the mouse message capturing
      ReleaseCapture_()
      
      ; end the d&d operation
      ImageList_EndDrag_()
      
      ; free the image list
      ImageList_Destroy_(DragImageList)      
      
      ; show the cursor again
      ShowCursor_(#TRUE)
      
      ; indicate, that d&d is no longer in progress
      IsDraging = #FALSE
      
      ; unhilight the target
      UnHilightTarget()
      
      ; That's it, the drag&drop is finished. Now we can work with what we know.
      ; The last call of the HilightTarget() procedure has allready set the
      ; TargetGadget and TargetItem values, so no more such checks are needed.
      
      ; I have inserted a message requester here to show the available information,
      ; and also to explain how to get it.
      
      Message$ = "Drag & Drop complete" + #CRLF$ + #CRLF$
      
      ; for d&d to start, there must be a valid source gadget and item, so there
      ; is no need to check for -1 here.
      
      If SourceGadget = #ExplorerList
        Message$ + "Source: ExplorerListGadget" + #CRLF$
        
        ; for ExplorerListGadget, to get the full item path, both these functions are needed.
        Message$ + "Item: " + GetGadgetText(#ExplorerList) + GetGadgetItemText(#ExplorerList, SourceItem, 0) + #CRLF$
        
        If GetGadgetItemState(#ExplorerList, SourceItem) & #PB_Explorer_Directory
          Message$ + "Type: Directory" + #CRLF$
        Else
          Message$ + "Type: File" + #CRLF$          
        EndIf        
      
      ElseIf SourceGadget = #ExplorerTree
        ; Here is a little issue. As said before, a treeview works with handles, also
        ; the ExplorerTreeGadget doesn't have 'individual' items like the list one, it
        ; only has one current one. Also, the d&d operation removes this current
        ; selection from the gadget.
        
        ; so what we do to get this info is to manually select the Source and the
        ; target item with SendMessage and it's handle, then get the information
        ; with GetGadgetText/State, and deselect the item again later:      
        SendMessage_(GadgetID(#ExplorerTree), #TVM_SELECTITEM, #TVGN_CARET, SourceItem)
        
        ; get the info:
        Message$ + "Source: ExplorerTreeGadget" + #CRLF$
        Message$ + "Item: " + GetGadgetText(#ExplorerTree) + #CRLF$
        If GetGadgetState(#ExplorerList) & #PB_Explorer_Directory
          Message$ + "Type: Directory" + #CRLF$
        Else
          Message$ + "Type: File" + #CRLF$          
        EndIf        
        
        ; delselect the item again:
        SendMessage_(GadgetID(#ExplorerTree), #TVM_SELECTITEM, #TVGN_CARET, 0)
              
      EndIf
            
      Message$ + #CRLF$
      
      If TargetGadget = -1
        Message$ + "Target: Item dropped not in the Explorer Gadgets." + #CRLF$
      
      ElseIf TargetGadget = #ExplorerList
        Message$ + "Source: ExplorerListGadget" + #CRLF$
        If TargetItem = -1
          Message$ + "Item: Item dropped in the empty Gadget area." + #CRLF$
        Else
          Message$ + "Item: " + GetGadgetText(#ExplorerList) + GetGadgetItemText(#ExplorerList, TargetItem, 0) + #CRLF$
          If GetGadgetItemState(#ExplorerList, TargetItem) & #PB_Explorer_Directory
            Message$ + "Type: Directory" + #CRLF$
          Else
            Message$ + "Type: File" + #CRLF$          
          EndIf                       
        EndIf
        
      ElseIf TargetGadget = #ExplorerTree
        Message$ + "Target: ExplorerTreeGadget" + #CRLF$
        If TargetItem = -1
          Message$ + "Item: Item dropped in the empty Gadget area." + #CRLF$
        Else                 
          ; same here: first select the target, get the info and unselect it.
          
          SendMessage_(GadgetID(#ExplorerTree), #TVM_SELECTITEM, #TVGN_CARET, TargetItem)
          Message$ + "Item: " + GetGadgetText(#ExplorerTree) + #CRLF$
          If GetGadgetState(#ExplorerList) & #PB_Explorer_Directory
            Message$ + "Type: Directory" + #CRLF$
          Else
            Message$ + "Type: File" + #CRLF$          
          EndIf  
          SendMessage_(GadgetID(#ExplorerTree), #TVM_SELECTITEM, #TVGN_CARET, 0)                                         
        EndIf
         
      EndIf
      
      ; That's it, now you have all the info you need. work with it ;)
      ; Let's just display our requester.
      
      ; One note though: If you do file operations in the displayed directorys,
      ; the ExplorerListGadget() will automatically refresh. However, the
      ; ExplorerTreeGadget() doesn't have that feature yet (PB v3.91).
      ; Just be aware of this.
      
      MessageRequester("Drag & Drop", Message$)
    
    EndIf  ; check Message
  
  EndIf  ; check IsDraging
  
  ProcedureReturn result  ; return result
EndProcedure



; That was all the hard part, now simply follows the GUI part:

If OpenWindow(#Window, 0, 0, 800, 600, #PB_Window_SystemMenu|#PB_Window_Screencentered, "Drag & Drop with ExplorerGadgets")
  If CreateGadgetList(WindowID())
    
    ExplorerTreeGadget(#ExplorerTree, 5, 5, 240, 590, "*.*", #PB_Explorer_AutoSort)
    ExplorerListGadget(#ExplorerList, 255, 5, 540, 590, "*.*", #PB_Explorer_AutoSort)
    
    ; these lines subclass the parent of the gadgets, and stores the old
    ; callback procedure address in our global variable.
    
    RealCallback_ExplorerList = SetWindowLong_(GetParent_(GadgetID(#ExplorerList)), #GWL_WNDPROC, @Callback_ExplorerList())
    RealCallback_ExplorerTree = SetWindowLong_(GetParent_(GadgetID(#ExplorerTree)), #GWL_WNDPROC, @Callback_ExplorerTree())
    
    ; for a treeview, drag&drop needs to be enabled first in order to work.
    ; this is very important.
    SetWindowLong_(GadgetID(#ExplorerTree), #GWL_STYLE, GetWindowLong_(GadgetID(#ExplorerTree), #GWL_STYLE) & ~#TVS_DISABLEDRAGDROP)
    
    ; finally set our main window callback:
    SetWindowCallback(@Callback_MainWindow())
    
    ; nothing to do in the event loop:
    Repeat
      Event = WaitWindowEvent()
    Until Event = #PB_EventCloseWindow
    
  EndIf
EndIf

End


; ------------------------------------------------------------------
;
; Phhhhew! That was lots of text again, and probably lots of
; typing errors again :)
; I hope it was easy to understand, and is helpfull to you.
; For more questions, mail me at 'freak@betriebsdirektor.de', or
; ask on the forums at 'http://forums.purebasic.com'
; For more of these tutorials, browse my site at 
; 'http://freak.purearea.net/'
;
; Thanks for reading..
;
; Timo
;
; ------------------------------------------------------------------