         title    Treeview

         .586
         .model   flat, STDCALL
         option   casemap: none   ; case sensitive

         include  Treeview.inc

         WinMain  PROTO

.const
szWndClassName  db       'Treeview',0
AppName         db       'Tree View',0
szTreeClassName db       'SysTreeView32',0   
szRootPath      db       'C:\',0
szAboutText     db       'Treeview of My Computer ',0ah,0ah
                db       'Ewayne Wagner',0ah
                db       'yooper@kalamazoo.net',0

.data?
pShellMalloc    LPMALLOC ?
hInst           dd       ?
hWndMain        dd       ?
iSeparatorPos   dd       ?
hWndTree        dd       ?
hTreeImageList  dd       ?

hMyComputer     dd       ?
hDrives         dd       27  dup(?)
szDrvLet        db       27  dup(?)

.data
Loaded          dd       0
ExpCol          dd       0

msg             MSG                  <?>
icc             INITCOMMONCONTROLSEX <?>

.code

;-----------------------------------------------------------------------
start:
      INVOKE     GetModuleHandle, 0h            
         mov     hInst, eax
         mov     icc.dwSize, sizeof INITCOMMONCONTROLSEX
         mov     icc.dwICC, ICC_LISTVIEW_CLASSES or ICC_TREEVIEW_CLASSES or ICC_TAB_CLASSES
      INVOKE     InitCommonControlsEx, addr icc

; initialize the Component Object Model(COM) library
      INVOKE     CoInitialize, 0
        test     eax, eax                ; any HRESULT with the MSB (the sign bit) = 1, is an error
          js     Exit

; Get global IMalloc object
      INVOKE     SHGetMalloc, addr pShellMalloc
         cmp     eax, E_FAIL
          jz     Shutdown

      INVOKE     WinMain    

; Release IMalloc Object
         mov     eax, pShellMalloc
         mov     eax, [eax]
      INVOKE     (IMalloc PTR [eax]).Release, pShellMalloc

Shutdown:    
; close the COM library
      INVOKE     CoUninitialize 

Exit:
      INVOKE     ExitProcess, eax

;-----------------------------------------------------------------------
WinMain proc
LOCAL    wc:WNDCLASSEX

         mov     wc.cbSize, sizeof WNDCLASSEX    
         mov     wc.style, CS_BYTEALIGNCLIENT or CS_BYTEALIGNWINDOW or CS_DBLCLKS
         mov     wc.lpfnWndProc, offset MainWndProc
         mov     wc.cbClsExtra, 0
         mov     wc.cbWndExtra, 0  
         mov     eax, hInst
         mov     wc.hInstance, eax
      INVOKE     LoadIcon, 0, IDI_APPLICATION
         mov     wc.hIcon, eax 
         mov     wc.hIconSm, eax
      INVOKE     LoadCursor, 0, IDC_SIZEWE
         mov     wc.hCursor, eax
         mov     wc.hbrBackground, COLOR_WINDOW
         mov     wc.lpszMenuName, MENUEX_1
         mov     wc.lpszClassName, offset szWndClassName
    invoke       RegisterClassEx, addr wc

;---------- [Center the window] ----------
      INVOKE     GetSystemMetrics, SM_CXSCREEN
         sub     eax, 350
         shr     eax, 1
        push     eax
      INVOKE     GetSystemMetrics, SM_CYSCREEN
         sub     eax, 400
         shr     eax, 1
         pop     ebx

      INVOKE     CreateWindowEx, WS_EX_ACCEPTFILES or WS_EX_APPWINDOW,\ 
                 addr szWndClassName, addr AppName,\
                 WS_OVERLAPPEDWINDOW or WS_VISIBLE or WS_CLIPCHILDREN or WS_CLIPSIBLINGS,\
                 ebx, eax, 350, 400, NULL, NULL, hInst, NULL
        test     eax, eax     
          jz     GetOut
         mov     hWndMain, eax

; Message Dispatch Loop        
      .while TRUE
         INVOKE     GetMessage, addr msg, NULL, 0, 0
         .break .if (!eax)
         INVOKE     TranslateMessage, addr msg
         INVOKE     DispatchMessage, addr msg
      .endw

GetOut:
         mov     eax, msg.wParam
         ret
WinMain endp

