[COM] Help with the IDropSource and IDropTarget interfaces

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: [COM] Help with the IDropSource and IDropTarget interfaces

24 Aug 2015, 09:12

Oh, that is unfortunate! Just me's implementation is so close to being the perfect one. One can only hope someone, by sheer luck or by vast experience (really unfortunate that 'Sean' seems no longer active), manages to get the OLE data source modification needed for the source helper right.
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: [COM] Help with the IDropSource and IDropTarget interfaces

27 May 2016, 13:42

jballi wrote:...A few "real life" examples wouldn't hurt. Thanks for sharing.
Here is a "real life" example of Just Me`s D&D interface. Download the needed scripts from Github https://github.com/AHK-just-me/DoDragDr ... er/sources or copy from earlier posts in this thread and place:

DoDragDrop.ahk
IDataObject.ahk
IDropSource.ahk
IDropTarget.ahk
IEnumFORMATETC.ahk

in the same folder as this GUI script below. Set your folder you want to display in that script.

Copy the non-working IDragSourceHelper.ahk from earlier in thread and place it with the others.

Now D&D from the windows explorer to the AHK GUI, notice how source drag icon shows up when start dragging a file. If using Shift or Ctrl + Lefltclick drag we get to know if its a Copy or Move operation just below the source drag icon. If not holding down Shift or Ctrl while drag, when drag icon/cursor leaves the shell window then user gets to know if it is a move or copy D&D. That is what the IDragSourceHelper does for the shell. Dragging from the AHK GUI it differentiate between copy and move operations, but the drag symbol is hardly visible and no source icon. Getting the IDragSourceHelper to work when doing D&D operations from an AHK GUI would be amazing :D Could this be solved?

Regards
zcooler

Code: Select all

#NoEnv
#SingleInstance, force
SetBatchLines, -1 ; important for performance
SetWinDelay, -1   ; important for Dock()
; ==================================================================================================================================
; Media folders
RSFolder := "F:\TV\Seen Recordings" ; Set the folder path here...easily better test if the folder has sub-folders.
RSCaption := "DRAGnDROP Interface Test - Set some folder which this button should display!"
; WatchFolder settings
WatchFolders=%RSFolder%* ; *=recurse subfolders
; ==================================================================================================================================
; Global variables for Dock() replacement
Global Gui1X, Gui1Y, Gui2X, Gui2Y, HGUI1, HGUI2
Global Gui2Visible := False
; Global variables for the TreeView
Global Logging := False    ; set to True to enable logging within the ReportFunction
Global TVHWND              ; handle of the TreeView control, filled later
Global TVFolder            ; name of the current root folder, filled later
Global TVRootID            ; ID of the current root node, filled later
Global TVDragID := 0       ; ID of the currently dragged item +++++
Global TVDropID := 0       ; ID of the item on which a drop ocurred +++++
Global TVLastID            ; ID of the last visible item +++++
Global TVClientW           ; the width of the TreeView control's client area, calculated later
Global TVClientH           ; the height of the TreeView control's client area, calculated later
Global TVDeltaW            ; delta to add to the width of the TreeView control, calculated later
Global TVDeltaH            ; delta to add to the height of the TreeView control, calculated later
Global TVItemHeight        ; height of all tree view items, calculated later
Global TVMaxHeight         ; maximum height of the TreeView control, calculated later
Global TVMaxRows := 20     ; maximum rows shown in the TreeView control
Global TVMinWidth := 300   ; minimum width of the TreeView control - not implemented yet
Global TVMaxWidth := 600   ; maximum width of the TreeView control - not implemented yet
;========================================================================================
; GUI 1
Gui, Gui1:New, +HwndHGUI1 +LabelGui1
Gui, Margin, 10, 10
Gui, Add, Button, ym gGetFiles vRS hwndHRSBTN, %RSCaption%     ;+++ added hwnd option
IDT_RS := IDropTarget_Create(HRSBTN, "_BTN") ;+++ Create a drop target for the buttons
Gui, Show, x400 y35, Some GUI

; ==================================================================================================================================
; GUI 2
Gui, Gui2:New, +HwndHGUI2 +OwnerGui1 -Caption +0x02000000 ; WS_CLIPCHILDREN = 0x02000000 is needed in this case ;
Gui, Margin, 0, 0
Gui, Color, 000000
Gui, Font, s9 w800, Ebrima ; Segoe UI Symbol Arial Tahoma
; Create the TreeView
Gui, Add, TreeView, r%TVMaxRows% w650 hwndTVHWND +0x0800 +0x200 Backgroundf2f2f2 -HScroll AltSubmit gTVLabel vTVVar -E0x0200 +E0x020000
; Calculate the deltas between the TreeView's non-client and client area
GuiControlGet, TV, Pos, TVVar
TVMaxHeight := TVH
TVDeltaH := Mod(TVMaxHeight, TVMaxRows)
TVDeltaW := TVDeltaH
; Set Vista+ theme and styles
TV_SetExplorerTheme(TVHWND, "R")
TV_DoubleBuffer(TVHWND)

ImageListID := IL_Create(12)
Loop 12 {
   IL_Add(ImageListID, "shell32.dll", A_Index) 
}
TV_SetImageList(ImageListID)
; Add a faked StatusBar
Gui, Add, Text, xp y+0 wp h20 +0x200 cWhite hwndTVHSB vSBVar +E0x020000
IDT_TV := IDropTarget_Create(TVHWND, "_TV") ;+++ Create a drop target for the TreeView
; ==================================================================================================================================
; Message handlers and monitoring
; Watched folders
Loop, Parse, WatchFolders,|
{
   SubTree := (SubStr(A_LoopField, StrLen(A_LoopField)) = "*")
   WatchFolder(RTrim(A_LoopField, "*"), "ReportFunction", SubTree, Watch := 19)
}
; Dock message handler
OnMessage(0x0003, "Dock") ; WM_MOVE = 0x0003
; Keyboard Nav handler
OnMessage(0x100, "OnKeyDown")
Return
; ======================================================================================================================
; Gui1 labels
; ======================================================================================================================
Gui1Close:
IDT_TV.RevokeDragDrop()
ExitApp
; ======================================================================================================================
; Gui2 labels
; ======================================================================================================================
GetFiles:
IsKEvent            := true
WinGetPos, Gui1X, Gui1Y, , , ahk_id %HGUI1%
GuiControlGet, HCTL, Hwnd, %A_GuiControl%
WinGetPos, CtlX, CtlY, CtlW, CtlH, ahk_id %HCTL%
Gui, Gui2:Default
Gui, Hide
Gui2Visible := False
GuiControl, -g, TVVar
TV_Delete()
TVFolder := %A_GuiControl%Folder
ItemCount := AddFoldersAndFilesToTree(TVFolder) ; Populate treeview call
TVRootID := TV_GetNext()
TV_Modify(TVRootID, "Select VisFirst Expand")
TVW := TVH := 0
GuiControlGet, TV, Pos, TVVar
GuiControl, Move, SBVar, % "y" . (TVY + TVH) . " w" . TVW
GuiControl, , SBVar, % "   " . ItemCount - 1 . " objects."
X := CtlX
Y := CtlY + CtlH
Gui, Show, x%X% y%Y% AutoSize
WinGetPos, Gui2X, Gui2Y, , , ahk_id %HGUI2%
GuiControl, +gTVLabel, TVVar
Gui2Visible := True
Return
; ----------------------------------------------------------------------------------------------------------------------
;+++ The GuiDropFiles label for Gui2 has to be removed. All code has to be moved to the IDropTargetOnDrop user function.
; ======================================================================================================================
; Common subroutines to Gui2
; ======================================================================================================================
TVLabel:
Critical ; try to forgo the use of Critical if possible
If (A_GuiEvent = "+") || (A_GuiEvent = "-") {
   GuiControl, -Redraw, TVVar
   GuiControlGet, TV, Pos, TVVar
   Gui, Show, AutoSize
   GuiControl, +Redraw, TVVar
}
Else If (A_GuiEvent = "Normal") && (A_EventInfo = TV_HitTest(TVHWND)) { ; in this case the item will be selected definitely
  ItemID := A_EventInfo
  Gosub, TVLabelSelect
}
Else If (A_GuiEvent == "D") { ;+++ Only left dragging is supported, changed
   TVDragID := A_EventInfo
   GoSub, TVDragDrop
}
Else
   IsKEvent := A_GuiEvent == "K" ? True : False
Return
;------------------------------------------------------------------------------
TVLabelSelect:
   TV_GetText(item, ItemID)
   FileName := item
   ItemID2 := ItemID
   while ParentID := TV_GetParent(ItemID2)
   {
      TV_GetText(item, ParentID)
      FileName := item "\" FileName
      ItemID2 := ParentID
   }
   if (IsMovie := InStr(FileExist(FileName), "D") ? false : true) 
	  RunWait, Open %FileName%
   else {
      GuiControl, -g, TVVar
      TV_Modify(ItemID, TV_Get(ItemID, "Expand") ? "-Expand" : "Expand")
      GuiControl, -Redraw, TVVar
      GuiControlGet, TV, Pos, TVVar
      Gui, Show, AutoSize
      GuiControl, +Redraw, TVVar
      GuiControl, +gTVLabel, TVVar
   }
return
;------------------------------------------------------------------------------ 
TVDragDrop:
   TVDragPath := TV_GetPath(TVDragID)
   TVDropID := 0
   TVLastID := TV_GetLastVisible(TVHWND)
   TV_GetClientSize(TVHWND, TVClientW, TVClientH)
   TVItemHeight := TV_GetItemHeight(TVHWND) ;;;;from old TVDragDrop label
   If (IsCritical := A_IsCritical)
      Critical, Off
   GuiControl, -g, TVVar       ;;;;from old TVDragDrop label
   DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0009, "Ptr", 0) ; TVM_SELECTITEM, TVGN_CARET (remove the selection)
   ;+++ TVDragDropRunning := True   v ; deprecated
   SavedClipboard := ClipBoardAll
   ClipboardSetFiles(TVDragPath, "")
   ;SB_SetText("   Dragging ...")
   DropEffect := DoDragDrop()
   ;SB_SetText("   DropEffect: " . {0: "NONE", 1: "COPY", 2: "MOVE"}[DropEffect])
   SetTimer, TVDragDropTimer, Off
   TVDragID := 0 ;+++
   TVDropID := 0 ;+++
   ;+++ Sleep, 10                     ; deprecated
   ;+++ If (DropEffect = 0)           ; deprecated
   ;+++    TVDragDropRunning := False ; deprecated
   ClipBoard := SavedClipboard
   DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0008, "Ptr", 0) ; TVM_SELECTITEM, TVIS_DROPHILITED
   GuiControl, +gTVLabel, TVVar  ;;;;from old TVDragDrop label
   If (IsCritical)
      Critical, %IsCritical%
Return
;------------------------------------------------------------------------------ 
TVDragDropTimer:
   MouseItemID := TV_HitTest(TVHWND, 0x0026)
   If (MouseItemID = 0) {
      TVDropID := 0
      DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0008, "Ptr", 0) ; TVM_SELECTITEM, TVIS_DROPHILITED
   }
   Else If (MouseItemID <> TVDropID) {
      TVDropID := MouseItemID
      HoverTickCount := ScrollTickCount := A_TickCount
      DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0008, "Ptr", TVDropID) ; TVM_SELECTITEM, TVIS_DROPHILITED
   }
   Else {
      If ((A_TickCount - HoverTickCount) >= 1000) {
         VarSetCapacity(TVIX, 40 + (A_PtrSize * 5), 0)
         NumPut(0x0048, TVIX, 0, "UInt") ; TVIF_CHILDREN | TVIF_STATE
         NumPut(TVDropID, TVIX, A_PtrSize, "UPtr")
         If DllCall("SendMessage", "Ptr", TVHWND, "UInt", A_IsUnicode ? 0x113E : 0x110C, "Ptr", 0, "Ptr", &TVIX, "Int")
         && (NumGet(TVIX, 20 + (A_PtrSize * 3), "Int") = 1) && !(NumGet(TVIX, A_PtrSize * 2, "Uint") & 0x0020) {
            DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x1102, "Ptr", 0x0002, "Ptr", TVDropID, "Int") ; TVM_EXPAND
            HoverTickCount := A_TickCount
            If (TVDropID = TVLastID)
               TVLastID := TV_GetLastVisible(TVHWND)
         }
      }
      If ((A_TickCount - ScrollTickCount) >= 100) { ; 100 ms seems to be sufficient here for scrolling
         If (TVDropID = TV_GetFirstVisible(TVHWND)) && (TVDropID <> TVRootID) {
            DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0008, "Ptr", 0) ; TVM_SELECTITEM, TVIS_DROPHILITED
            DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x0115, "Ptr", 0, "Ptr", 0) ; WM_VSCROLL
            TVDropID := 0
            HoverTickCount := A_TickCount
         }
         Else {
            TV_GetItemRect(TVHWND, TVDropID, X, Y, W, H)
            If (TVDropID = TVLastID) {
               If ((Y + H) > TVClientH)
                  DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x0115, "Ptr", 1, "Ptr", 0) ; WM_VSCROLL
            }
            Else If ((Y + H + TVItemHeight) > TVClientH) {
               DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0008, "Ptr", 0) ; TVM_SELECTITEM, TVIS_DROPHILITED
               DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x0115, "Ptr", 1, "Ptr", 0) ; WM_VSCROLL
               TVDropID := 0
               HoverTickCount := A_TickCount
            }
         }
         ScrollTickCount := A_TickCount
      }
   }
Return
;----------------------------------------------------------------------------------------------------------
;-FUNCTIONS------------------------------------------------------------------------------------------------
;----------------------------------------------------------------------------------------------------------
;----------------------------------------------------------------------------------------------------------
AddFoldersAndFilesToTree(Folder, ParentID := 0, First := 0) {
   ;msgbox % First
   SplitPath, Folder, FolderName
   If (ParentID = 0)
      ID := TV_Add(Folder, ParentID, "Icon7 Expand")     ; ID gets a value
   Else If (First = 0)
  	  ID := TV_Add(FolderName, ParentID, "Icon7")        ; ID gets a value
   Else If (First = 1)
      ID := TV_Add(FolderName, ParentID, "First Icon7")  ; ID gets a value
   ; -------------------------------------------------------------------------------------------------------------------------------
      FolderList =
      Loop, %Folder%\*.*, 2
         FolderList .= (A_Index = 1 ? "" : "`n") A_LoopFileTimeModified "|" A_LoopFileName "|" A_LoopFileLongPath
      ; Sort folder options If recurse
      If (Settings.SortRev = "1")                     ; Sort folders reverse enabled
         Sort, FolderList, F ReverseDirection D`n,    ; Reverses the list so that it contains 4,3,2,1
      If (Settings.SortTimeRev = "1")                 ; Sort folders timereverse enabled
         Sort, FolderList, F SortFunc                 ; Sort folders TimeModified, newest on top to oldest in bottom
      Loop, Parse, FolderList, `n
         AddFoldersAndFilesToTree(StrSplit(A_LoopField, "|").3, ID)
   ; -------------------------------------------------------------------------------------------------------------------------------
	FileList := ""
	Loop, % Folder . "\*.*", 0
         FileList .= (FileList <> "" ? "`n" : "") . A_LoopFileTimeCreated . "|" . A_LoopFileName . "|" . A_LoopFileTimeModified
   ; -------------------------------------------------------------------------------------------------------------------------------
    Sort, FileList, F ReverseDirection D`n,   ; Reverses the list so that it contains 4,3,2,1
   ; -------------------------------------------------------------------------------------------------------------------------------
   Loop, Parse, FileList, `n
   {
      Split := StrSplit(A_LoopField, "|") ;+++ to avoid repeated calls of StrSplit()
      If (Split.1 = Split.3) ; recording isn't finished yet
	     TV_Add(Split.2, ID, "Icon4")
	  Else                 ; recording is finished
	     TV_Add(Split.2, ID, "Icon2")
   }
   Return TV_GetCount()
}
; ==================================================================================================================================
; Auxiliary functions ==============================================================================================================
; ==================================================================================================================================
SortFunc(_line1, _line2)
{
    line1 := StrSplit(_line1, "|")
    line2 := StrSplit(_line2, "|")
 
    if (line1[1] < line2[1])        ; sort in descending order
        ;msgbox % line1[1] " " line2[1] "`ndescending"
		return 1
 
    if (line1[1] = line2[1])
	   ;msgbox % line1[1] "`nascending"
        if (line1[2] > line2[2])    ; sort in ascending order
            return 1
 
    return -1
}
ReverseDirection(a1, a2, offset)
{
    return offset  ; Offset is positive if a2 came after a1 in the original list; negative otherwise.
}
;--------------------------------------------------------------------------------
; Docking Guis
Dock(W, L, M, H) { ; <<<<< added Dock replacement
   If (H = HGUI1)  && Gui2Visible {
      WinGetPos, X, Y, , , ahk_id %HGUI1%
      Gui2X += X - Gui1X
      Gui2Y += Y - Gui1Y
      Gui1X := X
      Gui1Y := Y
      WinMove, ahk_id %HGUI2%, , %Gui2X%, %Gui2Y%
   }
}
; ==================================================================================================================================
; Explorer function for Drag&Drop and Pasting. Enables the explorer paste context menu option.
; ==================================================================================================================================
ClipboardSetFiles(FilesToSet, DropEffect := "Copy") {
   Static TCS := A_IsUnicode ? 2 : 1 ; size of a TCHAR
   Static PreferredDropEffect := DllCall("RegisterClipboardFormat", "Str", "Preferred DropEffect")
   Static DropEffects := {1: 1, 2: 2, Copy: 1, Move: 2}
   ; -------------------------------------------------------------------------------------------------------------------
   ; Count files and total string length
   TotalLength := 0
   FileArray := []
   Loop, Parse, FilesToSet, `n, `r
   {
      If (Length := StrLen(A_LoopField))
         FileArray.Push({Path: A_LoopField, Len: Length + 1})
      TotalLength += Length
   }
   FileCount := FileArray.Length()
   If !(FileCount && TotalLength)
      Return
   ; -------------------------------------------------------------------------------------------------------------------
   ; Add files to the clipboard
   If DllCall("OpenClipboard", "Ptr", A_ScriptHwnd) && DllCall("EmptyClipboard") {
      ; HDROP format ---------------------------------------------------------------------------------------------------
      ; 0x42 = GMEM_MOVEABLE (0x02) | GMEM_ZEROINIT (0x40)
      hPath := DllCall("GlobalAlloc", "UInt", 0x42, "UInt", 20 + (TotalLength + FileCount + 1) * TCS, "UPtr")
      pPath := DllCall("GlobalLock", "Ptr" , hPath)
      Offset := 20
      NumPut(Offset, pPath + 0, "UInt")         ; DROPFILES.pFiles = offset of file list
      NumPut(!!A_IsUnicode, pPath + 16, "UInt") ; DROPFILES.fWide = 0 --> ANSI, fWide = 1 --> Unicode
      For Each, File In FileArray
         Offset += StrPut(File.Path, pPath + Offset, File.Len) * TCS
      DllCall("GlobalUnlock", "Ptr", hPath)
      DllCall("SetClipboardData","UInt", 0x0F, "UPtr", hPath) ; 0x0F = CF_HDROP
      ; Preferred DropEffect format ------------------------------------------------------------------------------------
      If (DropEffect := DropEffects[DropEffect]) {
         ; Write Preferred DropEffect structure to clipboard to switch between copy/cut operations
         ; 0x42 = GMEM_MOVEABLE (0x02) | GMEM_ZEROINIT (0x40)
         hMem := DllCall("GlobalAlloc", "UInt", 0x42, "UInt", 4, "UPtr")
         pMem := DllCall("GlobalLock", "Ptr", hMem)
         NumPut(DropEffect, pMem + 0, "UChar")
         DllCall("GlobalUnlock", "Ptr", hMem)
         DllCall("SetClipboardData", "UInt", PreferredDropEffect, "Ptr", hMem)
      }
      DllCall("CloseClipboard")
   }
   Return
}
;--------------------------------------------------------------------------------------
; Clipboard function. Retrieves if files in clipboard comes from an explorer cut or copy operation.
; explorer copy = 5, explorer cut = 2
ClipboardGetDropEffect() {
   Static PreferredDropEffect := DllCall("RegisterClipboardFormat", "Str" , "Preferred DropEffect")
   DropEffect := 0
   If DllCall("IsClipboardFormatAvailable", "UInt", PreferredDropEffect) {
      If DllCall("OpenClipboard", "Ptr", 0) {
         hDropEffect := DllCall("GetClipboardData", "UInt", PreferredDropEffect, "UPtr")
         pDropEffect := DllCall("GlobalLock", "Ptr", hDropEffect, "UPtr")
         DropEffect := NumGet(pDropEffect + 0, 0, "UChar")
         DllCall("GlobalUnlock", "Ptr", hDropEffect)
         DllCall("CloseClipboard")
      }
   }
   Return DropEffect
}
;------------reverse logging function------------------------------------------------------------------------------
AddToBOF(FileName, String) { ; AddToBeginOfFile
   If (File := FileOpen(FileName, "rw", "UTF-8-RAW")) {
      Content := File.Read()
      File.Pos := 0
      File.Write(String . Content)
      File.Close()
      Return True
   }
   Return False
}
; ==================================================================================================================================
; Function:       Notifies about changes within folders.
;                 This is a rewrite of HotKeyIt's WatchDirectory() released at
;                    http://www.autohotkey.com/board/topic/60125-ahk-lv2-watchdirectory-report-directory-changes/
; Tested with:    AHK 1.1.23.01 (A32/U32/U64)
; Tested on:      Win 10 Pro x64
; Usage:          WatchFolder(Folder, UserFunc[, SubTree := False[, Watch := 3]])
; Parameters:
;     Folder      -  The full qualified path of the folder to be watched.
;                    Pass the string "**PAUSE" and set UserFunc to either True or False to pause respectively resume watching.
;                    Pass the string "**END" and an arbitrary value in UserFunc to completely stop watching anytime.
;                    If not, it will be done internally on exit.
;     UserFunc    -  The name of a user-defined function to call on changes. The function must accept at least two parameters:
;                    1: The path of the affected folder. The final backslash is not included even if it is a drive's root
;                       directory (e.g. C:).
;                    2: An array of change notifications containing the following keys:
;                       Action:  One of the integer values specified as FILE_ACTION_... (see below).
;                                In case of renaming Action is set to FILE_ACTION_RENAMED (4).
;                       Name:    The full path of the changed file or folder.
;                       OldName: The previous path in case of renaming, otherwise not used.
;                       IsDir:   True if Name is a directory; otherwise False. In case of Action 2 (removed) IsDir is always False.
;                    Pass the string "**DEL" to remove the directory from the list of watched folders.
;     SubTree     -  Set to true if you want the whole subtree to be watched (i.e. the contents of all sub-folders).
;                    Default: False - sub-folders aren't watched.
;     Watch       -  The kind of changes to watch for. This can be one or any combination of the FILE_NOTIFY_CHANGES_...
;                    values specified below.
;                    Default: 0x03 - FILE_NOTIFY_CHANGE_FILE_NAME + FILE_NOTIFY_CHANGE_DIR_NAME
; Return values:
;     Returns True on success; otherwise False.
; Change history:
;     1.0.01.00/2016-03-14/just me        -  bug-fix for multiple folders
;     1.0.00.00/2015-06-21/just me        -  initial release
; License:
;     The Unlicense -> http://unlicense.org/
; Remarks:
;     Due to the limits of the API function WaitForMultipleObjects() you cannot watch more than MAXIMUM_WAIT_OBJECTS (64)
;     folders simultaneously.
; MSDN:
;     ReadDirectoryChangesW          msdn.microsoft.com/en-us/library/aa365465(v=vs.85).aspx
;     FILE_NOTIFY_CHANGE_FILE_NAME   = 1   (0x00000001) : Notify about renaming, creating, or deleting a file.
;     FILE_NOTIFY_CHANGE_DIR_NAME    = 2   (0x00000002) : Notify about creating or deleting a directory.
;     FILE_NOTIFY_CHANGE_ATTRIBUTES  = 4   (0x00000004) : Notify about attribute changes.
;     FILE_NOTIFY_CHANGE_SIZE        = 8   (0x00000008) : Notify about any file-size change.
;     FILE_NOTIFY_CHANGE_LAST_WRITE  = 16  (0x00000010) : Notify about any change to the last write-time of files.
;     FILE_NOTIFY_CHANGE_LAST_ACCESS = 32  (0x00000020) : Notify about any change to the last access time of files.
;     FILE_NOTIFY_CHANGE_CREATION    = 64  (0x00000040) : Notify about any change to the creation time of files.
;     FILE_NOTIFY_CHANGE_SECURITY    = 256 (0x00000100) : Notify about any security-descriptor change.
;     FILE_NOTIFY_INFORMATION        msdn.microsoft.com/en-us/library/aa364391(v=vs.85).aspx
;     FILE_ACTION_ADDED              = 1   (0x00000001) : The file was added to the directory.
;     FILE_ACTION_REMOVED            = 2   (0x00000002) : The file was removed from the directory.
;     FILE_ACTION_MODIFIED           = 3   (0x00000003) : The file was modified.
;     FILE_ACTION_RENAMED            = 4   (0x00000004) : The file was renamed (not defined by Microsoft).
;     FILE_ACTION_RENAMED_OLD_NAME   = 4   (0x00000004) : The file was renamed and this is the old name.
;     FILE_ACTION_RENAMED_NEW_NAME   = 5   (0x00000005) : The file was renamed and this is the new name.
;     GetOverlappedResult            msdn.microsoft.com/en-us/library/ms683209(v=vs.85).aspx
;     CreateFile                     msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx
;     FILE_FLAG_BACKUP_SEMANTICS     = 0x02000000
;     FILE_FLAG_OVERLAPPED           = 0x40000000
; ==================================================================================================================================
WatchFolder(Folder, UserFunc, SubTree := False, Watch := 0x03) {
   Static DummyObject := {Base: {__Delete: Func("WatchFolder").Bind("**END", "")}}
   Static TimerID := "**" . A_TickCount
   Static TimerFunc := Func("WatchFolder").Bind(TimerID, "")
   Static MAXIMUM_WAIT_OBJECTS := 64
   Static MAX_DIR_PATH := 260 - 12 + 1
   Static SizeOfLongPath := MAX_DIR_PATH << !!A_IsUnicode
   Static SizeOfFNI := 0xFFFF ; size of the FILE_NOTIFY_INFORMATION structure buffer (64 KB)
   Static SizeOfOVL := 32     ; size of the OVERLAPPED structure (64-bit)
   Static WatchedFolders := {}
   Static EventArray := []
   Static HandleArray := []
   Static WaitObjects := 0
   Static BytesRead := 0
   Static Paused := False
   ; ===============================================================================================================================
   If (Folder = "")
      Return False
   SetTimer, % TimerFunc, Off
   RebuildWaitObjects := False
   ; ===============================================================================================================================
   If (Folder = TimerID) { ; called by timer
      If (ObjCount := EventArray.Length()) && !Paused {
         ObjIndex := DllCall("WaitForMultipleObjects", "UInt", ObjCount, "Ptr", &WaitObjects, "Int", 0, "UInt", 0, "UInt")
         While (ObjIndex >= 0) && (ObjIndex < ObjCount) {
            FolderName := WatchedFolders[ObjIndex + 1]
            D := WatchedFolders[FolderName]
            If DllCall("GetOverlappedResult", "Ptr", D.Handle, "Ptr", D.OVLAddr, "UIntP", BytesRead, "Int", True) {
               Changes := []
               FNIAddr := D.FNIAddr
               FNIMax := FNIAddr + BytesRead
               OffSet := 0
               PrevIndex := 0
               PrevAction := 0
               PrevName := ""
               Loop {
                  FNIAddr += Offset
                  OffSet := NumGet(FNIAddr + 0, "UInt")
                  Action := NumGet(FNIAddr + 4, "UInt")
                  Length := NumGet(FNIAddr + 8, "UInt") // 2
                  Name   := FolderName . "\" . StrGet(FNIAddr + 12, Length, "UTF-16")
                  IsDir  := InStr(FileExist(Name), "D") ? 1 : 0
                  If (Name = PrevName) {
                     If (Action = PrevAction)
                        Continue
                     If (Action = 1) && (PrevAction = 2) {
                        PrevAction := Action
                        Changes.RemoveAt(PrevIndex--)
                        Continue
                     }
                  }
                  If (Action = 4)
                     PrevIndex := Changes.Push({Action: Action, OldName: Name, IsDir: 0})
                  Else If (Action = 5) && (PrevAction = 4) {
                     Changes[PrevIndex, "Name"] := Name
                     Changes[PrevIndex, "IsDir"] := IsDir
                  }
                  Else
                     PrevIndex := Changes.Push({Action: Action, Name: Name, IsDir: IsDir})
                  PrevAction := Action
                  PrevName := Name
               } Until (Offset = 0) || ((FNIAddr + Offset) > FNIMax)
               If (Changes.Length() > 0)
                  D.Func.Call(FolderName, Changes)
               DllCall("ResetEvent", "Ptr", EventArray[D.Index])
               DllCall("ReadDirectoryChangesW", "Ptr", D.Handle, "Ptr", D.FNIAddr, "UInt", SizeOfFNI, "Int", D.SubTree
                                              , "UInt", D.Watch, "UInt", 0, "Ptr", D.OVLAddr, "Ptr", 0)
            }
            ObjIndex := DllCall("WaitForMultipleObjects", "UInt", ObjCount, "Ptr", &WaitObjects, "Int", 0, "UInt", 0, "UInt")
            Sleep, 0
         }
      }
   }
   ; ===============================================================================================================================
   Else If (Folder = "**PAUSE") { ; called to pause/resume watching
      Paused := !!UserFunc
      RebuildObjects := Paused
   }
   ; ===============================================================================================================================
   Else If (Folder = "**END") { ; called to stop watching
      For D In WatchedFolders
         If D Is Not Integer
            DllCall("CloseHandle", "Ptr", D.Handle)
      For Each, Event In EventArray
         DllCall("CloseHandle", "Ptr", Event)
      WatchedFolders := {}
      EventArray := []
      Paused := False
      Return True
   }
   ; ===============================================================================================================================
   Else { ; called to add, update, or remove folders
      Folder := RTrim(Folder, "\")
      VarSetCapacity(LongPath, SizeOfLongPath, 0)
      If !DllCall("GetLongPathName", "Str", Folder, "Ptr", &LongPath, "UInt", SizeOfLongPath)
         Return False
      VarSetCapacity(LongPath, -1)
      Folder := LongPath
      If (WatchedFolders[Folder]) { ; update or remove
         Handle := WatchedFolders[Folder, "Handle"]
         Index  := WatchedFolders[Folder, "Index"]
         DllCall("CloseHandle", "Ptr", Handle)
         DllCall("CloseHandle", "Ptr", EventArray[Index])
         EventArray.RemoveAt(Index)
         WatchedFolders.RemoveAt(Index)
         WatchedFolders.Delete(Folder)
         RebuildWaitObjects := True
      }
      If InStr(FileExist(Folder), "D") && (UserFunc <> "**DEL") && (EventArray.Length() < MAXIMUM_WAIT_OBJECTS) {
         If (IsFunc(UserFunc) && (UserFunc := Func(UserFunc)) && (UserFunc.MinParams >= 2)) && (Watch &= 0x017F) {
            Handle := DllCall("CreateFile", "Str", Folder . "\", "UInt", 0x01, "UInt", 0x07, "Ptr",0, "UInt", 0x03
                                          , "UInt", 0x42000000, "Ptr", 0, "UPtr")
            If (Handle > 0) {
               Event := DllCall("CreateEvent", "Ptr", 0, "Int", 1, "Int", 0, "Ptr", 0)
               Index := EventArray.Push(Event)
               WatchedFolders[Index] := Folder
               WatchedFolders[Folder] := {Func: UserFunc, Handle: Handle, Index: Index, SubTree: !!SubTree, Watch: Watch}
               WatchedFolders[Folder].SetCapacity("FNIBuff", SizeOfFNI)
               FNIAddr := WatchedFolders[Folder].GetAddress("FNIBuff")
               DllCall("RtlZeroMemory", "Ptr", FNIAddr, "Ptr", SizeOfFNI)
               WatchedFolders[Folder, "FNIAddr"] := FNIAddr
               WatchedFolders[Folder].SetCapacity("OVLBuff", SizeOfOVL)
               OVLAddr := WatchedFolders[Folder].GetAddress("OVLBuff")
               DllCall("RtlZeroMemory", "Ptr", OVLAddr, "Ptr", SizeOfOVL)
               NumPut(Event, OVLAddr + 8, A_PtrSize * 2, "Ptr")
               WatchedFolders[Folder, "OVLAddr"] := OVLAddr
               DllCall("ReadDirectoryChangesW", "Ptr", Handle, "Ptr", FNIAddr, "UInt", SizeOfFNI, "Int", SubTree
                                              , "UInt", Watch, "UInt", 0, "Ptr", OVLAddr, "Ptr", 0)
               RebuildWaitObjects := True
            }
         }
      }
      If (RebuildWaitObjects) {
         VarSetCapacity(WaitObjects, MAXIMUM_WAIT_OBJECTS * A_PtrSize, 0)
         OffSet := &WaitObjects
         For Index, Event In EventArray
            Offset := NumPut(Event, Offset + 0, 0, "Ptr")
      }
   }
   ; ===============================================================================================================================
   If (EventArray.Length() > 0)
      SetTimer, % TimerFunc, -100
   Return (RebuildWaitObjects) ; returns True on success, otherwise False
}
; ==================================================================================================================================
; WatchFolder - Reportfunctions
ReportFunction(Directory, Changes) {
   Global Dummy_DB_BackupEntry, TooltipInfoUpdater, SRFolder, tooltip_infofiles, IsMovie, Logging, RecycleBin_C, RecycleBin_D, RecycleBin_E, RecycleBin_F, RecycleBin_G
   Static LogFile := ""
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; Logging
   If (LogFile = "")
      LogFile := TooltipInfoUpdater . "\wd_changes.log"
   If (Logging)
      FormatTime, Prefix, , yyyy-MM-dd HH:mm:ss
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; Check if the current TreeView might be affected.
   TVChanged := False
   If (Gui2Visible) {
      Gui, Gui2:Default
      Gui, TreeView, TVVar
      GuiControl, -g, TVVar
      GuiControl, -Redraw, TVVar
      ; ----------------------------------------------------------------------------------------------------------------------------
      ; Loop through the changes.
      For Each, Change In Changes {
         Action := Change.Action
         Name := Change.Name
         OldName := Change.Oldname
         IsDir := Change.Isdir
         If (Logging)
            AddToBOF(LogFile, Prefix . " ReportFunction: Action: " . Action . " - Name: " . Name . " - OldName: " . OldName
                            . " - IsDir: " . IsDir . "`r`n")
         ; -------------------------------------------------------------------------------------------------------------------------
         ; Process action 4 (renamed) at first, because Action might have to be changed and processed later on.
         If (Action = 4) {
            OldID := FindPath(OldName, OldFoundPath)
            If (OldName <> OldFoundPath) ; OldName was not found, so we change Action to 1 (added)
               Action := 1 ; will be processed later on
            Else { ; OldName has been found, so we have to check if the new name is valid
               SplitPath, Name, File, Dir, Ext
               If (IsDir) { ; the new name is valid, modify the tree
	              TVChanged := TV_Modify(OldID, , File)
                  If (Logging)
                     AddToBOF(LogFile, Prefix . " TreeView: Renamed " . OldName . " to " . Name . "`r`n")
               }
               Else { ; modifying is not possible, so we have to delete the entry
	              TVChanged := TV_Delete(OldID)
                  If (Logging)
                     AddToBOF(LogFile, Prefix . " TreeView: Removed " . Name . "`r`n")
               }
               Continue ; nothing else to do, next change
            }
         }
		 ; -------------------------------------------------------------------------------------------------------------------------
         ; Action 3 (modified)
         If (Action = 3) {
            ItemID := FindPath(Name, FoundPath)
            If (!IsDir && (Name = FoundPath)) { ; a file shown in the TreeView has been modified
               SplitPath, Name, , , Ext
               If Ext In ts,avi,mp4,mkv,wmv,mpg ; it's a movie file, so the icon might need to be updated
               {
	              TVChanged := TV_Modify(ItemID, "Icon2")
				  If (Logging)
                     AddToBOF(LogFile, Prefix . " TreeView: Modified " . Name . "`r`n")
               }
            }
            Continue ; nothing else to do, next change
         }
         ; -------------------------------------------------------------------------------------------------------------------------
         ; Action 2 (removed)
         If (Action = 2) {
			ItemID := FindPath(Name, FoundPath)
            If (Name = FoundPath) { ; Name was found and must be deleted
			   TVChanged := TV_Delete(ItemID)
               If (Logging)
                  AddToBOF(LogFile, Prefix . " TreeView: Removed " . Name . "`r`n")
            }
            Continue ; nothing else to do, next change
         }
         ; -------------------------------------------------------------------------------------------------------------------------
         ; Action 1 (added)
         If (Action = 1) {
            ItemID := FindPath(Name, FoundPath)
            If (!ItemID || (Name = FoundPath)) ; Name doesn't belong to the TreeView or exists already, nothing to do with it
               Continue
            SplitPath, Name, File, Dir, Ext
            If (IsDir) { ; it's a folder
			      TVChanged := AddFoldersAndFilesToTree(Name, ItemID, First := 1)
               If (Logging)
                  AddToBOF(LogFile, Prefix . " TreeView: Added " . Name . "`r`n")
			   }
            Else { ; it's a file
               If (FoundPath = Dir) {
                  If Ext In ts,avi,mp4,mkv,wmv,mpg
                  {
                     FileGetTime, Created, %Name%, C
                     FileGetTime, Modified, %Name%, M
                     If (Created = Modified)  ; File still in recording
	                    TVChanged := TV_Add(File, ItemID, "First Icon4")
					 Else                     ; Recording is finished
	                    TVChanged := TV_Add(File, ItemID, "First Icon2")
				  }
                  Else   ; If FileExt In txt,srt,sub,log
	                 TV_Changed := TV_Add(File, ItemID, "First Icon5")
                  If (Logging)
                     AddToBOF(LogFile, Prefix . " TreeView: Added " . Name . "`r`n")
               }
            }
         }
      }
      If (TVChanged) {
         GuiControlGet, TV, Pos, TVVar
         GuiControl, , SBVar, % "   " . TV_GetCount() . " objects."
         Gui, Show, AutoSize ;NoActivate
         GuiControl, +Redraw, TVVar
      }
      Else
         GuiControl, +Redraw, TVVar
      GuiControl, +gTVLabel, TVVar
   }
}
;-----------------------------------------------------------------------------------------------------------------------------------
; FindPath - Find (parts of) the path within the TreeView
FindPath(PathToFind, ByRef FoundPath) {
   FoundPath := ""
   If !(InStr(PathToFind, TVFolder) = 1) ; wrong parameters - PathToFind does not belong to the root folder
      Return 0
   FoundPath := TVFolder
   PathID := TVRootID
   Split := StrSplit(SubStr(PathToFind, StrLen(TVFolder) + 2), "\") ; split the part following the root folder
   ItemID := TV_GetChild(PathID)
   Loop, % Split.Length() {
      Name := Split[A_Index]
      While (ItemID) {
         TV_GetText(ItemText, ItemID)
         If (Name = ItemText) {
            PathID := ItemID
            FoundPath .= "\" . Name
            ItemID := TV_GetChild(ItemID)
            Break
         }
         ItemID := TV_GetNext(ItemID)
      }
   } Until (ItemID = 0)
   Return PathID
}
; ==================================================================================================================================
; Additional TV functions
; ==================================================================================================================================
TV_GetClientSize(HTV, ByRef W, ByRef H) { ; retrieves the client size of the TreeView
   W := H := 0
   , VarSetCapacity(RC, 16, 0)
   If DllCall("GetClientRect", "Ptr", HTV, "Ptr", &RC) {
      W := NumGet(RC, 8, "Int")
      , H := NumGet(RC, 12, "Int")
      Return True
   }
   Return False
}
TV_GetFirstVisible(HTV) { ; retrieves the first currently visible item of the TreeView
   Return DllCall("SendMessage", "Ptr", HTV, "UInt", 0x110A, "Ptr", 0x0005, "Ptr", 0, "UPtr") ; TVM_GETNEXTITEM, TVGN_FIRSTVISIBLE
}
TV_GetLastVisible(HTV) { ; retrieves the last visible item of the TreeView
   Return DllCall("SendMessage", "Ptr", HTV, "UInt", 0x110A, "Ptr", 0x000A, "Ptr", 0, "UPtr") ; TVM_GETNEXTITEM, TVGN_LASTVISIBLE
}
TV_GetPath(ItemID, Delimiter := "\") {
   TV_GetText(ItemPath, ItemID)
   While (ItemID := TV_GetParent(ItemID)) {
      TV_GetText(ItemText, ItemID)
      ItemPath := ItemText . Delimiter . ItemPath
   }
   Return ItemPath
}
TV_GetItemHeight(HTV) { ; retrieves the height af all TreeView items
   Return DllCall("SendMessage", "Ptr", HTV, "UInt", 0x111C, "Ptr", 0, "Ptr", 0) ; TVM_GETITEMHEIGHT
}
TV_GetItemRect(HTV, ItemID, ByRef X, ByRef Y, ByRef W, ByRef H, TextOnly := True) { ; retrieves the item's position and size
   X := 0, Y := 0, W := 0, H := 0
   , VarSetCapacity(RC, 16, 0)
   , NumPut(ItemID, RC, 0, "UPtr")
   If DllCall("SendMessage", "Ptr", HTV, "UInt", 0x1104, "Ptr", !!TextOnly, "Ptr", &RC) { ; TVM_GETITEMRECT
      X := NumGet(RC, 0, "Int")
      , Y := NumGet(RC, 4, "Int")
      , W := NumGet(RC, 8, "Int") - X
      , H := NumGet(RC, 12, "Int") - Y
      Return True
   }
   Return False
}
TV_HitTest(HTV, Test := 0x0006, X := "", Y := "") { ; TVM_HITTEST
   ; TVM_HITTEST = 0x1111 -> msdn.microsoft.com/en-us/library/bb773732(v=vs.85).aspx
   ; TVHT_NOWHERE            0x0001  - In the client area, but below the last item.
   ; TVHT_ONITEMICON         0x0002  - On the bitmap associated with an item.
   ; TVHT_ONITEMLABEL        0x0004  - On the label (string) associated with an item.
   ; TVHT_ONITEM             0x0006  - On the bitmap or label associated with an item (defined differently in CommCtrl.h).
   ; TVHT_ONITEMINDENT       0x0008  - In the indentation associated with an item.
   ; TVHT_ONITEMBUTTON       0x0010  - On the button associated with an item.
   ; TVHT_ONITEMRIGHT        0x0020  - In the area to the right of an item.
   ; TVHT_ONITEMSTATEICON    0x0040  - On the state icon for a tree-view item that is in a user-defined state.
   ; TVHT_ONITEMROW          0x007E  - On or right of an item (not defined in CommCtrl.h).
   ; TVHT_ABOVE              0x0100  - Above the client area.
   ; TVHT_BELOW              0x0200  - Below the client area.
   ; TVHT_TORIGHT            0x0400  - To the right of the client area.
   ; TVHT_TOLEFT             0x0800  - To the left of the client area.
   Test &= 0x0FFF
   VarSetCapacity(TVHTI, 8 + (A_PtrSize * 2), 0) ; TVHITTESTINFO
   If (X = "") || (Y = "") {
      DllCall("User32.dll\GetCursorPos", "Ptr", &TVHTI)
      , DllCall("User32.dll\ScreenToClient", "Ptr", HTV, "Ptr", &TVHTI)
   }
   Else {
      NumPut(X, TVHTI, 0, "Int")
      , NumPut(Y, TVHTI, 4, "Int")
   }
   If !(ItemID := DllCall("SendMessage", "Ptr", HTV, "UInt", 0x1111, "Ptr", 0, "Ptr", &TVHTI, "UPtr"))
      Return 0
   Return (NumGet(TVHTI, 8, "UInt") & Test ? ItemID : 0)
}
; Retrieves the rectangle of the item's text area in screen coordinates
TV_GetItemScreenRect(HTV, ItemID, Byref L := 0, ByRef T := 0, ByRef R := 0, ByRef B := 0) {
   ; TVM_GETITEMRECT = 0x1104 -> msdn.microsoft.com/en-us/library/bb773610(v=vs.85).aspx
   L := T := R := B := 0
   VarSetCapacity(RC, 16, 0)
   NumPut(ItemID, RC, 0, "Int")
   If DllCall("SendMessage", "Ptr", HTV, "UInt", 0x1104, "Ptr", True, "Ptr", &RC) { ; get the rectangle
      DllCall("MapWindowPoints", "Ptr", HTV, "Ptr", 0, "Ptr", &RC, "UInt", 2) ; convert client to screen coordinates
      L := NumGet(RC,  0, "Int")
      T := NumGet(RC,  4, "Int")
      R := NumGet(RC,  8, "Int")
      B := NumGet(RC, 12, "Int")
      Return True
   }
   Return False
}
TV_SetExplorerTheme(HTV, Option := "") {
   ; Options:
   ;     R  -  Remove the TVS_HASLINES style using lines to show the hierarchy of items.
   ;     F  -  Set the TVS_FULLROWSELECT style. The entire row of the selected item is highlighted, and clicking anywhere on an
   ;           item's row causes it to be selected. The TVS_HASLINES style will be removed, also.
   If (TV_OSVersion() > 5) {
      If (Option = "R") || (Option = "F") {
         Control, Style, -0x0002, , ahk_id %HTV%
         If (Option = "F")
            Control, Style, +0x1000, , ahk_id %HTV%
      }
      Return !DllCall("UxTheme.dll\SetWindowTheme", "Ptr", HTV, "WStr", "Explorer", "Ptr", 0)
   }
   Return False
}
TV_AutoHScroll(HTV, Set := True) {
   ; TVS_EX_AUTOHSCROLL = 0x0020
   Return TV_SetExStyle(HTV, 0x0020, Set ? 0x0020 : 0)
}
TV_DoubleBuffer(HTV, Set := True) {
   ; TVS_EX_DOUBLEBUFFER = 0x0004
   Return TV_SetExStyle(HTV, 0x0004, Set ? 0x0004 : 0)
}
TV_FadeInOutExpandos(HTV, Set := True) {
   ; TVS_EX_FADEINOUTEXPANDOS = 0x0040
   Return TV_SetExStyle(HTV, 0x0040, Set ? 0x0040 : 0)
}
TV_RichToolTips(HTV, Set := True) {
   ; TVS_EX_RICHTOOLTIP = 0x0010
   Return TV_SetExStyle(HTV, 0x0010, Set ? 0x0010 : 0)
}
TV_SetExStyle(HTV, StyleMask, Style) {
   ; TVM_SETEXTENDEDSTYLE = 0x0112
   If (TV_OSVersion() > 5) {
      ;SendMessage, 0x112C, %StyleMask%, %Style%, , ahk_id %HTV% ; Old code
	  Return DllCall("SendMessage", "Ptr", HTV, "UInt", 0x112C, "Ptr", StyleMask, "Ptr", Style) ; New DLL call
      Return (ErrorLevel = "FAIL" ?  0 : !ErrorLevel)
   }
   Return False
}
TV_OSVersion() {
   Static OSVersion := DllCall("GetVersion", "UChar")
   Return OSVersion
}
; ==================================================================================================================================
; Drag N Drop Functions
; ==================================================================================================================================
; IDropTarget user functions
; ==================================================================================================================================
; Gui2 tree view
; ----------------------------------------------------------------------------------------------------------------------------------
; ----------------------------------------------------------------------------------------------------------------------------------
IDropTargetOnDrop_TV(TargetObject, pDataObj, KeyState, X, Y, DropEffect) {
   Static FO := [2, 1]
   SetTimer, TVDragDropTimer, Off
   ; DropEffect := CheckDropEffect(KeyState, DropEffect)
   DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0008, "Ptr", 0) ; TVM_SELECTITEM, TVIS_DROPHILITED
   ; ToolTip, DropEffect = %DropEffect%
   ; Support drag & drop.
   Gui, Gui2:Default
   FolderPath := ""
   If (ItemID := TV_HitTest(TVHWND)) { ; dropped on an item
      FolderPath := TV_GetPath(ItemID)
      If !InStr(FileExist(FolderPath), "d") ; dropped onto a file, so we either have use the parent folder or ignore the event
         SplitPath, FolderPath, , FolderPath
   }
   Else
       FolderPath := TVFolder ; super-global TVFolder has been set to the root folder's name when the TreeView was created
   ; MsgBox, 0, %A_ThisFunc%, %FolderPath% ; for testing
   ; IDataObject_SHFileOperation will use CF_HDROP directly
   ; ; FO_MOVE = 1, FO_COPY = 2, so we have to swap the DropEffect (Copy = 1, Move = 2)
   IDataObject_SHFileOperation(pDataObj, FolderPath, DropEffect ^ 3, TargetObject.HWND)
   GuiControl, +gTVLabel, %TVHWND%
   Return DropEffect
}
; ----------------------------------------------------------------------------------------------------------------------------------
IDropTargetOnEnter_TV(TargetObject, pDataObj, KeyState, X, Y, DropEffect) {
   If (DropEffect) {
      If IDataObject_GetDroppedFiles(pDataObj, DroppedFiles) {
         DragFile := DroppedFiles[1]
         SplitPath, DragFile, , , , , DragDrive
         SplitPath, TVFolder, , , , , TVDrive
         If (DragDrive = TVDrive)
            DropEffect := 2
      }
      TVDropID := 0
      TVLastID := TV_GetLastVisible(TVHWND)
      TV_GetClientSize(TVHWND, TVClientW, TVClientH)
      TVItemHeight := TV_GetItemHeight(TVHWND) ;;;;from old TVDragDrop label
      GuiControl, -g, %TVHWND%
      SetTimer, TVDragDropTimer, 10
   }
   Return DropEffect
}
IDropTargetOnEnter_RB(TargetObject, pDataObj, KeyState, X, Y, DropEffect) {
   Return 2
}
; ----------------------------------------------------------------------------------------------------------------------------------
; IDropTargetOnOver_TV(TargetObject, KeyState, X, Y, DropEffect) {
;    Return DropEffect
; }
IDropTargetOnOver_RB(TargetObject, KeyState, X, Y, DropEffect) {
   Return 2
}
; ----------------------------------------------------------------------------------------------------------------------------------
IDropTargetOnLeave_TV(TargetObject) {
   SetTimer, TVDragDropTimer, Off
   DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0008, "Ptr", 0) ; TVM_SELECTITEM, TVIS_DROPHILITED
   GuiControl, +gTVLabel, %TVHWND%
   Return 0 ; S_OK
}
; ----------------------------------------------------------------------------------------------------------------------------------
; Gui1 buttons
; ----------------------------------------------------------------------------------------------------------------------------------
IDropTargetOnDrop_BTN(TargetObject, pDataObj, KeyState, X, Y, DropEffect) {
   Global ERFolder, NMFolder, RSFolder, SRFolder, BUFolder
   ; Support drag & drop.
   GuiControlGet, BtnName, Name, % TargetObject.HWND
   FolderPath := %BtnName%Folder
   ; MsgBox, 0, %A_ThisFunc%, %FolderPath% ; for testing
   ; IDataObject_SHFileOperation will use CF_HDROP directly
   ; ; FO_MOVE = 1, FO_COPY = 2, so we have to swap the DropEffect (Copy = 1, Move = 2)
   IDataObject_SHFileOperation(pDataObj, FolderPath, DropEffect ^ 3, TargetObject.HWND)
}
; ----------------------------------------------------------------------------------------------------------------------------------
IDropTargetOnEnter_BTN(TargetObject, pDataObj, KeyState, X, Y, DropEffect) {
   Global ERFolder, NMFolder, RSFolder, SRFolder, BUFolder
   If (DropEffect) {
      If IDataObject_GetDroppedFiles(pDataObj, DroppedFiles) {
         DragFile := DroppedFiles[1]
         GuiControlGet, BtnName, Name, % TargetObject.HWND
         BtnFolder := %BtnName%Folder
         SplitPath, DragFile, , , , , DragDrive
         SplitPath, BtnFolder, , , , , BtnDrive
         If (DragDrive = BtnDrive)
            DropEffect := 2
		 Else
		    DropEffect := 2
      }
   }
   Return DropEffect
}
; ----------------------------------------------------------------------------------------------------------------------------------
IDropTargetOnDrop_RB(TargetObject, pDataObj, KeyState, X, Y, DropEffect) {
   If IDataObject_GetDroppedFiles(pDataObj, DroppedFiles) {
      For Each, FilePath In DroppedFiles
          FileRecycle, %FilePath%
         ; Msg .= FilePath . "`r`n" ; for testing
      ; ToolTip, %A_ThisFunc%:`r`n%Msg% ; for testing
      ; Return 2
   }
   Return 0
}
; ==================================================================================================================================
; Drag-and-drop includes
; ==================================================================================================================================
#Include *i DoDragDrop.ahk
#Include *i IDropTarget.ahk
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: [COM] Help with the IDropSource and IDropTarget interfaces