;-----------------------------------------------------------------------
MainWndProc proc hWnd:DWORD, uMsg, wParam, lParam
LOCAL    rect:RECT
LOCAL    lpsf:DWORD
LOCAL    sfi:SHFILEINFO

         mov     eax, uMsg
         cmp     eax, WM_NOTIFY
          je     wmnotify
         cmp     eax, WM_SIZE
          je     wmsize
         cmp     eax, WM_COMMAND
          je     wmcommand
         cmp     eax, WM_CREATE
          je     wmcreate
         cmp     eax, WM_DESTROY
          je     wmdestroy
      INVOKE     DefWindowProc, hWnd, uMsg, wParam, lParam
         jmp     return         

wmcommand:
         mov     eax, wParam
         and     eax, 0000FFFFh
      .if eax == IDM_QUIT
         INVOKE     PostQuitMessage, 0
      .elseif eax == IDM_ABOUT
         INVOKE     MessageBox, hWnd, addr szAboutText, addr AppName, MB_OK or MB_ICONQUESTION
      .endif     
         jmp     Ret0

; Main window is being resized
wmsize:  
         mov     eax, lParam
         mov     edx, eax
         and     eax, 0ffffh
         shr     edx, 16
      INVOKE     MoveWindow, hWndTree, 0, 0, eax, edx, TRUE
         jmp     Ret0

wmcreate:
; Initialize the tree view
      INVOKE     GetClientRect, hWnd, addr rect     
         mov     eax, rect.right
         shr     eax, 1
         dec     eax

      INVOKE     CreateWindowEx, WS_EX_CLIENTEDGE, addr szTreeClassName, 0,\
                 WS_CHILD or WS_VISIBLE or TVS_HASLINES or TVS_HASBUTTONS or\
                 TVS_LINESATROOT or WS_CLIPSIBLINGS,\
                 0, 0, eax, rect.bottom, hWnd, ID_TREEVIEW, hInst, NULL
        test     eax, eax     
          jz     Ret0
         mov     hWndTree, eax 

; Get the shell image list for the tree view
      INVOKE     SHGetFileInfo, addr szRootPath, 0, addr sfi, sizeof SHFILEINFO,\
                 SHGFI_SYSICONINDEX or SHGFI_SMALLICON
        test     eax, eax
          jz     Ret0
         mov     hTreeImageList, eax 
      INVOKE     SendMessage, hWndTree, TVM_SETIMAGELIST, 0, eax

      INVOKE     SHGetDesktopFolder, addr lpsf
        test     eax, eax
          js     Ret0

; Fill the window with the root items
      INVOKE     UpdateTreeView, hWndTree, lpsf, 0, TVI_ROOT

; Free the shell folder pointer
         mov     eax, lpsf
         mov     eax,[eax]
      INVOKE     (IShellFolder ptr [eax]).Release, lpsf
      INVOKE     SetFocus, hWndTree
      INVOKE     SendMessage, hWndTree, TVM_SELECTITEM, TVGN_CARET, hMyComputer
      INVOKE     SendMessage, hWndTree, TVM_EXPAND, TVE_EXPAND, hMyComputer
         mov     Loaded, 44
         jmp     Ret0   

wmdestroy:
      INVOKE     PostQuitMessage, NULL      
         jmp     Ret0

wmnotify:
      .if wParam == ID_TREEVIEW
         INVOKE     TreeViewNotify, hWnd, lParam
      .endif
         jmp     Ret0

Ret0:
         xor     eax, eax

return:
         ret
MainWndProc endp

;-----------------------------------------------------------------------
UpdateTreeView proc uses ebx edx hwnd:DWORD, pshfParent, pidlFull, htreeParent
LOCAL    peidl:LPENUMIDLIST
LOCAL    pidl:DWORD
LOCAL    pidlFullCur:DWORD
LOCAL    ptvobj:LPTV_OBJDATA
LOCAL    tvis:TV_INSERTSTRUCT
LOCAL    sfi:SHFILEINFO
LOCAL    strret:STRRET
LOCAL    szWork[64]:BYTE
LOCAL    FirstItem:DWORD, dwAttr, Cnt, hItemI

         mov     edx, pshfParent
         mov     edx, [edx]
      INVOKE     (IShellFolder PTR [edx]).EnumObjects, pshfParent, hwnd, SHCONTF_FOLDERS or\
                 SHCONTF_NONFOLDERS or SHCONTF_INCLUDEHIDDEN, addr peidl
        test     eax, eax
          js     RetError
         mov     FirstItem, 1
         and     Cnt, 0
         mov     byte ptr szDrvLet, 0