27 May 2016, 21:11

just me wrote:Nobody can contribute?
Hi just me,

zcooler pointed me to this thread elsewhere. There's a lot I've not done, but this should be enough to get IDragSourceHelper::FromBitmap working using the file represented by the clipboard, as per your example, just me. S_FAIL was being returned because the IDropSource's vtable wasn't being properly created.

Here's the files I've modified (minor modifications to everything but IDropSource.ahk), based on the files from just me's GitHub and with replacement/addition of the files in JM's post above the one I quoted.

Code: Select all

; ==================================================================================================================================
; DoDragDrop -> msdn.microsoft.com/en-us/library/ms678486(v=vs.85).aspx
; Requires: IDataObject.ahk, IDropSource.ahk
; ==================================================================================================================================
; Carries out an OLE drag and drop operation using the current contents of the clipboard.
; Return values:
;     If the data have been dropped successfully, the functions returns the performed drop operation (i.e. 1 for
;     DROPEFFECT_COPY or 2 for DROPEFFECT_MOVE). In all other cases the function returns 0.
;     If DROPEFFECT_MOVE is returned, the drag source should remove the data.
; ==================================================================================================================================
DoDragDrop() {
   ; DRAGDROP_S_DROP = 0x40100
   Static DropEffects := 0x03 ; DROPEFFECT_COPY | DROPEFFECT_MOVE
   IDropSource_Create(IDS)
   If !DllCall("Ole32.dll\OleGetClipboard", "PtrP", pDataObj) {
      HBITMAP := IDragSourceHelper_LoadImage(A_ScriptDir . "\Test.bmp", 64, 64)
      IDSH := IDragSourceHelper_CreateFromBitmap(pDataObj, HBITMAP, 64, 64)
;       IDSH := IDragSourceHelper_CreateFromWindow(pDataObj, TVHWND)
      RC := DllCall("Ole32.dll\DoDragDrop","Ptr", pDataObj, "Ptr", &IDS, "UInt", DropEffects, "PtrP", Effect, "Int")
      If IDataObject_GetPerformedDropEffect(pDataObj, PerformedDropEffect)
         Effect := PerformedDropEffect
      ObjRelease(pDataObj)
      ObjRelease(IDSH)
      DllCall("Gdi32.dll\DeleteObject", "Ptr", HBITMAP)
   }
   IDropSource_Free(IDS)
   Return (RC = 0x40100 ? Effect : 0)
}
; ==================================================================================================================================
#Include *i %A_ScriptDir%\IDataObject.ahk
#Include *i %A_ScriptDir%\IDropSource.ahk
; ==================================================================================================================================