ObjectLoop:
         mov     edx, peidl
         mov     edx, [edx]

      INVOKE     (IEnumIDList PTR [edx]).Next, peidl, 1, addr pidl, NULL
        test     eax, eax
          js     RetError
         cmp     eax, S_FALSE    ;no more items
          jz     RetOk

;---------- [Check to see if its a folder; if its a folder, add it to the tree] ----------
         mov     dwAttr, SFGAO_HASSUBFOLDER or SFGAO_FOLDER
         mov     edx, pshfParent
         mov     edx, [edx]
      INVOKE     (IShellFolder PTR [edx]).GetAttributesOf, pshfParent, 1, addr pidl, addr dwAttr
        test     eax, eax
          js     RetError
      .if dwAttr & (SFGAO_HASSUBFOLDER or SFGAO_FOLDER)
         .if dwAttr & SFGAO_FOLDER
            .if dwAttr == 20000000h
                  jmp     ObjectLoop
            .endif

;---------- [Save TV_OBJDATA] ----------
               mov     eax, pShellMalloc
               mov     eax, [eax]
            INVOKE     (IMalloc ptr [eax]).Alloc, pShellMalloc, sizeof TV_OBJDATA
              test     eax, eax
                jz     RetError
               mov     ptvobj, eax
               mov     ebx, eax

            INVOKE     IDList_Combine, pidlFull, pidl
               mov     (TV_OBJDATA PTR [ebx]).pidlFull, eax
               mov     pidlFullCur, eax
            INVOKE     IDList_CopyItem, pidl
               mov     (TV_OBJDATA PTR [ebx]).pidlRel, eax
               mov     eax, pshfParent
               mov     (TV_OBJDATA PTR [ebx]).pshfParent, eax
               mov     tvis.item.lParam, ebx

;---------- [Save tree item data] ----------
               mov     eax, htreeParent
               mov     tvis.hParent, eax
               mov     tvis.item.hItem, 0  
               mov     tvis.hInsertAfter, TVI_LAST
               mov     tvis.item.imask, TVIF_TEXT or TVIF_IMAGE or TVIF_SELECTEDIMAGE or TVIF_PARAM
            .if dwAttr & SFGAO_HASSUBFOLDER
                  mov     tvis.item.cChildren, TRUE
                   or     tvis.item.imask, TVIF_CHILDREN                         
            .endif

            INVOKE     SHGetFileInfo, pidlFullCur, 0, addr sfi, sizeof SHFILEINFO,
                       SHGFI_PIDL or SHGFI_DISPLAYNAME ; SHGFI_TYPENAME causes a problem
              test     eax,eax
                jz     RetError
               mov     tvis.item.cchTextMax, 255
               lea     eax, sfi.szDisplayName
               mov     tvis.item.pszText, eax
            INVOKE     lstrlen, eax
               sub     eax, 2
               mov     ebx, tvis.item.pszText
               add     ebx, eax
               mov     eax, [ebx]
            .if ax != '):' && !FirstItem && !Loaded
                  jmp     ObjectLoop
             .endif

            INVOKE     GetIcon, pidlFullCur, SHGFI_ICON
               mov     tvis.item.iImage, eax
            INVOKE     GetIcon, pidlFullCur, SHGFI_OPENICON 
               mov     tvis.item.iSelectedImage, eax

;---------- [Add item to the tree] ----------
            INVOKE     SendMessage, hwnd, TVM_INSERTITEM, 0, addr tvis
              test     eax, eax
                jz     RetError
               mov     hItemI, eax
            .if FirstItem == TRUE
                  mov     hMyComputer, eax
                  mov     FirstItem, 0
            .else

;---------- [Build the the drive list and drive handles] ----------
               INVOKE     lstrcpy, addr szWork, tvis.item.pszText
               INVOKE     lstrlen, addr szWork
                  sub     eax, 3
                  lea     ebx, szWork
                  add     ebx, eax
               .if word ptr [ebx+1] == '):'
                  INVOKE     lstrcpyn, addr szWork, ebx, 2
                     add     szWork, 32                             ; Convert to lower case
                  INVOKE     lstrcat, addr szDrvLet, addr szWork    ; szDrvLet = drive letters
                     .if byte ptr szWork > 96 && byte ptr szWork < 123  ; All drives
                           mov     ecx, hItemI
                           mov     eax, Cnt
                           dec     eax
                           mov     hDrives+[eax*4], ecx             ; Drive handles
                           inc     Cnt
                  .endif
               .endif
            .endif
         .endif
      .endif
         mov     eax, pShellMalloc
         mov     eax, [eax]
      INVOKE     (IMalloc PTR [eax]).Free, pShellMalloc, pidl
         jmp     ObjectLoop

;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
;RetOk:
         mov     eax, peidl
         mov     eax, [eax]
      INVOKE     (IEnumIDList PTR [eax]).Release, peidl

RetOk:
      INVOKE     lstrcat, addr szDrvLet, 0
         ret

RetError:
; Do Message
         ret
UpdateTreeView endp

;-----------------------------------------------------------------------
TreeViewNotify proc uses ebx edx hWnd:DWORD, pnmtv
LOCAL    pshf:DWORD
LOCAL    pidl:DWORD
LOCAL    ptvobj:LPTV_OBJDATA
LOCAL    strret:STRRET
LOCAL    tvhti:TV_HITTESTINFO
LOCAL    tvi:TV_ITEM
LOCAL    pt:POINT

         mov     eax, pnmtv
         mov     ebx, (NM_TREEVIEW ptr [eax]).itemNew.lParam 
         mov     ptvobj, ebx
         mov     eax, (NM_TREEVIEW ptr [eax]).hdr.code
         cmp     eax, TVN_ITEMEXPANDING
          je     item_expanding
         cmp     eax, TVN_SELCHANGEDA
          je     sel_changed
         jmp     RetOk

; Item is expanding...  
item_expanding:

         mov     ebx, pnmtv
        push     (NM_TREEVIEW ptr [ebx]).action
         pop     ExpCol

        test     (NM_TREEVIEW ptr [ebx]).itemNew.state, TVIS_EXPANDEDONCE 
         jnz     RetOk

         mov     ebx, ptvobj
         mov     edx, (TV_OBJDATA ptr [ebx]).pshfParent
         mov     edx, [edx]
      INVOKE     (IShellFolder PTR [edx]).BindToObject, (TV_OBJDATA ptr [ebx]).pshfParent, 
                 (TV_OBJDATA ptr [ebx]).pidlRel, 0, addr IID_IShellFolder, addr pshf 
        test     eax, eax
          js     RetError

         mov     ebx, pnmtv
         mov     edx, ptvobj
      INVOKE     UpdateTreeView, hWndTree, pshf, (TV_OBJDATA ptr [edx]).pidlFull,\
                 (NM_TREEVIEW ptr [ebx]).itemNew.hItem

      .if Loaded
         INVOKE     SendMessage, hWndTree, TVM_SORTCHILDREN, 0, (NM_TREEVIEW ptr [ebx]).itemNew.hItem
      .endif

         mov     eax, pshf
         mov     eax, [eax]
      INVOKE     (IShellFolder ptr [eax]).Release, pshf
         jmp     RetOk              

sel_changed:
; update list view header to give full path name
      INVOKE     SHGetDesktopFolder, addr pshf
        test     eax, eax
          js     RetError
         mov     ebx, ptvobj
         mov     edx, pshf
         mov     edx, [edx]
      INVOKE     (IShellFolder PTR [edx]).GetDisplayNameOf, pshf,\
                 (TV_OBJDATA ptr [ebx]).pidlFull, SHGDN_NORMAL, addr strret
        test     eax, eax
          js     RetError
      .if strret.uType == STRRET_CSTR
            lea     eax, strret.cstr
      .elseif strret.uType == STRRET_OFFSET
            mov     eax, (TV_OBJDATA ptr [ebx]).pidlFull
            add     eax, strret.uOffset
      .elseif strret.uType == STRRET_WSTR     
            mov     eax, strret.pOleStr
      .endif

; Display the Path
      .if Loaded && ExpCol != 1
         ;INVOKE     MessageBox, NULL, eax, addr AppName, MB_OK
         INVOKE     SendMessage, hWndMain, WM_SETTEXT, 0, eax
      .endif
         and     ExpCol, 0

         mov     eax, pshf
         mov     eax, [eax]
      INVOKE     (IShellFolder ptr [eax]).Release, pshf
   jmp     RetOk