Code: Select all

; ==================================================================================================================================
; IDragSourceHelper interface -> msdn.microsoft.com/en-us/library/bb762034(v=vs.85).aspx
; CLSID_DragDropHelper     "{4657278A-411B-11D2-839A-00C04FD918D0}"
; IID_IDropSourcetHelper   "{DE5BF786-477A-11D2-839D-00C04FD918D0}"
; ==================================================================================================================================
; Initializes the drag-image manager for a windowless control.
; ==================================================================================================================================
IDragSourceHelper_CreateFromBitmap(pDataObj, HBITMAP, Width, Height, ColorKey := 0x00FFFFFF, OffCX := 0, OffCY := 0) {
   Static InitializeFromBitmap := A_PtrSize * 3
   VarSetCapacity(SHDI, 32, 0) ; SHDRAGIMAGE structure, 64-bit size
   NumPut(Width, SHDI, 0, "Int")
   NumPut(Height, SHDI, 4, "Int")
   NumPut(OffCX, SHDI, 8, "Int")
   NumPut(OffCY, SHDI, 12, "Int")
   NumPut(20, SHDI, 8, "Int")
   NumPut(20, SHDI, 12, "Int")
   NumPut(HBITMAP, SHDI, 16, "UPtr")
   NumPut(ColorKey, SHDI, 16 + A_PtrSize, "UInt")
   If (pIDSH := _IDragSourceHelper_GetAppropriateObject()) {
      pVTBL := NumGet(pIDSH + 0, "UPtr")
      DllCall(NumGet(pVTBL + 0, InitializeFromBitmap, "UPtr"), "Ptr", pIDSH, "Ptr", &SHDI, "Ptr", pDataObj, "Int")
      Return pIDSH
   }
   Return False
}
; ==================================================================================================================================
; Initializes the drag-image manager for a control with a window.
; ==================================================================================================================================
IDragSourceHelper_CreateFromWindow(pDataObj, HWND, OffCX := 0, OffCY := 0) {
   Static InitializeFromWindow := A_PtrSize * 4
   If (pIDSH := _IDragSourceHelper_GetAppropriateObject()) {
      pVTBL := NumGet(pIDSH + 0, "UPtr")
      DllCall(NumGet(pVTBL + 0, InitializeFromWindow, "UPtr"), "Ptr", pIDSH, "Ptr", HWND, "Ptr", 0, "Ptr", pDataObj, "Int")
      Return pIDSH
   }
   Return False
}
; ==================================================================================================================================
; Auxiliary functions:
; ==================================================================================================================================
IDragSourceHelper_LoadImage(ImagePath, W := 0, H := 0) {
   HBITMAP := 0
   GDIPModule := DllCall("LoadLibrary", "Str", "Gdiplus.dll", "UPtr")
   VarSetCapacity(SI, 24, 0)
   NumPut(1, SI, 0, "UInt")
   DllCall("Gdiplus.dll\GdiplusStartup", "PtrP", GDIPToken, "Ptr", &SI, "Ptr", 0)
   DllCall("Gdiplus.dll\GdipCreateBitmapFromFile", "WStr", ImagePath, "PtrP", GDIPBitmap)
	DllCall("Gdiplus.dll\GdipGetImageWidth", "Ptr", GDIPBitmap, "UIntP", PW)
	DllCall("Gdiplus.dll\GdipGetImageHeight", "Ptr", GDIPBitmap, "UIntP", PH)
   DllCall("Gdiplus.dll\GdipCreateHBITMAPFromBitmap", "Ptr", GDIPBitmap, "PtrP", HBITMAP, "UInt", 0xFFFFFFFF)
   DllCall("Gdiplus.dll\GdipDisposeImage", "Ptr", GDIPBitmap)
   DllCall("Gdiplus.dll\GdiplusShutdown", "Ptr", GDIPToken)
   DllCall("FreeLibrary", "Ptr", GDIPModule)
   If (W = 0)
      W := PW
   If (H = 0)
      H := PH
   If (W <> PW) || (H <> PH)
      HBITMAP := DllCall("CopyImage", "Ptr", HBITMAP, "UInt", 0, "Int", W, "Int", H, "UInt", 0x800A, "UPtr") ; 0x200A
   Return HBITMAP
}
; ==================================================================================================================================
; Private functions:
; ==================================================================================================================================
_IDragSourceHelper_GetAppropriateObject()
{
   ; This just returns IDSH1; on Vista and later, ComObjQuery for IDragSourceHelper2 appears to work
   return ComObjCreate("{4657278A-411B-11D2-839A-00C04FD918D0}", "{DE5BF786-477A-11D2-839D-00C04FD918D0}")
}

Code: Select all

; ==================================================================================================================================
; IDropSource interface -> msdn.microsoft.com/en-us/library/ms690071(v=vs.85).aspx
; Note: Right-drag is not supported as yet!
; ==================================================================================================================================
IDropSource_Create(ByRef IDropSource) {
   Static Methods := ["QueryInterface", "AddRef", "Release", "QueryContinueDrag", "GiveFeedback"]
   Static VTBL
   if (!VarSetCapacity(VTBL)) {
      VarSetCapacity(VTBL, Methods.Length() * A_PtrSize)
      For Index, Method In Methods {
         CB := RegisterCallback("IDropSource_" . Method, "")
         NumPut(CB, VTBL, (Index-1) * A_PtrSize, "UPtr")
      }
   }
   VarSetCapacity(IDropSource, A_PtrSize)
   NumPut(&VTBL, IDropSource, "UPtr")
}
; ----------------------------------------------------------------------------------------------------------------------------------
; Assuming VTBL is kept static (which I'm doing because of possible scope issues, and the freeing ones that arise from that), this wouldn't work well if multiple 
; IDropSources are present
IDropSource_Free(IDropSource) {
   RETURN

   While (CB := NumGet(IDropSource + (A_PtrSize * A_Index), "Ptr"))
      DllCall("GlobalFree", "Ptr", CB)
   NumPut(0, IDropSource + 0, "UPtr")
}
; ==================================================================================================================================
; The following functions must not be called directly, they are reserved for internal and system use.
; ==================================================================================================================================
IDropSource_QueryInterface(IDropSource, RIID, PPV) {
   ; IUnknown    -> msdn.microsoft.com/en-us/library/ms682521(v=vs.85).aspx
   ; IDropSource -> msdn.microsoft.com/en-us/library/windows/desktop/ms690071(v=vs.85).aspx
   static IID_IUnknown, IID_IDropSource
   if (!VarSetCapacity(IID_IUnknown))
       IDropSource_GuidFromStr("{00000000-0000-0000-C000-000000000046}", IID_IUnknown)
      ,IDropSource_GuidFromStr("{00000121-0000-0000-C000-000000000046}", IID_IDropSource)

   If (IDropSource_IsEqualIID(RIID, &IID_IDropSource) || IDropSource_IsEqualIID(RIID, &IID_IUnknown)) {
      NumPut(IDropSource, PPV + 0, "Ptr")
      Return 0 ; S_OK
   }
   Else {
      NumPut(0, PPV + 0, "Ptr")
      Return 0x80004002 ; E_NOINTERFACE
   }
}
; ----------------------------------------------------------------------------------------------------------------------------------
IDropSource_AddRef(IDropSource) {
   ; IUnknown -> msdn.microsoft.com/en-us/library/ms691379(v=vs.85).aspx
   ; Reference counting is not needed in this case.
   Return 1
}
; ----------------------------------------------------------------------------------------------------------------------------------
IDropSource_Release(IDropSource) {
   ; IUnknown -> msdn.microsoft.com/en-us/library/ms682317(v=vs.85).aspx
   ; Reference counting is not needed in this case.
   Return 0
}
; ----------------------------------------------------------------------------------------------------------------------------------
IDropSource_QueryContinueDrag(IDropSource, fEscapePressed, grfKeyState) {
   ; QueryContinueDrag -> msdn.microsoft.com/en-us/library/ms690076(v=vs.85).aspx
   ; DRAGDROP_S_CANCEL : S_OK : DRAGDROP_S_DROP
   Return (fEscapePressed ? 0x40101 : (grfKeyState & 0x01) ? 0 : 0x40100)
}
; ----------------------------------------------------------------------------------------------------------------------------------
IDropSource_GiveFeedback(IDropSource, dwEffect) {
   ; GiveFeedback -> msdn.microsoft.com/en-us/library/ms693723(v=vs.85).aspx
   Return 0x40102 ; DRAGDROP_S_USEDEFAULTCURSORS
}
; ----------------------------------------------------------------------------------------------------------------------------------
IDropSource_IsEqualIID(rguid1, rguid2) {
   Static IsEqualGUID := DllCall("GetProcAddress", "Ptr", DllCall("GetModuleHandle", "Str", "ole32", "Ptr"), "AStr", "IsEqualGUID", "Ptr")
   Return DllCall(IsEqualGUID, "Ptr", rguid1, "Ptr", rguid2)
}
; ----------------------------------------------------------------------------------------------------------------------------------
IDropSource_GuidFromStr(sGuid, ByRef pGuid) {
   If (VarSetCapacity(pGuid) < 16)
      VarSetCapacity(pGuid, 16)
   Return DllCall("ole32\CLSIDFromString", "WStr", sGuid, "Ptr", &pGuid) == 0 ? &pGuid : 0
}
; ==================================================================================================================================
#Include *i %A_ScriptDir%\IDragSourceHelper.ahk
; ==================================================================================================================================
Thanks to zcooler for his DnD interface test program, which I used to actually test what I was changing. A version I changed slightly to make it easier for me to test:

Code: Select all

#NoEnv
#SingleInstance, force
SetBatchLines, -1 ; important for performance
SetWinDelay, -1   ; important for Dock()
; ==================================================================================================================================
; Media folders
RSFolder := "C:\1\Kaaris-Or_Noir_Part_II-FR-Reissue-2CD-FLAC-2014-Mrflac\CD1" ; Set the folder path here...easily better test if the folder has sub-folders.
RSCaption := "DRAGnDROP Interface Test - Set some folder which this button should display!"
; WatchFolder settings
WatchFolders=%RSFolder%* ; *=recurse subfolders
; ==================================================================================================================================
; Global variables for Dock() replacement
Global Gui1X, Gui1Y, Gui2X, Gui2Y, HGUI1, HGUI2
Global Gui2Visible := False
; Global variables for the TreeView
Global Logging := False    ; set to True to enable logging within the ReportFunction
Global TVHWND              ; handle of the TreeView control, filled later
Global TVFolder            ; name of the current root folder, filled later
Global TVRootID            ; ID of the current root node, filled later
Global TVDragID := 0       ; ID of the currently dragged item +++++
Global TVDropID := 0       ; ID of the item on which a drop ocurred +++++
Global TVLastID            ; ID of the last visible item +++++
Global TVClientW           ; the width of the TreeView control's client area, calculated later
Global TVClientH           ; the height of the TreeView control's client area, calculated later
Global TVDeltaW            ; delta to add to the width of the TreeView control, calculated later
Global TVDeltaH            ; delta to add to the height of the TreeView control, calculated later
Global TVItemHeight        ; height of all tree view items, calculated later
Global TVMaxHeight         ; maximum height of the TreeView control, calculated later
Global TVMaxRows := 20     ; maximum rows shown in the TreeView control
Global TVMinWidth := 300   ; minimum width of the TreeView control - not implemented yet
Global TVMaxWidth := 600   ; maximum width of the TreeView control - not implemented yet
;========================================================================================
; GUI 1
Gui, Gui1:New, +HwndHGUI1 +LabelGui1
Gui, Margin, 10, 10
Gui, Add, Button, ym gGetFiles vRS hwndHRSBTN, %RSCaption%     ;+++ added hwnd option
IDT_RS := IDropTarget_Create(HRSBTN, "_BTN") ;+++ Create a drop target for the buttons
Gui, Show, x400 y35, Some GUI

; ==================================================================================================================================
; GUI 2
Gui, Gui2:New, +HwndHGUI2 +OwnerGui1 -Caption +0x02000000 ; WS_CLIPCHILDREN = 0x02000000 is needed in this case ;
Gui, Margin, 0, 0
Gui, Color, 000000
Gui, Font, s9 w800, Ebrima ; Segoe UI Symbol Arial Tahoma
; Create the TreeView
Gui, Add, TreeView, r%TVMaxRows% w650 hwndTVHWND +0x0800 +0x200 Backgroundf2f2f2 -HScroll AltSubmit gTVLabel vTVVar -E0x0200 +E0x020000
; Calculate the deltas between the TreeView's non-client and client area
GuiControlGet, TV, Pos, TVVar
TVMaxHeight := TVH
TVDeltaH := Mod(TVMaxHeight, TVMaxRows)
TVDeltaW := TVDeltaH
; Set Vista+ theme and styles
TV_SetExplorerTheme(TVHWND, "R")
TV_DoubleBuffer(TVHWND)

ImageListID := IL_Create(12)
Loop 12 {
   IL_Add(ImageListID, "shell32.dll", A_Index) 
}
TV_SetImageList(ImageListID)
; Add a faked StatusBar
Gui, Add, Text, xp y+0 wp h20 +0x200 cWhite hwndTVHSB vSBVar +E0x020000
IDT_TV := IDropTarget_Create(TVHWND, "_TV") ;+++ Create a drop target for the TreeView
; ==================================================================================================================================
; Message handlers and monitoring
; Watched folders
Loop, Parse, WatchFolders,|
{
   SubTree := (SubStr(A_LoopField, StrLen(A_LoopField)) = "*")
   WatchFolder(RTrim(A_LoopField, "*"), "ReportFunction", SubTree, Watch := 19)
}
; Dock message handler
OnMessage(0x0003, "Dock") ; WM_MOVE = 0x0003
; Keyboard Nav handler
OnMessage(0x100, "OnKeyDown")
Return
; ======================================================================================================================
; Gui1 labels
; ======================================================================================================================
Gui1Close:
IDT_TV.RevokeDragDrop()
ExitApp
; ======================================================================================================================
; Gui2 labels
; ======================================================================================================================
GetFiles:
IsKEvent            := true
WinGetPos, Gui1X, Gui1Y, , , ahk_id %HGUI1%
GuiControlGet, HCTL, Hwnd, %A_GuiControl%
WinGetPos, CtlX, CtlY, CtlW, CtlH, ahk_id %HCTL%
Gui, Gui2:Default
Gui, Hide
Gui2Visible := False
GuiControl, -g, TVVar
TV_Delete()
TVFolder := %A_GuiControl%Folder
ItemCount := AddFoldersAndFilesToTree(TVFolder) ; Populate treeview call
TVRootID := TV_GetNext()
TV_Modify(TVRootID, "Select VisFirst Expand")
TVW := TVH := 0
GuiControlGet, TV, Pos, TVVar
GuiControl, Move, SBVar, % "y" . (TVY + TVH) . " w" . TVW
GuiControl, , SBVar, % "   " . ItemCount - 1 . " objects."
X := CtlX
Y := CtlY + CtlH
Gui, Show, x%X% y%Y% AutoSize
WinGetPos, Gui2X, Gui2Y, , , ahk_id %HGUI2%
GuiControl, +gTVLabel, TVVar
Gui2Visible := True
Return
; ----------------------------------------------------------------------------------------------------------------------
;+++ The GuiDropFiles label for Gui2 has to be removed. All code has to be moved to the IDropTargetOnDrop user function.
; ======================================================================================================================
; Common subroutines to Gui2
; ======================================================================================================================
TVLabel:
Critical ; try to forgo the use of Critical if possible
If (A_GuiEvent = "+") || (A_GuiEvent = "-") {
   GuiControl, -Redraw, TVVar
   GuiControlGet, TV, Pos, TVVar
   Gui, Show, AutoSize
   GuiControl, +Redraw, TVVar
}
Else If (A_GuiEvent = "Normal") && (A_EventInfo = TV_HitTest(TVHWND)) { ; in this case the item will be selected definitely
  ItemID := A_EventInfo
  Gosub, TVLabelSelect
}
Else If (A_GuiEvent == "D") { ;+++ Only left dragging is supported, changed
   TVDragID := A_EventInfo
   GoSub, TVDragDrop
}
Else
   IsKEvent := A_GuiEvent == "K" ? True : False
Return
;------------------------------------------------------------------------------
TVLabelSelect:
   TV_GetText(item, ItemID)
   FileName := item
   ItemID2 := ItemID
   while ParentID := TV_GetParent(ItemID2)
   {
      TV_GetText(item, ParentID)
      FileName := item "\" FileName
      ItemID2 := ParentID
   }
   if (IsMovie := InStr(FileExist(FileName), "D") ? false : true) 
	  RunWait, Open %FileName%
   else {
      GuiControl, -g, TVVar
      TV_Modify(ItemID, TV_Get(ItemID, "Expand") ? "-Expand" : "Expand")
      GuiControl, -Redraw, TVVar
      GuiControlGet, TV, Pos, TVVar
      Gui, Show, AutoSize
      GuiControl, +Redraw, TVVar
      GuiControl, +gTVLabel, TVVar
   }
return
;------------------------------------------------------------------------------ 
TVDragDrop:
   TVDragPath := TV_GetPath(TVDragID)
   TVDropID := 0
   TVLastID := TV_GetLastVisible(TVHWND)
   TV_GetClientSize(TVHWND, TVClientW, TVClientH)
   TVItemHeight := TV_GetItemHeight(TVHWND) ;;;;from old TVDragDrop label
   If (IsCritical := A_IsCritical)
      Critical, Off
   GuiControl, -g, TVVar       ;;;;from old TVDragDrop label
   ;DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0009, "Ptr", 0) ; TVM_SELECTITEM, TVGN_CARET (remove the selection)
   ;+++ TVDragDropRunning := True   v ; deprecated
   ;SavedClipboard := ClipBoardAll
   ;ClipboardSetFiles(TVDragPath, "")
   ;SB_SetText("   Dragging ...")
   DropEffect := DoDragDrop()
   ;SB_SetText("   DropEffect: " . {0: "NONE", 1: "COPY", 2: "MOVE"}[DropEffect])
   SetTimer, TVDragDropTimer, Off
   TVDragID := 0 ;+++
   TVDropID := 0 ;+++
   ;+++ Sleep, 10                     ; deprecated
   ;+++ If (DropEffect = 0)           ; deprecated
   ;+++    TVDragDropRunning := False ; deprecated
   ;ClipBoard := SavedClipboard
   ;DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0008, "Ptr", 0) ; TVM_SELECTITEM, TVIS_DROPHILITED
   GuiControl, +gTVLabel, TVVar  ;;;;from old TVDragDrop label
   If (IsCritical)
      Critical, %IsCritical%