RetOk:
         mov     eax, TRUE
         ret

RetError:
         xor     eax, eax
         ret
TreeViewNotify endp

;-----------------------------------------------------------------------
IDList_GetSize proc uses ebx pidl:DWORD
LOCAL    cbTotal:DWORD

         mov     cbTotal, 0
      .if pidl != 0
            mov     ebx, pidl

StartLoop:
          movsx     eax, (ITEMIDLIST PTR [ebx]).mkid.cb
           test     eax, eax
             jz     EndLoop
            add     cbTotal, eax
            add     ebx, eax
            jmp     StartLoop

EndLoop:
      .endif
         mov     eax, cbTotal
         ret
IDList_GetSize endp

;-----------------------------------------------------------------------
IDList_CopyItem  proc uses ebx edx ecx edi esi pidl:DWORD
LOCAL    pidlNew:DWORD
LOCAL    sz:DWORD
      
         mov     ebx, pidl 
        movsx    edx, (ITEMIDLIST PTR [ebx]).mkid.cb
         add     edx, sizeof WORD
         mov     sz, edx
        
         mov     edx, pShellMalloc
         mov     edx, [edx]
      INVOKE     (IMalloc PTR [edx]).Alloc, pShellMalloc, sz
      
        test     eax, eax
          jz     Ret0
         mov     pidlNew, eax

         mov     esi, pidl
         mov     edi, pidlNew
         mov     ecx, sz
      .while (ecx)
            mov     al, byte ptr [esi]
            mov     byte ptr [edi], al
            dec     ecx
            inc     esi
            inc     edi
      .endw
      
RetOk: 
         mov     eax, pidlNew
         ret
      
Ret0:
         xor          eax, eax
         ret        
IDList_CopyItem  endp

;-----------------------------------------------------------------------
IDList_Combine proc uses esi edi ecx edx pidl1:DWORD, pidl2
LOCAL    pidlNew:DWORD
LOCAL    cb1:DWORD
LOCAL    cb2:DWORD
      
; Get length of 1st pidl (not including NULL terminating word)
      INVOKE     IDList_GetSize, pidl1
         mov     cb1, eax
      
; Get length of 2nd pidl (not including NULL terminating word)
      INVOKE     IDList_GetSize, pidl2
         mov     cb2, eax
      
; Get total length of resulting combined pidl (including NULL terminating word)
         mov     eax, sizeof ITEMIDLIST.mkid.cb
         add     eax, cb1
         add     eax, cb2
        
; Allocate the memory
         mov     edx, pShellMalloc
         mov     edx, [edx]
      INVOKE     (IMalloc PTR [edx]).Alloc, pShellMalloc, eax
      
        test     eax, eax
          jz     RetError
         mov     pidlNew, eax
         mov     edi, eax
      
; Write 1st pidl to destination (pidlNew)
         mov     esi, pidl1
         mov     ecx, cb1
      .while (ecx)
            mov     al, byte ptr [esi]
            mov     byte ptr [edi], al
            dec     ecx
            inc     esi
            inc     edi
      .endw

; Write 2nd pidl after 1st
         mov     esi, pidl2
         mov     ecx, cb2
      .while (ecx)
            mov     al, byte ptr [esi]
            mov     byte ptr [edi], al
            dec     ecx
            inc     esi
            inc     edi
      .endw
      
; Add terminating word
         mov     word ptr [edi], 0
        
         mov     eax, pidlNew
         ret

RetError:
         int     3
         xor     eax, eax
         ret        
IDList_Combine   endp

;-----------------------------------------------------------------------
; dwType = SHGFI_OPENICON or SHGFI_ICON
GetIcon proc uses edx pidl:DWORD, dwType
LOCAL    shfi:SHFILEINFO
        
         mov     edx, SHGFI_PIDL or SHGFI_SYSICONINDEX or SHGFI_SMALLICON
          or     edx, dwType

; SHGetfileinfo requires full pidl
      INVOKE     SHGetFileInfo, pidl, 0, addr shfi, sizeof SHFILEINFO, edx
         mov     eax, shfi.iIcon
         ret
GetIcon endp

end start