Return
;------------------------------------------------------------------------------ 
TVDragDropTimer:
   MouseItemID := TV_HitTest(TVHWND, 0x0026)
   If (MouseItemID = 0) {
      TVDropID := 0
      DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0008, "Ptr", 0) ; TVM_SELECTITEM, TVIS_DROPHILITED
   }
   Else If (MouseItemID <> TVDropID) {
      TVDropID := MouseItemID
      HoverTickCount := ScrollTickCount := A_TickCount
      DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0008, "Ptr", TVDropID) ; TVM_SELECTITEM, TVIS_DROPHILITED
   }
   Else {
      If ((A_TickCount - HoverTickCount) >= 1000) {
         VarSetCapacity(TVIX, 40 + (A_PtrSize * 5), 0)
         NumPut(0x0048, TVIX, 0, "UInt") ; TVIF_CHILDREN | TVIF_STATE
         NumPut(TVDropID, TVIX, A_PtrSize, "UPtr")
         If DllCall("SendMessage", "Ptr", TVHWND, "UInt", A_IsUnicode ? 0x113E : 0x110C, "Ptr", 0, "Ptr", &TVIX, "Int")
         && (NumGet(TVIX, 20 + (A_PtrSize * 3), "Int") = 1) && !(NumGet(TVIX, A_PtrSize * 2, "Uint") & 0x0020) {
            DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x1102, "Ptr", 0x0002, "Ptr", TVDropID, "Int") ; TVM_EXPAND
            HoverTickCount := A_TickCount
            If (TVDropID = TVLastID)
               TVLastID := TV_GetLastVisible(TVHWND)
         }
      }
      If ((A_TickCount - ScrollTickCount) >= 100) { ; 100 ms seems to be sufficient here for scrolling
         If (TVDropID = TV_GetFirstVisible(TVHWND)) && (TVDropID <> TVRootID) {
            DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0008, "Ptr", 0) ; TVM_SELECTITEM, TVIS_DROPHILITED
            DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x0115, "Ptr", 0, "Ptr", 0) ; WM_VSCROLL
            TVDropID := 0
            HoverTickCount := A_TickCount
         }
         Else {
            TV_GetItemRect(TVHWND, TVDropID, X, Y, W, H)
            If (TVDropID = TVLastID) {
               If ((Y + H) > TVClientH)
                  DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x0115, "Ptr", 1, "Ptr", 0) ; WM_VSCROLL
            }
            Else If ((Y + H + TVItemHeight) > TVClientH) {
               DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0008, "Ptr", 0) ; TVM_SELECTITEM, TVIS_DROPHILITED
               DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x0115, "Ptr", 1, "Ptr", 0) ; WM_VSCROLL
               TVDropID := 0
               HoverTickCount := A_TickCount
            }
         }
         ScrollTickCount := A_TickCount
      }
   }
Return
;----------------------------------------------------------------------------------------------------------
;-FUNCTIONS------------------------------------------------------------------------------------------------
;----------------------------------------------------------------------------------------------------------
;----------------------------------------------------------------------------------------------------------
AddFoldersAndFilesToTree(Folder, ParentID := 0, First := 0) {
   ;msgbox % First
   SplitPath, Folder, FolderName
   If (ParentID = 0)
      ID := TV_Add(Folder, ParentID, "Icon7 Expand")     ; ID gets a value
   Else If (First = 0)
  	  ID := TV_Add(FolderName, ParentID, "Icon7")        ; ID gets a value
   Else If (First = 1)
      ID := TV_Add(FolderName, ParentID, "First Icon7")  ; ID gets a value
   ; -------------------------------------------------------------------------------------------------------------------------------
      FolderList =
      Loop, %Folder%\*.*, 2
         FolderList .= (A_Index = 1 ? "" : "`n") A_LoopFileTimeModified "|" A_LoopFileName "|" A_LoopFileLongPath
      ; Sort folder options If recurse
      If (Settings.SortRev = "1")                     ; Sort folders reverse enabled
         Sort, FolderList, F ReverseDirection D`n,    ; Reverses the list so that it contains 4,3,2,1
      If (Settings.SortTimeRev = "1")                 ; Sort folders timereverse enabled
         Sort, FolderList, F SortFunc                 ; Sort folders TimeModified, newest on top to oldest in bottom
      Loop, Parse, FolderList, `n
         AddFoldersAndFilesToTree(StrSplit(A_LoopField, "|").3, ID)
   ; -------------------------------------------------------------------------------------------------------------------------------
	FileList := ""
	Loop, % Folder . "\*.*", 0
         FileList .= (FileList <> "" ? "`n" : "") . A_LoopFileTimeCreated . "|" . A_LoopFileName . "|" . A_LoopFileTimeModified
   ; -------------------------------------------------------------------------------------------------------------------------------
    Sort, FileList, F ReverseDirection D`n,   ; Reverses the list so that it contains 4,3,2,1
   ; -------------------------------------------------------------------------------------------------------------------------------
   Loop, Parse, FileList, `n
   {
      Split := StrSplit(A_LoopField, "|") ;+++ to avoid repeated calls of StrSplit()
      If (Split.1 = Split.3) ; recording isn't finished yet
	     TV_Add(Split.2, ID, "Icon4")
	  Else                 ; recording is finished
	     TV_Add(Split.2, ID, "Icon2")
   }
   Return TV_GetCount()
}
; ==================================================================================================================================
; Auxiliary functions ==============================================================================================================
; ==================================================================================================================================
SortFunc(_line1, _line2)
{
    line1 := StrSplit(_line1, "|")
    line2 := StrSplit(_line2, "|")
 
    if (line1[1] < line2[1])        ; sort in descending order
        ;msgbox % line1[1] " " line2[1] "`ndescending"
		return 1
 
    if (line1[1] = line2[1])
	   ;msgbox % line1[1] "`nascending"
        if (line1[2] > line2[2])    ; sort in ascending order
            return 1
 
    return -1
}
ReverseDirection(a1, a2, offset)
{
    return offset  ; Offset is positive if a2 came after a1 in the original list; negative otherwise.
}
;--------------------------------------------------------------------------------
; Docking Guis
Dock(W, L, M, H) { ; <<<<< added Dock replacement
   If (H = HGUI1)  && Gui2Visible {
      WinGetPos, X, Y, , , ahk_id %HGUI1%
      Gui2X += X - Gui1X
      Gui2Y += Y - Gui1Y
      Gui1X := X
      Gui1Y := Y
      WinMove, ahk_id %HGUI2%, , %Gui2X%, %Gui2Y%
   }
}
; ==================================================================================================================================
; Explorer function for Drag&Drop and Pasting. Enables the explorer paste context menu option.
; ==================================================================================================================================
ClipboardSetFiles(FilesToSet, DropEffect := "Copy") {
   Static TCS := A_IsUnicode ? 2 : 1 ; size of a TCHAR
   Static PreferredDropEffect := DllCall("RegisterClipboardFormat", "Str", "Preferred DropEffect")
   Static DropEffects := {1: 1, 2: 2, Copy: 1, Move: 2}
   ; -------------------------------------------------------------------------------------------------------------------
   ; Count files and total string length
   TotalLength := 0
   FileArray := []
   Loop, Parse, FilesToSet, `n, `r
   {
      If (Length := StrLen(A_LoopField))
         FileArray.Push({Path: A_LoopField, Len: Length + 1})
      TotalLength += Length
   }
   FileCount := FileArray.Length()
   If !(FileCount && TotalLength)
      Return
   ; -------------------------------------------------------------------------------------------------------------------
   ; Add files to the clipboard
   If DllCall("OpenClipboard", "Ptr", A_ScriptHwnd) && DllCall("EmptyClipboard") {
      ; HDROP format ---------------------------------------------------------------------------------------------------
      ; 0x42 = GMEM_MOVEABLE (0x02) | GMEM_ZEROINIT (0x40)
      hPath := DllCall("GlobalAlloc", "UInt", 0x42, "UInt", 20 + (TotalLength + FileCount + 1) * TCS, "UPtr")
      pPath := DllCall("GlobalLock", "Ptr" , hPath)
      Offset := 20
      NumPut(Offset, pPath + 0, "UInt")         ; DROPFILES.pFiles = offset of file list
      NumPut(!!A_IsUnicode, pPath + 16, "UInt") ; DROPFILES.fWide = 0 --> ANSI, fWide = 1 --> Unicode
      For Each, File In FileArray
         Offset += StrPut(File.Path, pPath + Offset, File.Len) * TCS
      DllCall("GlobalUnlock", "Ptr", hPath)
      DllCall("SetClipboardData","UInt", 0x0F, "UPtr", hPath) ; 0x0F = CF_HDROP
      ; Preferred DropEffect format ------------------------------------------------------------------------------------
      If (DropEffect := DropEffects[DropEffect]) {
         ; Write Preferred DropEffect structure to clipboard to switch between copy/cut operations
         ; 0x42 = GMEM_MOVEABLE (0x02) | GMEM_ZEROINIT (0x40)
         hMem := DllCall("GlobalAlloc", "UInt", 0x42, "UInt", 4, "UPtr")
         pMem := DllCall("GlobalLock", "Ptr", hMem)
         NumPut(DropEffect, pMem + 0, "UChar")
         DllCall("GlobalUnlock", "Ptr", hMem)
         DllCall("SetClipboardData", "UInt", PreferredDropEffect, "Ptr", hMem)
      }
      DllCall("CloseClipboard")
   }
   Return
}
;--------------------------------------------------------------------------------------
; Clipboard function. Retrieves if files in clipboard comes from an explorer cut or copy operation.
; explorer copy = 5, explorer cut = 2
ClipboardGetDropEffect() {
   Static PreferredDropEffect := DllCall("RegisterClipboardFormat", "Str" , "Preferred DropEffect")
   DropEffect := 0
   If DllCall("IsClipboardFormatAvailable", "UInt", PreferredDropEffect) {
      If DllCall("OpenClipboard", "Ptr", 0) {
         hDropEffect := DllCall("GetClipboardData", "UInt", PreferredDropEffect, "UPtr")
         pDropEffect := DllCall("GlobalLock", "Ptr", hDropEffect, "UPtr")
         DropEffect := NumGet(pDropEffect + 0, 0, "UChar")
         DllCall("GlobalUnlock", "Ptr", hDropEffect)
         DllCall("CloseClipboard")
      }
   }
   Return DropEffect
}
;------------reverse logging function------------------------------------------------------------------------------
AddToBOF(FileName, String) { ; AddToBeginOfFile
   If (File := FileOpen(FileName, "rw", "UTF-8-RAW")) {
      Content := File.Read()
      File.Pos := 0
      File.Write(String . Content)
      File.Close()
      Return True
   }
   Return False
}
; ==================================================================================================================================
; Function:       Notifies about changes within folders.
;                 This is a rewrite of HotKeyIt's WatchDirectory() released at
;                    http://www.autohotkey.com/board/topic/60125-ahk-lv2-watchdirectory-report-directory-changes/
; Tested with:    AHK 1.1.23.01 (A32/U32/U64)
; Tested on:      Win 10 Pro x64
; Usage:          WatchFolder(Folder, UserFunc[, SubTree := False[, Watch := 3]])
; Parameters:
;     Folder      -  The full qualified path of the folder to be watched.
;                    Pass the string "**PAUSE" and set UserFunc to either True or False to pause respectively resume watching.
;                    Pass the string "**END" and an arbitrary value in UserFunc to completely stop watching anytime.
;                    If not, it will be done internally on exit.
;     UserFunc    -  The name of a user-defined function to call on changes. The function must accept at least two parameters:
;                    1: The path of the affected folder. The final backslash is not included even if it is a drive's root
;                       directory (e.g. C:).
;                    2: An array of change notifications containing the following keys:
;                       Action:  One of the integer values specified as FILE_ACTION_... (see below).
;                                In case of renaming Action is set to FILE_ACTION_RENAMED (4).
;                       Name:    The full path of the changed file or folder.
;                       OldName: The previous path in case of renaming, otherwise not used.
;                       IsDir:   True if Name is a directory; otherwise False. In case of Action 2 (removed) IsDir is always False.
;                    Pass the string "**DEL" to remove the directory from the list of watched folders.
;     SubTree     -  Set to true if you want the whole subtree to be watched (i.e. the contents of all sub-folders).
;                    Default: False - sub-folders aren't watched.
;     Watch       -  The kind of changes to watch for. This can be one or any combination of the FILE_NOTIFY_CHANGES_...
;                    values specified below.
;                    Default: 0x03 - FILE_NOTIFY_CHANGE_FILE_NAME + FILE_NOTIFY_CHANGE_DIR_NAME
; Return values:
;     Returns True on success; otherwise False.
; Change history:
;     1.0.01.00/2016-03-14/just me        -  bug-fix for multiple folders
;     1.0.00.00/2015-06-21/just me        -  initial release
; License:
;     The Unlicense -> http://unlicense.org/
; Remarks:
;     Due to the limits of the API function WaitForMultipleObjects() you cannot watch more than MAXIMUM_WAIT_OBJECTS (64)
;     folders simultaneously.
; MSDN:
;     ReadDirectoryChangesW          msdn.microsoft.com/en-us/library/aa365465(v=vs.85).aspx
;     FILE_NOTIFY_CHANGE_FILE_NAME   = 1   (0x00000001) : Notify about renaming, creating, or deleting a file.
;     FILE_NOTIFY_CHANGE_DIR_NAME    = 2   (0x00000002) : Notify about creating or deleting a directory.
;     FILE_NOTIFY_CHANGE_ATTRIBUTES  = 4   (0x00000004) : Notify about attribute changes.
;     FILE_NOTIFY_CHANGE_SIZE        = 8   (0x00000008) : Notify about any file-size change.
;     FILE_NOTIFY_CHANGE_LAST_WRITE  = 16  (0x00000010) : Notify about any change to the last write-time of files.
;     FILE_NOTIFY_CHANGE_LAST_ACCESS = 32  (0x00000020) : Notify about any change to the last access time of files.
;     FILE_NOTIFY_CHANGE_CREATION    = 64  (0x00000040) : Notify about any change to the creation time of files.
;     FILE_NOTIFY_CHANGE_SECURITY    = 256 (0x00000100) : Notify about any security-descriptor change.
;     FILE_NOTIFY_INFORMATION        msdn.microsoft.com/en-us/library/aa364391(v=vs.85).aspx
;     FILE_ACTION_ADDED              = 1   (0x00000001) : The file was added to the directory.
;     FILE_ACTION_REMOVED            = 2   (0x00000002) : The file was removed from the directory.
;     FILE_ACTION_MODIFIED           = 3   (0x00000003) : The file was modified.
;     FILE_ACTION_RENAMED            = 4   (0x00000004) : The file was renamed (not defined by Microsoft).
;     FILE_ACTION_RENAMED_OLD_NAME   = 4   (0x00000004) : The file was renamed and this is the old name.
;     FILE_ACTION_RENAMED_NEW_NAME   = 5   (0x00000005) : The file was renamed and this is the new name.
;     GetOverlappedResult            msdn.microsoft.com/en-us/library/ms683209(v=vs.85).aspx
;     CreateFile                     msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx
;     FILE_FLAG_BACKUP_SEMANTICS     = 0x02000000
;     FILE_FLAG_OVERLAPPED           = 0x40000000
; ==================================================================================================================================
WatchFolder(Folder, UserFunc, SubTree := False, Watch := 0x03) {
   Static DummyObject := {Base: {__Delete: Func("WatchFolder").Bind("**END", "")}}
   Static TimerID := "**" . A_TickCount
   Static TimerFunc := Func("WatchFolder").Bind(TimerID, "")
   Static MAXIMUM_WAIT_OBJECTS := 64
   Static MAX_DIR_PATH := 260 - 12 + 1
   Static SizeOfLongPath := MAX_DIR_PATH << !!A_IsUnicode
   Static SizeOfFNI := 0xFFFF ; size of the FILE_NOTIFY_INFORMATION structure buffer (64 KB)
   Static SizeOfOVL := 32     ; size of the OVERLAPPED structure (64-bit)
   Static WatchedFolders := {}
   Static EventArray := []
   Static HandleArray := []
   Static WaitObjects := 0
   Static BytesRead := 0
   Static Paused := False
   ; ===============================================================================================================================
   If (Folder = "")
      Return False
   SetTimer, % TimerFunc, Off
   RebuildWaitObjects := False
   ; ===============================================================================================================================
   If (Folder = TimerID) { ; called by timer
      If (ObjCount := EventArray.Length()) && !Paused {
         ObjIndex := DllCall("WaitForMultipleObjects", "UInt", ObjCount, "Ptr", &WaitObjects, "Int", 0, "UInt", 0, "UInt")
         While (ObjIndex >= 0) && (ObjIndex < ObjCount) {
            FolderName := WatchedFolders[ObjIndex + 1]
            D := WatchedFolders[FolderName]
            If DllCall("GetOverlappedResult", "Ptr", D.Handle, "Ptr", D.OVLAddr, "UIntP", BytesRead, "Int", True) {
               Changes := []
               FNIAddr := D.FNIAddr
               FNIMax := FNIAddr + BytesRead
               OffSet := 0
               PrevIndex := 0
               PrevAction := 0
               PrevName := ""
               Loop {
                  FNIAddr += Offset
                  OffSet := NumGet(FNIAddr + 0, "UInt")
                  Action := NumGet(FNIAddr + 4, "UInt")
                  Length := NumGet(FNIAddr + 8, "UInt") // 2
                  Name   := FolderName . "\" . StrGet(FNIAddr + 12, Length, "UTF-16")
                  IsDir  := InStr(FileExist(Name), "D") ? 1 : 0
                  If (Name = PrevName) {
                     If (Action = PrevAction)
                        Continue
                     If (Action = 1) && (PrevAction = 2) {
                        PrevAction := Action
                        Changes.RemoveAt(PrevIndex--)
                        Continue
                     }
                  }
                  If (Action = 4)
                     PrevIndex := Changes.Push({Action: Action, OldName: Name, IsDir: 0})
                  Else If (Action = 5) && (PrevAction = 4) {
                     Changes[PrevIndex, "Name"] := Name
                     Changes[PrevIndex, "IsDir"] := IsDir
                  }
                  Else
                     PrevIndex := Changes.Push({Action: Action, Name: Name, IsDir: IsDir})
                  PrevAction := Action
                  PrevName := Name
               } Until (Offset = 0) || ((FNIAddr + Offset) > FNIMax)
               If (Changes.Length() > 0)
                  D.Func.Call(FolderName, Changes)
               DllCall("ResetEvent", "Ptr", EventArray[D.Index])
               DllCall("ReadDirectoryChangesW", "Ptr", D.Handle, "Ptr", D.FNIAddr, "UInt", SizeOfFNI, "Int", D.SubTree
                                              , "UInt", D.Watch, "UInt", 0, "Ptr", D.OVLAddr, "Ptr", 0)
            }
            ObjIndex := DllCall("WaitForMultipleObjects", "UInt", ObjCount, "Ptr", &WaitObjects, "Int", 0, "UInt", 0, "UInt")
            Sleep, 0
         }
      }
   }
   ; ===============================================================================================================================
   Else If (Folder = "**PAUSE") { ; called to pause/resume watching
      Paused := !!UserFunc
      RebuildObjects := Paused
   }
   ; ===============================================================================================================================
   Else If (Folder = "**END") { ; called to stop watching
      For D In WatchedFolders
         If D Is Not Integer
            DllCall("CloseHandle", "Ptr", D.Handle)
      For Each, Event In EventArray
         DllCall("CloseHandle", "Ptr", Event)
      WatchedFolders := {}
      EventArray := []
      Paused := False
      Return True
   }
   ; ===============================================================================================================================
   Else { ; called to add, update, or remove folders
      Folder := RTrim(Folder, "\")
      VarSetCapacity(LongPath, SizeOfLongPath, 0)
      If !DllCall("GetLongPathName", "Str", Folder, "Ptr", &LongPath, "UInt", SizeOfLongPath)
         Return False
      VarSetCapacity(LongPath, -1)
      Folder := LongPath
      If (WatchedFolders[Folder]) { ; update or remove
         Handle := WatchedFolders[Folder, "Handle"]
         Index  := WatchedFolders[Folder, "Index"]
         DllCall("CloseHandle", "Ptr", Handle)
         DllCall("CloseHandle", "Ptr", EventArray[Index])
         EventArray.RemoveAt(Index)
         WatchedFolders.RemoveAt(Index)
         WatchedFolders.Delete(Folder)
         RebuildWaitObjects := True
      }
      If InStr(FileExist(Folder), "D") && (UserFunc <> "**DEL") && (EventArray.Length() < MAXIMUM_WAIT_OBJECTS) {
         If (IsFunc(UserFunc) && (UserFunc := Func(UserFunc)) && (UserFunc.MinParams >= 2)) && (Watch &= 0x017F) {
            Handle := DllCall("CreateFile", "Str", Folder . "\", "UInt", 0x01, "UInt", 0x07, "Ptr",0, "UInt", 0x03
                                          , "UInt", 0x42000000, "Ptr", 0, "UPtr")
            If (Handle > 0) {
               Event := DllCall("CreateEvent", "Ptr", 0, "Int", 1, "Int", 0, "Ptr", 0)
               Index := EventArray.Push(Event)
               WatchedFolders[Index] := Folder
               WatchedFolders[Folder] := {Func: UserFunc, Handle: Handle, Index: Index, SubTree: !!SubTree, Watch: Watch}
               WatchedFolders[Folder].SetCapacity("FNIBuff", SizeOfFNI)
               FNIAddr := WatchedFolders[Folder].GetAddress("FNIBuff")
               DllCall("RtlZeroMemory", "Ptr", FNIAddr, "Ptr", SizeOfFNI)
               WatchedFolders[Folder, "FNIAddr"] := FNIAddr
               WatchedFolders[Folder].SetCapacity("OVLBuff", SizeOfOVL)
               OVLAddr := WatchedFolders[Folder].GetAddress("OVLBuff")
               DllCall("RtlZeroMemory", "Ptr", OVLAddr, "Ptr", SizeOfOVL)
               NumPut(Event, OVLAddr + 8, A_PtrSize * 2, "Ptr")
               WatchedFolders[Folder, "OVLAddr"] := OVLAddr
               DllCall("ReadDirectoryChangesW", "Ptr", Handle, "Ptr", FNIAddr, "UInt", SizeOfFNI, "Int", SubTree
                                              , "UInt", Watch, "UInt", 0, "Ptr", OVLAddr, "Ptr", 0)
               RebuildWaitObjects := True
            }
         }
      }
      If (RebuildWaitObjects) {
         VarSetCapacity(WaitObjects, MAXIMUM_WAIT_OBJECTS * A_PtrSize, 0)
         OffSet := &WaitObjects
         For Index, Event In EventArray
            Offset := NumPut(Event, Offset + 0, 0, "Ptr")
      }
   }
   ; ===============================================================================================================================
   If (EventArray.Length() > 0)
      SetTimer, % TimerFunc, -100
   Return (RebuildWaitObjects) ; returns True on success, otherwise False
}
; ==================================================================================================================================
; WatchFolder - Reportfunctions
ReportFunction(Directory, Changes) {
   Global Dummy_DB_BackupEntry, TooltipInfoUpdater, SRFolder, tooltip_infofiles, IsMovie, Logging, RecycleBin_C, RecycleBin_D, RecycleBin_E, RecycleBin_F, RecycleBin_G
   Static LogFile := ""
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; Logging
   If (LogFile = "")
      LogFile := TooltipInfoUpdater . "\wd_changes.log"
   If (Logging)
      FormatTime, Prefix, , yyyy-MM-dd HH:mm:ss
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; Check if the current TreeView might be affected.
   TVChanged := False
   If (Gui2Visible) {
      Gui, Gui2:Default
      Gui, TreeView, TVVar
      GuiControl, -g, TVVar
      GuiControl, -Redraw, TVVar
      ; ----------------------------------------------------------------------------------------------------------------------------
      ; Loop through the changes.
      For Each, Change In Changes {
         Action := Change.Action
         Name := Change.Name
         OldName := Change.Oldname
         IsDir := Change.Isdir
         If (Logging)
            AddToBOF(LogFile, Prefix . " ReportFunction: Action: " . Action . " - Name: " . Name . " - OldName: " . OldName
                            . " - IsDir: " . IsDir . "`r`n")
         ; -------------------------------------------------------------------------------------------------------------------------
         ; Process action 4 (renamed) at first, because Action might have to be changed and processed later on.
         If (Action = 4) {
            OldID := FindPath(OldName, OldFoundPath)
            If (OldName <> OldFoundPath) ; OldName was not found, so we change Action to 1 (added)
               Action := 1 ; will be processed later on
            Else { ; OldName has been found, so we have to check if the new name is valid
               SplitPath, Name, File, Dir, Ext
               If (IsDir) { ; the new name is valid, modify the tree
	              TVChanged := TV_Modify(OldID, , File)
                  If (Logging)
                     AddToBOF(LogFile, Prefix . " TreeView: Renamed " . OldName . " to " . Name . "`r`n")
               }
               Else { ; modifying is not possible, so we have to delete the entry
	              TVChanged := TV_Delete(OldID)
                  If (Logging)
                     AddToBOF(LogFile, Prefix . " TreeView: Removed " . Name . "`r`n")
               }
               Continue ; nothing else to do, next change
            }
         }
		 ; -------------------------------------------------------------------------------------------------------------------------
         ; Action 3 (modified)
         If (Action = 3) {
            ItemID := FindPath(Name, FoundPath)
            If (!IsDir && (Name = FoundPath)) { ; a file shown in the TreeView has been modified
               SplitPath, Name, , , Ext
               If Ext In ts,avi,mp4,mkv,wmv,mpg ; it's a movie file, so the icon might need to be updated
               {
	              TVChanged := TV_Modify(ItemID, "Icon2")
				  If (Logging)
                     AddToBOF(LogFile, Prefix . " TreeView: Modified " . Name . "`r`n")
               }
            }
            Continue ; nothing else to do, next change
         }
         ; -------------------------------------------------------------------------------------------------------------------------
         ; Action 2 (removed)
         If (Action = 2) {
			ItemID := FindPath(Name, FoundPath)
            If (Name = FoundPath) { ; Name was found and must be deleted
			   TVChanged := TV_Delete(ItemID)
               If (Logging)
                  AddToBOF(LogFile, Prefix . " TreeView: Removed " . Name . "`r`n")
            }
            Continue ; nothing else to do, next change
         }
         ; -------------------------------------------------------------------------------------------------------------------------
         ; Action 1 (added)
         If (Action = 1) {
            ItemID := FindPath(Name, FoundPath)
            If (!ItemID || (Name = FoundPath)) ; Name doesn't belong to the TreeView or exists already, nothing to do with it
               Continue
            SplitPath, Name, File, Dir, Ext
            If (IsDir) { ; it's a folder
			      TVChanged := AddFoldersAndFilesToTree(Name, ItemID, First := 1)
               If (Logging)
                  AddToBOF(LogFile, Prefix . " TreeView: Added " . Name . "`r`n")
			   }
            Else { ; it's a file
               If (FoundPath = Dir) {
                  If Ext In ts,avi,mp4,mkv,wmv,mpg
                  {
                     FileGetTime, Created, %Name%, C
                     FileGetTime, Modified, %Name%, M
                     If (Created = Modified)  ; File still in recording
	                    TVChanged := TV_Add(File, ItemID, "First Icon4")
					 Else                     ; Recording is finished
	                    TVChanged := TV_Add(File, ItemID, "First Icon2")
				  }
                  Else   ; If FileExt In txt,srt,sub,log
	                 TV_Changed := TV_Add(File, ItemID, "First Icon5")
                  If (Logging)
                     AddToBOF(LogFile, Prefix . " TreeView: Added " . Name . "`r`n")
               }
            }
         }
      }
      If (TVChanged) {
         GuiControlGet, TV, Pos, TVVar
         GuiControl, , SBVar, % "   " . TV_GetCount() . " objects."
         Gui, Show, AutoSize ;NoActivate
         GuiControl, +Redraw, TVVar
      }
      Else
         GuiControl, +Redraw, TVVar
      GuiControl, +gTVLabel, TVVar
   }
}
;-----------------------------------------------------------------------------------------------------------------------------------
; FindPath - Find (parts of) the path within the TreeView
FindPath(PathToFind, ByRef FoundPath) {
   FoundPath := ""
   If !(InStr(PathToFind, TVFolder) = 1) ; wrong parameters - PathToFind does not belong to the root folder
      Return 0
   FoundPath := TVFolder
   PathID := TVRootID
   Split := StrSplit(SubStr(PathToFind, StrLen(TVFolder) + 2), "\") ; split the part following the root folder
   ItemID := TV_GetChild(PathID)
   Loop, % Split.Length() {
      Name := Split[A_Index]
      While (ItemID) {
         TV_GetText(ItemText, ItemID)
         If (Name = ItemText) {
            PathID := ItemID
            FoundPath .= "\" . Name
            ItemID := TV_GetChild(ItemID)
            Break
         }
         ItemID := TV_GetNext(ItemID)
      }
   } Until (ItemID = 0)
   Return PathID
}
; ==================================================================================================================================
; Additional TV functions
; ==================================================================================================================================
TV_GetClientSize(HTV, ByRef W, ByRef H) { ; retrieves the client size of the TreeView
   W := H := 0
   , VarSetCapacity(RC, 16, 0)
   If DllCall("GetClientRect", "Ptr", HTV, "Ptr", &RC) {
      W := NumGet(RC, 8, "Int")
      , H := NumGet(RC, 12, "Int")
      Return True
   }
   Return False
}
TV_GetFirstVisible(HTV) { ; retrieves the first currently visible item of the TreeView
   Return DllCall("SendMessage", "Ptr", HTV, "UInt", 0x110A, "Ptr", 0x0005, "Ptr", 0, "UPtr") ; TVM_GETNEXTITEM, TVGN_FIRSTVISIBLE
}
TV_GetLastVisible(HTV) { ; retrieves the last visible item of the TreeView
   Return DllCall("SendMessage", "Ptr", HTV, "UInt", 0x110A, "Ptr", 0x000A, "Ptr", 0, "UPtr") ; TVM_GETNEXTITEM, TVGN_LASTVISIBLE
}
TV_GetPath(ItemID, Delimiter := "\") {
   TV_GetText(ItemPath, ItemID)
   While (ItemID := TV_GetParent(ItemID)) {
      TV_GetText(ItemText, ItemID)
      ItemPath := ItemText . Delimiter . ItemPath
   }
   Return ItemPath
}
TV_GetItemHeight(HTV) { ; retrieves the height af all TreeView items
   Return DllCall("SendMessage", "Ptr", HTV, "UInt", 0x111C, "Ptr", 0, "Ptr", 0) ; TVM_GETITEMHEIGHT
}
TV_GetItemRect(HTV, ItemID, ByRef X, ByRef Y, ByRef W, ByRef H, TextOnly := True) { ; retrieves the item's position and size
   X := 0, Y := 0, W := 0, H := 0
   , VarSetCapacity(RC, 16, 0)
   , NumPut(ItemID, RC, 0, "UPtr")
   If DllCall("SendMessage", "Ptr", HTV, "UInt", 0x1104, "Ptr", !!TextOnly, "Ptr", &RC) { ; TVM_GETITEMRECT
      X := NumGet(RC, 0, "Int")
      , Y := NumGet(RC, 4, "Int")
      , W := NumGet(RC, 8, "Int") - X
      , H := NumGet(RC, 12, "Int") - Y
      Return True
   }
   Return False
}
TV_HitTest(HTV, Test := 0x0006, X := "", Y := "") { ; TVM_HITTEST
   ; TVM_HITTEST = 0x1111 -> msdn.microsoft.com/en-us/library/bb773732(v=vs.85).aspx
   ; TVHT_NOWHERE            0x0001  - In the client area, but below the last item.
   ; TVHT_ONITEMICON         0x0002  - On the bitmap associated with an item.
   ; TVHT_ONITEMLABEL        0x0004  - On the label (string) associated with an item.
   ; TVHT_ONITEM             0x0006  - On the bitmap or label associated with an item (defined differently in CommCtrl.h).
   ; TVHT_ONITEMINDENT       0x0008  - In the indentation associated with an item.
   ; TVHT_ONITEMBUTTON       0x0010  - On the button associated with an item.
   ; TVHT_ONITEMRIGHT        0x0020  - In the area to the right of an item.
   ; TVHT_ONITEMSTATEICON    0x0040  - On the state icon for a tree-view item that is in a user-defined state.
   ; TVHT_ONITEMROW          0x007E  - On or right of an item (not defined in CommCtrl.h).
   ; TVHT_ABOVE              0x0100  - Above the client area.
   ; TVHT_BELOW              0x0200  - Below the client area.
   ; TVHT_TORIGHT            0x0400  - To the right of the client area.
   ; TVHT_TOLEFT             0x0800  - To the left of the client area.
   Test &= 0x0FFF
   VarSetCapacity(TVHTI, 8 + (A_PtrSize * 2), 0) ; TVHITTESTINFO
   If (X = "") || (Y = "") {
      DllCall("User32.dll\GetCursorPos", "Ptr", &TVHTI)
      , DllCall("User32.dll\ScreenToClient", "Ptr", HTV, "Ptr", &TVHTI)
   }
   Else {
      NumPut(X, TVHTI, 0, "Int")
      , NumPut(Y, TVHTI, 4, "Int")
   }
   If !(ItemID := DllCall("SendMessage", "Ptr", HTV, "UInt", 0x1111, "Ptr", 0, "Ptr", &TVHTI, "UPtr"))
      Return 0
   Return (NumGet(TVHTI, 8, "UInt") & Test ? ItemID : 0)
}
; Retrieves the rectangle of the item's text area in screen coordinates
TV_GetItemScreenRect(HTV, ItemID, Byref L := 0, ByRef T := 0, ByRef R := 0, ByRef B := 0) {
   ; TVM_GETITEMRECT = 0x1104 -> msdn.microsoft.com/en-us/library/bb773610(v=vs.85).aspx
   L := T := R := B := 0
   VarSetCapacity(RC, 16, 0)
   NumPut(ItemID, RC, 0, "Int")
   If DllCall("SendMessage", "Ptr", HTV, "UInt", 0x1104, "Ptr", True, "Ptr", &RC) { ; get the rectangle
      DllCall("MapWindowPoints", "Ptr", HTV, "Ptr", 0, "Ptr", &RC, "UInt", 2) ; convert client to screen coordinates
      L := NumGet(RC,  0, "Int")
      T := NumGet(RC,  4, "Int")
      R := NumGet(RC,  8, "Int")
      B := NumGet(RC, 12, "Int")
      Return True
   }
   Return False
}
TV_SetExplorerTheme(HTV, Option := "") {
   ; Options:
   ;     R  -  Remove the TVS_HASLINES style using lines to show the hierarchy of items.
   ;     F  -  Set the TVS_FULLROWSELECT style. The entire row of the selected item is highlighted, and clicking anywhere on an
   ;           item's row causes it to be selected. The TVS_HASLINES style will be removed, also.
   If (TV_OSVersion() > 5) {
      If (Option = "R") || (Option = "F") {
         Control, Style, -0x0002, , ahk_id %HTV%
         If (Option = "F")
            Control, Style, +0x1000, , ahk_id %HTV%
      }
      Return !DllCall("UxTheme.dll\SetWindowTheme", "Ptr", HTV, "WStr", "Explorer", "Ptr", 0)
   }
   Return False
}
TV_AutoHScroll(HTV, Set := True) {
   ; TVS_EX_AUTOHSCROLL = 0x0020
   Return TV_SetExStyle(HTV, 0x0020, Set ? 0x0020 : 0)
}
TV_DoubleBuffer(HTV, Set := True) {
   ; TVS_EX_DOUBLEBUFFER = 0x0004
   Return TV_SetExStyle(HTV, 0x0004, Set ? 0x0004 : 0)
}
TV_FadeInOutExpandos(HTV, Set := True) {
   ; TVS_EX_FADEINOUTEXPANDOS = 0x0040
   Return TV_SetExStyle(HTV, 0x0040, Set ? 0x0040 : 0)
}
TV_RichToolTips(HTV, Set := True) {
   ; TVS_EX_RICHTOOLTIP = 0x0010
   Return TV_SetExStyle(HTV, 0x0010, Set ? 0x0010 : 0)
}
TV_SetExStyle(HTV, StyleMask, Style) {
   ; TVM_SETEXTENDEDSTYLE = 0x0112
   If (TV_OSVersion() > 5) {
      ;SendMessage, 0x112C, %StyleMask%, %Style%, , ahk_id %HTV% ; Old code
	  Return DllCall("SendMessage", "Ptr", HTV, "UInt", 0x112C, "Ptr", StyleMask, "Ptr", Style) ; New DLL call
      Return (ErrorLevel = "FAIL" ?  0 : !ErrorLevel)
   }
   Return False
}
TV_OSVersion() {
   Static OSVersion := DllCall("GetVersion", "UChar")
   Return OSVersion
}
; ==================================================================================================================================
; Drag N Drop Functions
; ==================================================================================================================================
; IDropTarget user functions
; ==================================================================================================================================
; Gui2 tree view
; ----------------------------------------------------------------------------------------------------------------------------------
; ----------------------------------------------------------------------------------------------------------------------------------
IDropTargetOnDrop_TV(TargetObject, pDataObj, KeyState, X, Y, DropEffect) {
   Static FO := [2, 1]
   SetTimer, TVDragDropTimer, Off
   ; DropEffect := CheckDropEffect(KeyState, DropEffect)
   DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0008, "Ptr", 0) ; TVM_SELECTITEM, TVIS_DROPHILITED
   ; ToolTip, DropEffect = %DropEffect%
   ; Support drag & drop.
   Gui, Gui2:Default
   FolderPath := ""
   If (ItemID := TV_HitTest(TVHWND)) { ; dropped on an item
      FolderPath := TV_GetPath(ItemID)
      If !InStr(FileExist(FolderPath), "d") ; dropped onto a file, so we either have use the parent folder or ignore the event
         SplitPath, FolderPath, , FolderPath
   }
   Else
       FolderPath := TVFolder ; super-global TVFolder has been set to the root folder's name when the TreeView was created
   ; MsgBox, 0, %A_ThisFunc%, %FolderPath% ; for testing
   ; IDataObject_SHFileOperation will use CF_HDROP directly
   ; ; FO_MOVE = 1, FO_COPY = 2, so we have to swap the DropEffect (Copy = 1, Move = 2)
   IDataObject_SHFileOperation(pDataObj, FolderPath, DropEffect ^ 3, TargetObject.HWND)
   GuiControl, +gTVLabel, %TVHWND%
   Return DropEffect
}
; ----------------------------------------------------------------------------------------------------------------------------------
IDropTargetOnEnter_TV(TargetObject, pDataObj, KeyState, X, Y, DropEffect) {
   If (DropEffect) {
      If IDataObject_GetDroppedFiles(pDataObj, DroppedFiles) {
         DragFile := DroppedFiles[1]
         SplitPath, DragFile, , , , , DragDrive
         SplitPath, TVFolder, , , , , TVDrive
         If (DragDrive = TVDrive)
            DropEffect := 2
      }
      TVDropID := 0
      TVLastID := TV_GetLastVisible(TVHWND)
      TV_GetClientSize(TVHWND, TVClientW, TVClientH)
      TVItemHeight := TV_GetItemHeight(TVHWND) ;;;;from old TVDragDrop label
      GuiControl, -g, %TVHWND%
      SetTimer, TVDragDropTimer, 10
   }
   Return DropEffect
}
IDropTargetOnEnter_RB(TargetObject, pDataObj, KeyState, X, Y, DropEffect) {
   Return 2
}
; ----------------------------------------------------------------------------------------------------------------------------------
; IDropTargetOnOver_TV(TargetObject, KeyState, X, Y, DropEffect) {
;    Return DropEffect
; }
IDropTargetOnOver_RB(TargetObject, KeyState, X, Y, DropEffect) {
   Return 2
}
; ----------------------------------------------------------------------------------------------------------------------------------
IDropTargetOnLeave_TV(TargetObject) {
   SetTimer, TVDragDropTimer, Off
   DllCall("SendMessage", "Ptr", TVHWND, "UInt", 0x110B, "Ptr", 0x0008, "Ptr", 0) ; TVM_SELECTITEM, TVIS_DROPHILITED
   GuiControl, +gTVLabel, %TVHWND%
   Return 0 ; S_OK
}
; ----------------------------------------------------------------------------------------------------------------------------------
; Gui1 buttons
; ----------------------------------------------------------------------------------------------------------------------------------
IDropTargetOnDrop_BTN(TargetObject, pDataObj, KeyState, X, Y, DropEffect) {
   Global ERFolder, NMFolder, RSFolder, SRFolder, BUFolder
   ; Support drag & drop.
   GuiControlGet, BtnName, Name, % TargetObject.HWND
   FolderPath := %BtnName%Folder
   ; MsgBox, 0, %A_ThisFunc%, %FolderPath% ; for testing
   ; IDataObject_SHFileOperation will use CF_HDROP directly
   ; ; FO_MOVE = 1, FO_COPY = 2, so we have to swap the DropEffect (Copy = 1, Move = 2)
   IDataObject_SHFileOperation(pDataObj, FolderPath, DropEffect ^ 3, TargetObject.HWND)
}
; ----------------------------------------------------------------------------------------------------------------------------------
IDropTargetOnEnter_BTN(TargetObject, pDataObj, KeyState, X, Y, DropEffect) {
   Global ERFolder, NMFolder, RSFolder, SRFolder, BUFolder
   If (DropEffect) {
      If IDataObject_GetDroppedFiles(pDataObj, DroppedFiles) {
         DragFile := DroppedFiles[1]
         GuiControlGet, BtnName, Name, % TargetObject.HWND
         BtnFolder := %BtnName%Folder
         SplitPath, DragFile, , , , , DragDrive
         SplitPath, BtnFolder, , , , , BtnDrive
         If (DragDrive = BtnDrive)
            DropEffect := 2
		 Else
		    DropEffect := 2
      }
   }
   Return DropEffect
}
; ----------------------------------------------------------------------------------------------------------------------------------
IDropTargetOnDrop_RB(TargetObject, pDataObj, KeyState, X, Y, DropEffect) {
   If IDataObject_GetDroppedFiles(pDataObj, DroppedFiles) {
      For Each, FilePath In DroppedFiles
          FileRecycle, %FilePath%
         ; Msg .= FilePath . "`r`n" ; for testing
      ; ToolTip, %A_ThisFunc%:`r`n%Msg% ; for testing
      ; Return 2
   }
   Return 0
}
; ==================================================================================================================================
; Drag-and-drop includes
; ==================================================================================================================================
#Include *i DoDragDrop.ahk
#Include *i IDropTarget.ahk

To those reading: this isn't complete. IDataSource needs to be implemented to move arbitrary things. Currently this is limited to what's on the clipboard (I test by Ctrl-Cing a file before running Untitled.ahk). Also, currently, with IDragSourceHelper, you get this:
Image

With Drag & Drop Images and Drop Descriptions for MFC Applications, you get this:
Image

The CodeProject project has code for setting up a DROPDESCRIPTION and for also using the new cursors. This is far as I'll go because:
  • I don't write GUI AHK programs, so I'm already at a disadvantage for best practices etc., especially when compared to the contributors of this thread alone
  • Writing easily reusable code isn't my jam, either...
  • just me actually appears to know how the clipboard stuff works, unlike me
  • I'm hella sleep deprived
  • I really want to watch this week's Orphan Black
Last edited by qwerty12 on 28 May 2016, 08:30, edited 2 times in total.
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: [COM] Help with the IDropSource and IDropTarget interfaces

28 May 2016, 05:41

Wow, this is great stuff :D Very impressing qwerty12 :mrgreen:

Best regards
zcooler
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: [COM] Help with the IDropSource and IDropTarget interfaces

09 Feb 2017, 11:37

Hi qwerty12!

I had hopes Just Me would jump on this and finish off the nicest AHK D&D interface up to date, but he doesnt seem to be motivated to do so. This COM stuff is something of a beast, makes my head spinn of dizyness and I get nowhere. Few folks seem to understand this stuff and since you are one of those I wanna hear with ya if you might be interested to have an another go at it?

To clarify for others that might wanna have a go, your alterations of Just Me`s interface works if Ctrl-C (Copy to clipboard) the file in the Explorer and then drag the same file from the AHK app. Then the dragimage pops-up alright. Although its the file icon showing instead of (if a movie file) the thumbnail. Also getting the DROPDESCRIPTION and the new cursors working along with the little counter image (white digit on a blue background) that pops-up if there are multiple files being D&D would be fantastic.

Regards
zcooler
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: [COM] Help with the IDropSource and IDropTarget interfaces

09 Feb 2017, 13:58

Hey zcooler,

I have to be honest here; GUI stuff doesn't interest me, which is why this isn't really holding my attention. I don't know the first thing about making a GUI in AHK and that suits me because I don't have a need for GUI scripts personally.
zcooler wrote:Few folks seem to understand this stuff and since you are one of those I wanna hear with ya if you might be interested to have an another go at it?
I'm not one of them, either. I knew nothing of COM before AutoHotkey, and even then what I know even now are only the basics. Another issue is that I'm not a programmer, so I'm really not good at producing reusable code. Tell you what, a personal project of mine is to write a simple shell extension in C (can't stand C++, ATL or MFC and all that bollox). If I finish that, I'll try to have a go at this as then I'll be in a far better state to understand more of what's going on. But, still, asking somebody other than me would be your best course of action, so I'll happily sit aside if someone else who has a penchant for seeing pretty images with their drag operations wants to do it.
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: [COM] Help with the IDropSource and IDropTarget interfaces

09 Feb 2017, 15:08

Hey qwerty12,
Thanks for the swift and honest answer. I understand your position, Im in it myself ;) Its not mainly about pretty images, but more about the tiny AHK drag&drop cursors and lack of dropdescription which makes it hard to even visually detect if a Drag operation is carried through at all in a styled TreeView GUI. Well, this was unfortunate, the D&D interface seem to remain unfinished.

:wave:
just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [COM] Help with the IDropSource and IDropTarget interfaces

12 Feb 2017, 05:18

Hi querty12,

I missed your post from May 2016 but now noticed your last answer. Sorry, and thanks for your contribution!

Now I tried your changes in my test environment with no success at first. Then I noticed the key phrase:
IDataSource needs to be implemented to move arbitrary things. Currently this is limited to what's on the clipboard (I test by Ctrl-Cing a file before running Untitled.ahk).
And after copying a file to the clipboard in the shell the drag image was shown.

After analyzing your changes I found that even my original code (at least my local version) actually shows the drag image on suitable applications. The E_FAIL returned by IDragSourceHelper.InitializeFromBitmap seems to be caused by the limits of the data object returned from OleGetClipboard().

Some remarks:
  • IDropSource VTBL:
    S_FAIL was being returned because the IDropSource's vtable wasn't being properly created.
    The way I did it might look strange, but it is actually working when used like I do it in DoDragDrop().
  • IDropSource_Free():
    Assuming VTBL is kept static (which I'm doing because of possible scope issues, and the freeing ones that arise from that), this wouldn't work well if multiple IDropSources are present.
    I agree that 'freeing' the VTBL is useless. But it's rather impractical to have more than one IDropSource which would mean more than one DoDragDrop() running at the same time in AHK v1.1. ;)
  • IDropSource_QueryInterface():
    This method is'nt called for IID_IUnknown and even not for IID_IDropSource in this scenario.
GUI stuff doesn't interest me, which is why this isn't really holding my attention.
I'm not really interested in the 'visual effects' provided by IDragSourceHelper. I just wanted to know why it wasn't working. IDropSource seems to be working with suitable applications. When used by an AHK script, everyone will know what he's currently dragging.

Thanks again,
just me
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: [COM] Help with the IDropSource and IDropTarget interfaces

12 Feb 2017, 14:01

Hi just me,
just me wrote:I missed your post from May 2016 but now noticed your last answer. Sorry, and thanks for your contribution!
Thanks :-) But thanks to you for the immense work you put into this (the only experience I have with this sort of thing is my quick implementation of the IMMNotificationClient interface - I know I wouldn't have been able to come up with something as clean as this)
After analyzing your changes I found that even my original code (at least my local version) actually shows the drag image on suitable applications. The E_FAIL returned by IDragSourceHelper.InitializeFromBitmap seems to be caused by the limits of the data object returned from OleGetClipboard().
OK, that's good, my changes weren't much and, it seems, unnecessary. :| Despite what I told zcooler, I did actually spend a couple of nights again on this to no avail...
The way I did it might look strange, but it is actually working when used like I do it in DoDragDrop()
You're absolutely right. Back then, I was thinking in one dimension: you create one struct for the vtable and another struct for the object itself, which holds just the pointer to the vtable. This time, when I downloaded all the code again (from your Git repository, this post, zcooler's demo and my changes), I realised now (with a bit more experience - I'm really not a programmer! :-)) after looking over the differences at every step of doing that the superior method you were using to construct the vtable and actually kept your code the same instead of changing it:
  • One struct is used to hold both the vtable and the pointer to the vtable that would ordinarily, by convention, be in a separate struct. But the code that gets a pointer to the COM object isn't going to look beyond the beginning of the struct, reading at most A_PtrSize bytes for the vtable pointer. Said vtable pointer points to the first COM function, like usual
  • (Methods.Length() + 2) confused me - I mean, I understood why +1 would be needed for the extra pointer, but an additional +1 atop that and zeroing of the struct (when it was going to get filled with pointers right after that) seemed unneeded. So I changed it, watched AutoHotkey crash sometimes after I dragged a file and then when I looked at IDropSource_Free(), I understood everything :oops:
I agree that 'freeing' the VTBL is useless. But it's rather impractical to have more than one IDropSource which would mean more than one DoDragDrop() running at the same time in AHK v1.1. ;)
Sorry, I was talking rubbish. In My Attempt Mk2 I got rid of that inane comment and kept your code there the same.
This method is'nt called for IID_IUnknown and even not for IID_IDropSource in this scenario.
Thanks. I still ended up changing that one blindly. I should've checked, but I just wanted to be sure, I guess, that it wasn't the little things :\
I'm not really interested in the 'visual effects' provided by IDragSourceHelper. I just wanted to know why it wasn't working. IDropSource seems to be working with suitable applications. When used by an AHK script, everyone will know what he's currently dragging.
Does this mean you're working on this again? I'm evidently not the right person for this and zcooler will be happy to hear that :-)

Best regards,
qwerty12
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: [COM] Help with the IDropSource and IDropTarget interfaces

12 Feb 2017, 17:17

just me wrote:I'm not really interested in the 'visual effects' provided by IDragSourceHelper. I just wanted to know why it wasn't working. IDropSource seems to be working with suitable applications. When used by an AHK script, everyone will know what he's currently dragging.
Yes of course, everyone will know what he's currently dragging when used by an AHK script. In the AHK script im using the COM D&D interface with, sometimes the Drag operation never gets initialized at all and then it's hard to visually detect it with the default AHK cursor. The small square under the mouse cursor is the only sign when a Move Drag operation is initiated. That square easily gets masked by the underlying TV row. Like this:
2017-02-12_22-19-01.png
If sitting a few meters from the TV/Monitor (HTPC system) you ought to have quite sharp vision to even see it and often I have to drag the cursor to an empty part of the treeview to make sure the Drag operation has been initialized alright.
That is why I think the 'visual effects' provided by the IDragSourceHelper, with DropDescriptions, would silverline this already fantastic D&D interface.
qwerty12 wrote:I'm evidently not the right person for this and zcooler will be happy to hear that :-)
Not only me, the whole AHK community would be very happy to be using a D&D interface that completely measures up with the MS Shell counterpart ;)

:wave:
just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [COM] Help with the IDropSource and IDropTarget interfaces

13 Feb 2017, 04:41

Hi querty12,

don't worry. I didn't want to blame you. Though I am a programmer it's still very COMplicated stuff for me. I think that every further step would require a fully featured implementation of the IDataObject interface. That's still far beyond my skills.

Best regards!

just me
just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [COM] Help with the IDropSource and IDropTarget interfaces

13 Feb 2017, 05:02

Hi zcooler,

I know which application you are talking about. And I already tried to explain you why it's 'unstable' sometimes.

You cannot use a drag image or a drop description in this case. But it's easy to use user-defined cursors while dragging. I'll update the GitHub repository as soon as I'm ready. Might be better than nothing. ;)

Regards,

just me
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: [COM] Help with the IDropSource and IDropTarget interfaces

13 Feb 2017, 12:41

Greetings Oh Great One :D
just me wrote:I know which application you are talking about. And I already tried to explain you why it's 'unstable' sometimes.
Sorry Just me, our discussions about this script/app have been so plentyful I cannot remember em all. It was quite some time ago also. A very long break from AHK doesnt make things clearer either. Took up golfing instead ;) To not stray too much off topic I can only say its not unstable, more like a smaller quirk. Im aware how to aviod a drag operation initialization failure with my script now. Key is "slow dragging". To be clear (for others that are interested in using Just me`s COM D&D interface) this is an AHK related quirk in my script and has nothing to do with the COM D&D interface or the Demo I posted above.
just me wrote:You cannot use a drag image or a drop description in this case.
Yes that is obvious, no drag image or a drop description (upon pressing SHIFT or Ctrl when dragging within source window/folder as the Shell) when a drag operation initialization fails or are you referring to something else?
just me wrote:But it's easy to use user-defined cursors while dragging. I'll update the GitHub repository as soon as I'm ready. Might be better than nothing. ;)
That is fantastic news, Just me :mrgreen:

Best regards
zcooler
just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [COM] Help with the IDropSource and IDropTarget interfaces

14 Feb 2017, 10:45

Ok, updated package available on GitHub.
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: [COM] Help with the IDropSource and IDropTarget interfaces

14 Feb 2017, 11:42

Greetings Just me,
Wow, you move swift like the wind :mrgreen: Maybe adding user-defined cursors was just as easy as you were stating earlier...well easy for you, but really hard for the rest of us ;)

I kept the IDC_CROSS for DROPEFFECT_COPY which also is used on the DROPESCRIPTION in the Shell (to display adding/copy).
I exchanged the UpArrow to IDC_SIZEALL Four-pointed white arrow pointing north, south, east, and west for DROPEFFECT_MOVE operations. The Shell DROPESCRIPTION uses a black rightpointed arrow here, but that one wasnt available amongst the Win10 cursors. Although white cursors are preferrable cuz the contrast/visibility is better on top of black text.

Pretty nice additions indeed :clap: It is now easier to see the drag initialization even though I will keep on dreaming about those cool drag images displaying movie and series thumbnails ;) Maybe some day :lol:
Thank you, Just me :)

Best regards
zcooler
just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [COM] Help with the IDropSource and IDropTarget interfaces

14 Feb 2017, 16:17

It's not restricted to available system cursors. You also could create your own cursors and use (e.g.) LoadCursorFromFile().
just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [COM] Help with the IDropSource and IDropTarget interfaces

17 Mar 2017, 11:23

Well, I finally found a simple solution providing basic support for drag images with an AHK drop source on Win Vista+. It's a combination of SHDoDragDrop() - API and SHCreateDataObject() - Win Vista+.
SHDoDragDrop - API wrote:As of Windows Vista, if a drag image is not already stored in the data object pdtobj and a drag image cannot be obtained from the window specified by hwnd, the Shell provides a generic drag image. A drag image can fail to be obtained from the specified window either because hwnd is NULL or the specified window does not support the DI_GETDRAGIMAGE message.
But using it passing a data object retrieved by OleGetClipboard() it doesn't. Apparently, this data object lacks a sufficient implementation of IDataObject.SetData() needed to add a drag image.

On Win Vista+ SHCreateDataObject() is able to create the sufficient data object and to embed the contents of the data object returned by OleGetClipboard(). So SHDoDragDrop() - API is able to use a user-defined drag image or to create a default image on demand. There are three options:
  1. If you pass NULL in the HWND parameter or if the window/control specified by the passed HWND doesn't create a drag image a default (blank) drag image and a textual drop description will be used.
  2. If the windoe/control specified by the passed HWND creates a drag image (e.g. ListView and TreeView controls are able to do it), this drag image will be used in combination with a 'modern' drag cursor.
  3. If you pass a handle of a bitmap (HBITMAP) in DragImage SHDoDragDrop() - AHK tries to create an own drag image. On success, it will be used in combination with a 'modern' drag cursor.
I've added/updated the sources on GitHub.

Changes:
  • Added SHDataObject.ahk
  • Added SHDoDragDrop.ahk
  • Added SHDoDragDrop_sample.ahk
  • Changed IDataObject.ahk (added some auxiliary functions).
  • Changed IDropTarget.ahk to respect the 'preferred drop effect'
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: [COM] Help with the IDropSource and IDropTarget interfaces

17 Mar 2017, 13:53

OHHHH JEEEZ....a test blowout coming up during the weekend :dance:
zcooler
Posts: 455
Joined: 11 Jan 2014, 04:59

Re: [COM] Help with the IDropSource and IDropTarget interfaces

18 Mar 2017, 06:37

SWEET MOTHER OF GOD :shock: That is a huge breakthrough and you are fantastic, Just me :bravo: Truely ROCKSTAR level stuff :mrgreen:

In a broader D&D interface implementation perspective into my app it is no problem to retrieve the icon associated with the file which is usually used as the DragImage. However I have no idea how to retrieve thumbnail paths for media files programatically. The shell D&D interface do prioritize thumbnail dragimages before icons. Well, I will have to investigate that one since it is not within scope of your D&D interface.

Chockingly well done, Just me :superhappy: Praise the Just me :lol: ;)

Best regards
zcooler :wave:

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Google [Bot] and 71 guests