Announcement

Collapse

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

Treeview/Listview Select Files and Folders

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Treeview/Listview Select Files and Folders

    this is something i have been playing around with for a while.

    please direct all comments, suggestions and flames to the following powerbasic for windows topic:

    http://www.powerbasic.com/support/pb...ad.php?t=10918

    note this is a work in progress, but i am going to be busy for a few weeks and wanted to get some feedback.

    ------------------


    [this message has been edited by stuart sanders (edited august 21, 2003).]
    Stuart Sanders
    stuart at bitshk dot com
    www.bitshk.com

    #2
    Code:
    ' sffModal.bas
    '
    ' This is a test example for the sff library routines
    ' This will save any selected files/directories to an ini file and then
    ' reload and preselect them next time it runs.
    '----------------------------------------------------------------------------
    
    #Compile Exe
    '#Tools Off
    '#Debug Error On
    #Register None
    #Include "win32api.inc"
    #Include "COMMCTRL.INC"
    #Include "libSFF.inc"
    
    '----------------------------------------------------------------------------
    
    Function PbMain()
        Local lngRet As Long
    	Local strIniFile As ASCIIZ * %MAX_PATH
    	Local strRet As ASCIIZ * 32767
    
    	'Must fill in these settings before calling the SFF...see constant 
    	'declarations for other options
    
    	'directory attribute mask
    	'options are %HIDDEN, %SYSTEM, %SUBDIR
    	SFF_gudtSettings.dwdDirAttributeMask = %HIDDEN Or %SYSTEM Or %SUBDIR
    
    	'drive types:
    	'SFF_gudtSettings.dwdDriveTypes = %SFF_DriveType_AllExistingDrives	'all drives
    	SFF_gudtSettings.dwdDriveTypes = %SFF_DriveType_Fixed				'fixed drives
    
    	'allowed file attributes
    	SFF_gudtSettings.dwdFileAttributeMask = %HIDDEN Or %SYSTEM
    
    	'which drives to display (letters)
    	SFF_gudtSettings.strDriveString = Chr$(67 To 90)		
    	'Optionally supply the drives letters you wish to allow, leave blank to allow all
    
    	'file mask - not currently used
    	'SFF_gudtSettings.strFileMatchMask = "*|*.bas"		
    	'can be a pipe delimited list of masks
    
    	'get ini file name
    	GetModuleFilename ByVal %NULL, strIniFile, SizeOf(strIniFile)
    	Replace ".exe" With ".ini" In strIniFile
    
    	lngRet = GetPrivateProfileString("Application", "SelectedFiles","",strRet,SizeOf(strRet),strIniFile)
    	If lngRet > 0 Then
    		SFF_gstrPreselected = Left$(strRet, lngRet)
    	Else
    		SFF_gstrPreselected = ""
    	End If
    
    	SFF_dlgMain( %HWND_DESKTOP, _
    	  "Select Files and Folders", _
    	  "Please select the files and/or directory you wish to use and click Accept," _
    	  & $CR & "Click on the Close button to exit without selecting anything" )
    
    	'save settings
    	If SFF_gstrReturn <> Chr$(0) Then
        	lngRet = WritePrivateProfileString (ByCopy "Application", _
        	ByCopy "SelectedFiles", ByCopy SFF_gstrReturn, ByCopy strIniFile)
    	End If
    End Function
    ------------------
    Stuart Sanders
    stuart at bitshk dot com
    www.bitshk.com

    Comment


      #3
      this is multipart. you will need to cut and paste both pieces, or download the source and the compiled example from:

      http://www.bitshk.com/files/sff.zip

      [CODE]
      ' libsff.inc
      '
      ' select multiple files and folders
      '
      ' this is the include file.
      '
      ' warning: this is a work in progress!
      '
      ' pb forums publish history
      ' version 0 - 21 aug 2003
      '
      '----------------------------------------------------------------------------(')
      '
      ' features include:
      ' (some of these remain unchanged from libbff)
      '
      ' - proper file and directory icons from windows system
      ' - custom icons for check state for both treeview and listview
      ' - tri-state check for the directory treeview
      ' - directory treeview loaded as user needs for better performance
      ' - use of global array to store selected files and directories until displayed
      ' - use of separate worker threads to fill in file and directory information
      ' - allows masking based on file and directory attributes (read only, hidden, system)
      ' - fully customizable title and message
      ' - fully customizable drive type selections
      '
      ' and as george bleck so artfully said:
      ' - fully customizable everything as you can change the code
      '
      '--------------------------------------------------------------------------------
      ' license and disclaimer:
      '
      ' this code hereby released into the public domain by stuart sanders (aug 2003).
      ' by doing so i remove any and all restrictions on usage. though it would be
      ' appreciated if you don't claim this as being your own work.
      '
      ' by downloading or using this code you absolve the author of any and all
      ' responsibility regarding fitness of use, incidental, special or consequential
      ' damages, including but not limited to loss of profit or goodwill. in other
      ' words use at your own risk, or don't use it.
      '
      ' that out of the way, any and all feedback welcome, and given the time and
      ' inclination i will try to evolve this into something more compact,
      ' reliable and fast. feed back can be made the via powerbasic forums or to
      ' stuart at bitshk dot com.
      '
      ' of course if you love this and want to send me a free copy of your killer app
      ' that makes use of it i'd be happy to accept
      '
      '--------------------------------------------------------------------------------
      ' specifically used code samples and thanks go to:
      '
      ' initial frame work from which i extended:
      ' george bleck - http://www.powerbasic.com/support/pb...ad.php?t=23778
      '
      ' other treeview and listview code
      ' steve mc gregor - http://www.powerbasic.com/support/pb...ad.php?t=23042
      ' erik christensen - http://www.powerbasic.com/support/pb...ad.php?t=23218
      ' gregery d engle - http://www.powerbasic.com/support/pb...ad.php?t=23003
      ' dominic mitchell - http://www.powerbasic.com/support/pb...ead.php?t=7531
      '
      ' storage of icons in source
      ' george bleck - as above from which i hunted for and found binbas:
      ' edwin knoppert - for binbas program to convert icon to asm statements
      '
      ' other
      ' chris carroll for non-forum help, support and direction with general pb issues
      '
      ' finally
      ' erik chistensen said that i should include a thankyou to semen matusovski as his
      ' code was heavily inspired by code provided by semen
      '
      '--------------------------------------------------------------------------------
      ' compatibility:
      '
      ' this was written using:
      ' - pbwin 7.02
      ' - win32api.inc (18 june 2003)
      ' - commctrl.inc (18 june 2003)
      ' - windows 2000 sp3
      '
      ' i don't have pb win 6, so i am only guessing, but i believe the only non pb 6
      ' pb commands i use are parse$ and join$. these should be easy enough to write
      ' replacements for and i can add a compile time option for pb6 if someone provides
      ' the code.
      '
      ' if this doesn't compile for you and your version of pbwin, the pb win32api.inc
      ' and the commctrl.inc are not the same as above, i suggest you investigate
      ' any known compatibility issues before contacting me. it might also be better
      ' to use the forums at www.powerbasic.com as there are other people, many of
      ' whom have been using powerbasic longer than me.
      '
      '--------------------------------------------------------------------------------
      ' tested platforms:
      ' - windows 2000 workstation sp3
      '
      '--------------------------------------------------------------------------------
      ' to do:
      '
      ' possible functionality:
      ' - might be more efficient to include a option to store items as exclusions to a
      ' fully selecred directory. this way an entire file list in a directory doesn't
      ' need to be added to the array just because 1 file is unchecked.
      ' - insert listview items with state, rather than changing state after insertion.
      ' - intercept listview set check state by keyboard.
      ' - when selecting/unselecting a file, it is possible that the parent directory state
      ' needs to cleared or fully selected. so the child directories under the parent
      ' must be checked to see whether their status impacts the change. if the parent
      ' has not been expanded, then the simplest solution is to force the app to expand
      ' the node and fill in directories and status. probably not fastest solution. an
      ' alternative would be to check the directory/selected array and return the status
      ' of all child directories (all checked/all unchecked/mixed - greyed)
      ' - with a large array of selected items, displaying the state may significantly slow.
      ' using a sorted array and modified routines to use the sorting may speed things.
      ' - review criticalsection additions to ensure implementation is ok.
      '
      '
      ' known bugs/problems:
      ' - if tv add thread closes before completion, it currently just collapses and deletes
      ' child nodes. this needs to close under a controlled environment so that anything
      ' that needs to be reinserted into the selected array is done so cleanly.
      '
      ' testing:
      ' - pb6 (parse/join replacement functions, compile time option?)
      ' - platform testing on non-win2k
      '
      '
      '--------------------------------------------------------------------------------
      ' general notes
      '
      ' the objective when i started out with this was to explore george's bff code
      ' as a way to familiarise myself with pb, because although i'd had it for a
      ' few months i hadn't really done anything with it due to time. since the file
      ' system was one area i wanted to explore this seemed a interesting place to
      ' start.
      '
      ' i fully realise that a lot if not all of this code is not optimised. it was
      ' a project of exploration into pb and some aspects of the windows api. and things
      ' have been tacked on as bugs occured or as i thought of them. the full logic of
      ' the treeview/listview interaction needs to be re-examined, and at some stage i
      ' fully intend doing so, just not right now. this will give me some time to
      ' delve into other others of windows and pb and i can bring back my experience
      ' to this (or whatever results from it) at a future date.
      '
      '--------------------------------------------------------------------------------
      ' calling
      '
      ' this version provides two methods of calling.
      '
      ' the first method method is as a standalone modal dialog that is self contained.
      ' you pass the paramters stating how and what to display and when the user clicks
      ' accept it populates a delimited global string with the selected files. see the
      ' sample file sffmodal.bas for a working example.
      '
      ' sff_gudtsettings.dwddirattributemask = %hidden or %system or %subdir
      ' sff_gudtsettings.dwddrivetypes = %sff_drivetype_fixed
      ' sff_gudtsettings.dwdfileattributemask = %hidden or %system
      ' sff_gudtsettings.strdrivestring = chr$(67 to 90)
      ' sff_gstrpreselected = <semicolon delimited file/directory list>
      ' sff_dlgmain( %hwnd_desktop, _
      ' "select files and folders", _
      ' "please select the files and/or directories you wish to use and click accept," & $cr & "click on the close button to exit without selecting anything" )
      '
      '
      ' the second method uses a transparent form to embed (not sure if thats the right
      ' term) the conrol onto a parent form. this uses a modeless dialog and requires
      ' the parent form to have a functional message queue. the buttons are not displayed,
      ' and the parent form will need to initiate the function to generate the delimited
      ' selected files string. an example would be:
      '
      ' sff_gudtsettings.dwddirattributemask = %hidden or %system or %subdir
      ' sff_gudtsettings.dwddrivetypes = %sff_drivetype_fixed
      ' sff_gudtsettings.dwdfileattributemask = %hidden or %system
      ' sff_gudtsettings.strdrivestring = chr$(67 to 90)
      ' sff_gudtsettings.fmodelessform = %true
      ' sff_gstrpreselected = <semicolon delimited file/directory list>
      ' call sff_dlgmain(vhparent, _
      ' "select files and folders", _
      ' "please select the files and/or directories you wish to use")
      '
      ' note the main difference is setting the fmodelessform setting to %true
      '
      ' additionally, either method can be instructed to change the sizes of the
      ' controls and form. these settings are:
      ' sff_gudtsettings.lngformstartx ' horizontal position of form
      ' sff_gudtsettings.lngformstarty = ' vertical position of form
      ' sff_gudtsettings.lngcontrolsizex = ' size (width) of listview and treeview
      ' sff_gudtsettings.lngcontrolsizey = ' size (height) of listview and treeview
      ' sff_gudtsettings.lngverticalborder = ' top/bottom gap between edge and control
      ' sff_gudtsettings.lnghorizontalborder = ' width between form border and controls
      ' sff_gudtsettings.lngmessageheight = ' height of message label (for multiline message)
      ' sff_gudtsettings.lngcontrolgap = ' gap between controls
      ' there are defaults for each of these that are preselected for modal use.
      '
      ' if sff_gstrreturn <> chr$(0) then
      '
      '--------------------------------------------------------------------------------
      ' returns
      '
      ' by calling sff_getselectedlist(), (done by the accept button in modal variant)
      ' a routine is started that recursively checks the treeview and then adds all
      ' files in the global array. these are joined into a single global string
      ' sff_gstrreturn that is for all intensive purposes identical to sff_gstrpreselected.
      '
      ' the global string sff_gstrreturn contains any selected files/directories and is
      ' semicolon ; delimited. currently, testing for chr$(0) will let you know whether
      ' the user has chosen no files or cleared them all, as opposed to canceling.
      '
      ' in the case of the modal popup, the accept button does this, so as soon as the
      ' calling routine gets control back it can check the return value and act
      ' accordingly. in the embedded modeless case, the parent form will need to
      ' call sff_getselectedlist when it is appropriate to do so. eg saving.

      '--------------------------------------------------------------------------------
      ' ** user defined types **
      '--------------------------------------------------------------------------------

      'main user defined type to store user passable parameters and to store internal info
      type sff_settingstype
      'user selectable options
      dwddirattributemask as dword 'file attribute mask (read only, hidden, system)
      dwddrivetypes as dword 'fixed, removable, all, etc.
      dwdfileattributemask as dword 'file attribute mask (read only, hidden, system)
      strdrivestring as string * 26 'optional list of drive letters, leave blank for all

      'display options
      fmodelessform as long 'model (0/false - popup) or modeless (<>0/true - embed)
      lngformstartx as long 'position of form (x)
      lngformstarty as long 'position of form (y)
      lngcontrolsizex as long 'size of treeview and listview x length
      lngcontrolsizey as long 'size of treeview and listview y height
      lngverticalborder as long 'size of border around controls
      lnghorizontalborder as long 'size of border around controls
      lngmessageheight as long 'size of message - y/height
      lngcontrolgap as long 'size of gap between listview and treeview
      'not currently used:
      strfilematchmask as string * 16 'match to mask file against, ex: *.bas

      'internal use - general
      hdlg as dword 'sff main dialog handle
      'internal use - treeview
      htreeview as dword 'handle to the treeview
      htvloadingmessage as dword 'handle to the loading message item in the treeview
      htvthread as dword 'handle of the fill thread
      htvthreaditem as dword 'handle to the item being filled by the fill thread
      htvcurrentitem as dword 'currently selected directory
      htvimagelist as long 'handle to treeview system image list for cleanup on exit
      htvstateimagelist as long 'handle to treeview state image list for cleanup on exit
      tvfclosethread as long 'flag whether to close the fill thread or not
      tvflockthread as long 'flag whether to allow closing of fill thread before completion
      'internal use - listview
      hlistview as dword 'handle to the treeview
      lvboolclosethread as long 'flag whether to close the fill thread or not
      hlvthread as dword 'handle of the fill thread
      hlvthreaditem as dword 'handle to the item being filled by the fill thread
      hlvimagelist as long 'handle to listview system image list for cleanup on exit
      hlvstateimagelist as long 'handle to listview state image list for cleanup on exit
      end type

      '--------------------------------------------------------------------------------
      ' ** globals **
      '--------------------------------------------------------------------------------

      global sff_gudtsettings as sff_settingstype ' main settings and parameters
      global sff_gastrselected() as string ' global array for selected files
      global sff_gstrpreselected as string ' string to pass preselected files/folders from user/app
      global sff_gstrreturn as string ' string to return selected files to user/app
      global sff_gcsarray as critical_section

      '--------------------------------------------------------------------------------
      ' ** includes **
      '--------------------------------------------------------------------------------


      '--------------------------------------------------------------------------------
      ' ** constants **
      '--------------------------------------------------------------------------------
      '%shfileinfo_defined = %true

      ' dialog id constants
      %sff_id_btnaccept = 1002
      %sff_id_btnclose = 1003
      %sff_id_dlgmain = 101
      '%sff_id_edtselectedpath = 1005
      %sff_id_staticmessage = 1004
      %sff_id_tvwdirectorytree = 1001
      %sff_id_lvwfilelist = 1006

      ' sff_settingstype.dwddrivetypes constants
      %sff_drivetype_rootdirectorydoesnotexist = 2??? 'bad drives
      %sff_drivetype_removable = 4??? 'removeable media only
      %sff_drivetype_fixed = 8??? 'fixed media only (hard drives)
      %sff_drivetype_remote = 16??? 'network drives
      %sff_drivetype_ramdisk = 64??? 'ram drives
      %sff_drivetype_cdrom = 32??? 'cdrom drives
      %sff_drivetype_allexistingdrives = 124??? 'all possible drive types

      ' icon names for extraction to temp path
      $sff_iconhourglass = "$sffhg$.ico" 'icon (clock) name for the fill thread
      $sff_iconchecked = "$sffchk$.ico" 'icon (clock) name for the fill thread
      $sff_iconunchecked = "$sffuchk$.ico" 'icon (clock) name for the fill thread
      $sff_icongreyed = "$sffgr$.ico" 'icon (clock) name for the fill thread

      ' checkbox states
      %sff_unchecked = 1
      %sff_checked = 2
      %sff_greyed = 3

      ' custom message identifiers for callback function
      %sff_tv_checkstatechange = %wm_user + 100
      %sff_lv_checkstatechange = %wm_user + 101


      '--------------------------------------------------------------------------------
      ' ** declarations **
      '--------------------------------------------------------------------------------
      ' main form and callback
      declare function sff_dlgmain(byval hparent as dword, byval strtitle as string, byval strmessage as string) as long
      declare callback function sff_dlgmaincallback()

      ' general functions and subs
      declare function sff_geticonindex(byval strfilepath as string) as dword
      declare function sff_getselectedlist () as string
      declare function sff_getselected (byval hitem as long) as string
      declare function sff_initselected() as long
      declare function sff_makecheckedicon() as dword
      declare function sff_makeuncheckedicon() as dword
      declare function sff_makegreyedicon() as dword
      declare function sff_makehourglassicon() as dword

      ' directory treeview functions and subs
      declare function sff_tvaddsubfilestonode(byval hitem as dword) as long
      declare function sff_tvchangechildstate (byval hitem as long, byval lngstate as long) as long
      declare function sff_tvchangefilestate (byval hitem as dword, byval lngstate as long) as long
      declare function sff_tvchangelistviewstate (byval hitem as long, byval lngstate as long) as long
      declare function sff_tvchangeparentstate (byval hitem as long, byval lngstate as long) as long
      declare function sff_tvchangestate (byval hitem as long, byval lngstate as long) as long
      declare function sff_tvcheckchildrenstate (byval hitem as dword) as long
      declare function sff_tvcheckfilestate (byval hitem as long, byval lngstate as long) as long
      declare function sff_tvgetfullpath(byval hitem as dword) as string
      declare function sff_tvgetitem(byval hitem as dword) as string
      declare function sff_tvgetstate(byval hitem as dword) as long
      declare function sff_tvhaschildren(byval strpath as string) as long
      declare function sff_tvinsertitem(byval hparent as dword, byval stritem as string, byval lngchildrenflag as long, byval lngiconindex as long, byval lngstate as long) as long
      declare function sff_tvremoveselected (byval hitem as long) as long
      declare sub sff_tvclosethethread()
      declare sub sff_tvexpandnode(byval hitem as dword)
      declare sub sff_tvfillroot()
      declare sub sff_tvinitialise ()

      ' file listview functions and subs
      declare function sff_lvfill(byval hitem as dword) as long
      declare function sff_lvchangeparentstate (byval lngitem as long, byval lngstate as long) as long
      declare function sff_lvinsertitem(byval stritem as string, byval lngiconindex as long, byval lngstate as long) as long
      declare function sff_lvremoveselected (byval lngitem as long) as long
      declare sub sff_lvclosethethread()
      declare sub sff_lvstartfillthread(byval hitem as dword)
      declare sub sff_lvinitialise ()


      '--------------------------------------------------------------------------------
      ' ** main form and callback **
      '--------------------------------------------------------------------------------

      function sff_dlgmain(byval hparent as dword, byval strtitle as string, byval strmessage as string) as long
      ' purpose: display sff main form and controls
      ' parameters: hparent - handle of parent dialog
      ' strtitle - title to display on form
      ' strmessage - customisable message to display on form
      ' notes: using shorter name locals to ease reading of calcs
      local lngresult as long
      local lngformlength as long
      local lngformheight as long
      local lngformx as long
      local lngformy as long
      local lngsizex as long
      local lngsizey as long
      local lngvertbord as long
      local lnghoribord as long
      local lnggap as long
      local lngmsgheight as long
      %buttonlength = 54
      %buttonheight = 21

      'check to see if user customised size, otherwise use defaults
      lngsizex = sff_gudtsettings.lngcontrolsizex
      if lngsizex = 0 then
      lngsizex = 222
      end if
      lngsizey = sff_gudtsettings.lngcontrolsizey
      if lngsizey = 0 then
      lngsizey = 174
      end if
      lngvertbord = sff_gudtsettings.lngverticalborder
      if lngvertbord = 0 then
      lngvertbord = 9
      end if
      lnghoribord = sff_gudtsettings.lnghorizontalborder
      if lnghoribord = 0 then
      lnghoribord = 9
      end if
      lngmsgheight = sff_gudtsettings.lngmessageheight
      if lngmsgheight = 0 then
      lngmsgheight = 21
      end if
      lnggap = sff_gudtsettings.lngcontrolgap
      if lnggap = 0 then
      lnggap = 5
      end if

      'calculate form height and length based on user settings
      lngformlength = (lngsizex * 2) + (lnghoribord * 2) + lnggap
      lngformheight = lngsizey + (lngvertbord * 2) + lngmsgheight + lnggap
      if sff_gudtsettings.fmodelessform = 0 then
      lngformheight = lngformheight + lnggap + %buttonheight
      end if

      'set up the dialog. if modeless embedded, some extra setting are desirable
      if sff_gudtsettings.fmodelessform = 0 then
      dialog new hparent, strtitle, _
      0, _ 'x
      0, _ 'y
      lngformlength, _ 'length
      lngformheight, _ 'height
      to sff_gudtsettings.hdlg
      else
      dialog new hparent, "select files", _
      sff_gudtsettings.lngformstartx, _ 'x
      sff_gudtsettings.lngformstarty, _ 'y
      lngformlength, _ 'length
      lngformheight, _ 'height
      %ws_child or %ws_clipsiblings or %ws_visible or %ds_control or %ds_nofailcreate, _
      to sff_gudtsettings.hdlg
      end if

      'add common controls
      control add "systreeview32", sff_gudtsettings.hdlg, %sff_id_tvwdirectorytree, _
      "systreeview321", _
      lnghoribord, _ 'x
      lngvertbord + lngmsgheight + lnggap, _ 'y
      lngsizex, _ 'length
      lngsizey, _ 'height
      %ws_child or %ws_visible or %ws_border or %ws_tabstop or %tvs_hasbuttons or %tvs_haslines or %tvs_linesatroot or %tvs_showselalways or %tvs_checkboxes, _
      %ws_ex_clientedge or %ws_ex_left or %ws_ex_ltrreading or %ws_ex_rightscrollbar
      control add "syslistview32", sff_gudtsettings.hdlg, %sff_id_lvwfilelist, _
      ", _
      lngsizex + lnghoribord + lnggap, _ 'x
      lngvertbord + lngmsgheight + lnggap, _ 'y
      lngsizex, _ 'length
      lngsizey, _ 'height
      %ws_child or %ws_visible or %ws_border or %ws_tabstop or %lvs_report, _
      %ws_ex_clientedge
      control add label, sff_gudtsettings.hdlg, %sff_id_staticmessage, strmessage, _
      lnghoribord, _ 'x
      lngvertbord, _ 'y
      lngsizex * 2, _ 'length
      lngmsgheight 'height

      'for modal popup, some buttons might be nice to handle exit. also need to show dialog
      if sff_gudtsettings.fmodelessform = 0 then
      control add button, sff_gudtsettings.hdlg, %sff_id_btnaccept, "&accept", _
      lnghoribord + lngsizex - %buttonlength , _
      lngvertbord + lngsizey + lnggap * 2 + lngmsgheight, _
      %buttonlength, _
      %buttonheight
      control add button, sff_gudtsettings.hdlg, %sff_id_btnclose, "&close", _
      lnghoribord + lngsizex + lnggap, _
      lngvertbord + lngsizey + lnggap * 2 + lngmsgheight, _
      %buttonlength, _
      %buttonheight
      dialog show modal sff_gudtsettings.hdlg, call sff_dlgmaincallback to lngresult
      else
      dialog show modeless sff_gudtsettings.hdlg, call sff_dlgmaincallback to lngresult
      end if

      sff_dlgmain_exit:
      function = lngresult
      end function

      '--------------------------------------------------------------------------------

      callback function sff_dlgmaincallback()
      ' purpose: main callback function
      ' parameters: none
      ' returns: nothing
      local hselecteditem as dword
      local udtnmtreeview as nm_treeview ptr
      local udtnmlistview as nm_listview ptr
      local udttv_item as tv_item
      local udtlv_item as lv_item
      local ptrnmhdr as nmhdr ptr
      local keypress as tv_keydown ptr
      local udttv_hittestinfo as tv_hittestinfo
      local dwpos as dword
      local intret as integer
      local udtlv_hittestinfo as lv_hittestinfo
      local lngstate as long
      local dwret as dword
      static lngcount as long
      static lngcount2 as long

      select case cbmsg
      case %wm_initdialog
      'initialise critical section to protect global array thread operations
      initializecriticalsection sff_gcsarray

      'make icons
      sff_makecheckedicon
      sff_makeuncheckedicon
      sff_makegreyedicon
      sff_makehourglassicon

      'enable treeview and listview
      initcommoncontrols

      'initialise treeview
      control handle cbhndl, %sff_id_tvwdirectorytree to sff_gudtsettings.htreeview
      sff_tvinitialise ()

      'initialise listview
      control handle cbhndl, %sff_id_lvwfilelist to sff_gudtsettings.hlistview
      sff_lvinitialise ()

      'finish init
      sff_initselected
      sff_tvfillroot

      case %wm_notify
      select case cbctl
      case %sff_id_tvwdirectorytree
      udtnmtreeview = cblparam
      ptrnmhdr = cblparam
      select case @udtnmtreeview.hdr.code
      case %tvn_itemexpanding
      select case @udtnmtreeview.action
      case %tve_expand
      sff_tvexpandnode @udtnmtreeview.itemnew.hitem
      'case %tve_collapse
      ' treeview_expand sff_gudtsettings.htreeview, @udtnmtreeview.itemnew.hitem, %tve_collapse ' or %tve_collapsereset
      end select
      case %tvn_selchanged
      'clicked on treeview node. fill listview with file list of corresponding directory
      if @udtnmtreeview.itemnew.hitem = sff_gudtsettings.htvloadingmessage then
      else
      hselecteditem = treeview_getselection(sff_gudtsettings.htreeview)
      sff_gudtsettings.htvcurrentitem = hselecteditem
      sff_lvstartfillthread hselecteditem
      end if
      case %nm_click
      'perform test to determine which node was clicked and whether it was the state image
      dwpos = getmessagepos()
      intret = lowrd(dwpos)
      udttv_hittestinfo.pt.x = intret
      intret = hiwrd(dwpos)
      udttv_hittestinfo.pt.y = intret
      call mapwindowpoints(%hwnd_desktop, @ptrnmhdr.hwndfrom, udttv_hittestinfo.pt, 1)
      call treeview_hittest(@ptrnmhdr.hwndfrom, udttv_hittestinfo)
      if udttv_hittestinfo.flags = %tvht_onitemstateicon then
      postmessage cbhndl, %sff_tv_checkstatechange, @ptrnmhdr.hwndfrom, udttv_hittestinfo.hitem
      end if
      case %tvn_keydown
      'test for keyboard input
      keypress = cblparam
      select case @keypress.wvkey
      case %vk_space
      'handle space bar same as clicking the state image
      postmessage cbhndl, %sff_tv_checkstatechange, @ptrnmhdr.hwndfrom, treeview_getselection(@ptrnmhdr.hwndfrom)
      end select
      end select

      case %sff_id_lvwfilelist
      udtnmlistview = cblparam
      ptrnmhdr = cblparam
      select case @udtnmlistview.hdr.code
      case %nm_dblclk
      'trap the doubleclick event otherwise state also changes for dblclk.
      function = %true
      case %nm_click
      'perform test to determine which listview item was selected and whether it was the state image
      dwpos = getmessagepos()
      intret = lowrd(dwpos)
      udtlv_hittestinfo.pt.x = intret
      intret = hiwrd(dwpos)
      udtlv_hittestinfo.pt.y = intret
      call mapwindowpoints(%hwnd_desktop, @ptrnmhdr.hwndfrom, udtlv_hittestinfo.pt, 1)
      call listview_hittest(@ptrnmhdr.hwndfrom, udtlv_hittestinfo)
      if udtlv_hittestinfo.flags = %lvht_onitemstateicon then
      postmessage cbhndl, %sff_lv_checkstatechange, @ptrnmhdr.hwndfrom, udtlv_hittestinfo.iitem
      end if
      case %lvn_keydown
      'test for keyboard input
      keypress = cblparam
      select case @keypress.wvkey
      case %vk_space
      'ideally handle the space bar as a click event if we can determine which listview item
      'for now cancel the event until this works
      function = %true
      'postmessage cbhndl, %sff_lv_checkstatechange, @ptrnmhdr.hwndfrom, ' need equivalent to treeview_getselection(@ptrnmhdr.hwndfrom)
      end select
      end select

      end select

      case %wm_command
      select case cbctl
      case %sff_id_btnaccept
      if cbctlmsg = %bn_clicked or cbctlmsg = 1 then
      hselecteditem = treeview_getselection(sff_gudtsettings.htreeview)
      if hselecteditem then
      sff_gstrreturn = sff_getselectedlist ()
      dialog end cbhndl
      end if
      end if
      case %sff_id_btnclose
      if cbctlmsg = %bn_clicked or cbctlmsg = 1 then
      dialog end cbhndl
      end if
      case %wm_destroy
      sff_tvclosethethread
      sff_lvclosethethread

      kill environ$("temp") & $sff_iconhourglass
      kill environ$("temp") & $sff_iconchecked
      kill environ$("temp") & $sff_iconunchecked
      kill environ$("temp") & $sff_icongreyed
      end select

      case %sff_tv_checkstatechange
      udttv_item.mask = %tvif_state or %tvif_handle
      udttv_item.hitem = cblparam
      udttv_item.statemask = %tvis_stateimagemask
      treeview_getitem sff_gudtsettings.htreeview, udttv_item
      lngstate = udttv_item.state

      shift right lngstate, 12

      if lngstate = %sff_unchecked then
      call sff_tvremoveselected (cblparam)
      elseif lngstate = %sff_greyed then
      'we only want user to directly click between checked and unchecked
      'so grey needs to be changed to unchecked
      udttv_item.state = indextostateimagemask(%sff_unchecked)
      treeview_setitem sff_gudtsettings.htreeview, udttv_item
      lngstate = %sff_unchecked
      call sff_tvremoveselected (cblparam)
      end if
      sff_tvchangechildstate cblparam, lngstate
      sff_tvchangelistviewstate cblparam, lngstate
      sff_tvchangeparentstate cblparam, lngstate

      case %sff_lv_checkstatechange
      udtlv_item.mask = %lvif_state
      udtlv_item.iitem = cblparam
      udtlv_item.statemask = %lvis_stateimagemask
      listview_getitem sff_gudtsettings.hlistview, udtlv_item
      lngstate = udtlv_item.state

      shift right lngstate, 12
      select case lngstate
      case %sff_unchecked
      sff_lvremoveselected cblparam
      case %sff_checked

      end select

      sff_lvchangeparentstate cblparam, lngstate

      case %wm_destroy
      imagelist_destroy sff_gudtsettings.htvimagelist
      imagelist_destroy sff_gudtsettings.htvstateimagelist
      imagelist_destroy sff_gudtsettings.hlvimagelist
      imagelist_destroy sff_gudtsettings.hlvstateimagelist
      deletecriticalsection sff_gcsarray
      end select
      end function

      '--------------------------------------------------------------------------------
      ' ** general functions and subs **
      '--------------------------------------------------------------------------------

      function sff_geticonindex(byval strfilepath as string) as dword
      ' purpose: lookup icon handle for specified file
      ' parameters: strfilepath - path and filename of specific file or folder
      ' returns: handle to icon image in system image list
      local udtshfileinfo as shfileinfo

      shgetfileinfo byval strptr(strfilepath), 0, udtshfileinfo, sizeof(udtshfileinfo), %shgfi_smallicon or %shgfi_sysiconindex
      function = udtshfileinfo.iicon
      end function

      '--------------------------------------------------------------------------------

      function sff_getselectedlist () as string
      ' purpose: iterates through the treeview to collate all selected dirs
      ' also collates all selected files/dirs from selected array
      ' parameters: none
      ' returns: delimited string of all selected files and folders
      local hitem as dword
      local udttv_item as tv_item
      local lngstate as long
      local strreturn as string
      local strtemp as string
      local lngi as long

      'collect selected folders in treeview
      hitem = treeview_getroot(sff_gudtsettings.htreeview)
      do
      udttv_item.mask = %tvif_handle or %tvif_children or %tvif_state
      udttv_item.hitem = hitem
      udttv_item.statemask = %tvis_stateimagemask
      treeview_getitem sff_gudtsettings.htreeview, udttv_item
      lngstate = udttv_item.state
      shift right lngstate, 12

      if lngstate = 2 then
      'save checked items
      strreturn = strreturn & ucase$(sff_tvgetfullpath(hitem)) & ";"
      end if
      if lngstate = 3 and udttv_item.cchildren = 1 then
      'if greyed, proceed down to next level of treeview
      strreturn = strreturn & sff_getselected (hitem)
      end if
      hitem = treeview_getnextitem (sff_gudtsettings.htreeview, hitem, %tvgn_next)
      loop until hitem = 0

      ' collect selected files/folders in selected array
      entercriticalsection sff_gcsarray
      array sort sff_gastrselected()
      if ubound(sff_gastrselected()) > 0 then
      if len(strreturn) > 0 then
      strreturn = strreturn & ";" & trim$(join$(sff_gastrselected(), ";"),";")
      else
      strreturn = trim$(join$(sff_gastrselected(), ";"),";")
      end if
      end if
      leavecriticalsection sff_gcsarray

      strreturn = trim$(strreturn, ";")
      function = strreturn
      end function

      '--------------------------------------------------------------------------------

      function sff_getselected (byval hitem as long) as string
      ' purpose: self referencing treeview search function to look for selected items
      ' parameters: hitem - handle of treeview item
      ' returns: delimited string of all selected directories
      ' notes: if a node is selected and not greyed there is no need to proceed
      ' further, as returning this node will cover all child items
      local htmpitem as long
      local udttv_item as tv_item
      local hparentitem as long
      loca
      Stuart Sanders
      stuart at bitshk dot com
      www.bitshk.com

      Comment


        #4
        This is the second part of libSFF.inc. Cut and paste this as well as the former code.

        Code:
        Function SFF_TVChangeState (ByVal hItem As Long, ByVal lngState As Long) As Long
        ' Purpose:		change the state of the specified node to the specified state
        ' Parameters:	hItem - handle to node that is to be changed
        ' 				lngState - new state of node
        ' Returns:		unused currently
        	Local udtTV_Item As TV_ITEM
        	udtTV_Item.mask = %TVIF_STATE Or %TVIF_HANDLE
        	udtTV_Item.hItem = hItem
        	udtTV_Item.stateMask = %TVIS_STATEIMAGEMASK
        	udtTV_Item.state = IndexToStateImageMask(lngState)
        	TreeView_SetItem SFF_gudtSettings.hTreeView, udtTV_Item
        End Function
        
        '--------------------------------------------------------------------------------
        
        Function SFF_TVCheckChildrenState (ByVal hItem As DWord) As Long
        ' Purpose:		check to see whether child nodes are either all checked,
        '				all unchecked, or a mix
        ' Parameters:	hItem - handle to the parent node to check
        ' Returns:		%SFF_Checked - all child nodes are checked
        '				%SFF_UnChecked - all child nodes are unchecked
        '				%SFF_Greyed - child nodes are a mixed of checked and unchecked
        	Local hTmpItem As Long
        	Local udtTV_Item As TV_ITEM
        	Local lngState As Long
        	Local alngState() As Long
        	Local lngRet As Long
        	Local lngCount As Long
        	ReDim alngState (0 To 3)
        
        
        	hTmpItem = TreeView_GetChild(SFF_gudtSettings.hTreeView, hItem)
        	' if directory node hasn't been expanded yet then htmpItem will return 0
        	' we need to expand the node in order to accurately check status
        	If htmpItem = 0 Then
        		SFF_TVExpandNode (hItem)
        		Do
        			Dialog DoEvents
        		Loop Until SFF_gudtSettings.hTVThread = 0
        		hTmpItem = TreeView_GetChild(SFF_gudtSettings.hTreeView, hItem)
        	End If
        
        	Do
        		udtTV_Item.mask = %TVIF_STATE Or %TVIF_HANDLE
        		udtTV_Item.hItem = hTmpItem
        		udtTV_Item.stateMask = %TVIS_STATEIMAGEMASK
        		TreeView_GetItem SFF_gudtSettings.hTreeView, udtTV_Item
        		lngState = udtTV_Item.state
        		Shift Right lngState, 12
        
        		Incr alngState(lngState)
        		hTmpItem = TreeView_GetNextSibling(SFF_gudtSettings.hTreeView, hTmpItem)
        		If hTmpItem = %NULL Then Exit Loop
        	Loop
        
        	lngCount = alngState(1) + alngState(2) + alngState(3)
        	If alngState(1) = lngCount Then
        		lngRet = %SFF_UnChecked
        	ElseIf alngState(2) = lngCount Then
        		lngRet = %SFF_Checked
        	Else
        		lngRet = %SFF_Greyed
        	End If
        SFF_TVCheckChildrenState_Exit:
        	Function = lngRet
        End Function
        
        '--------------------------------------------------------------------------------
        
        Function SFF_TVCheckFileState (ByVal hItem As DWord, ByVal lngState As Long) As Long
        ' Purpose:		test for whether files under a node match a state passed.  Usually
        '				result of a state change passing up the tree.  The state change need to
        '				take into consideration of files that may not match the changing state.
        ' Parameters:	hItem - handle to the node to check for differing file states
        ' Returns:		%SFF_Checked - files match checked state
        '				%SFF_UnChecked - files match unchecked state
        '				%SFF_Greyed - one or more files don't match testing state
        	Local lngRet As Long
        	Local strPath As String
        	Local strFile As String
        	Local lngArrayRet As Long
        	Local strFilePath As String
        
        	lngRet = lngState
        
        	Select Case lngState
        		Case %SFF_Checked
        			strPath = SFF_TVGetFullPath(hItem)
        			strFile = Dir$(strPath, SFF_gudtSettings.dwdFileAttributeMask)
        			Do
        				If Len(strFile) > 0 Then
        					If (GetAttr (strPath & strFile) And Not %FILE_ATTRIBUTE_DIRECTORY) Then
        						strFilePath = strPath & strFile
        						EnterCriticalSection SFF_gcsArray
        							Array Scan SFF_gastrSelected(), Collate UCase, = strFilePath, To lngArrayRet
        						LeaveCriticalSection SFF_gcsArray
        						If lngArrayRet = 0 Then
        							lngRet = %SFF_Greyed
        							Exit Loop
        						End If
        					End If
        				Else
        					Exit Loop
        				End If
        				strFile = Dir$
        			Loop
        
        		Case %SFF_UnChecked
        			strPath = SFF_TVGetFullPath(hItem)
        			strFile = Dir$(strPath, SFF_gudtSettings.dwdFileAttributeMask)
        			Do
        				If Len(strFile) > 0 Then
        					If (GetAttr (strPath & strFile) And Not %FILE_ATTRIBUTE_DIRECTORY) Then
        						EnterCriticalSection SFF_gcsArray
        							Array Scan SFF_gastrSelected(), Collate UCase, = (strPath & strFile), To lngArrayRet
        						LeaveCriticalSection SFF_gcsArray
        						If lngArrayRet <> 0 Then
        							lngRet = %SFF_Greyed
        							Exit Loop
        						End If
        					End If
        				Else
        					Exit Loop
        				End If
        				strFile = Dir$
        			Loop
        	End Select
        
        SFF_TVCheckFileState_Exit:
        	Function = lngRet
        End Function
        
        '--------------------------------------------------------------------------------
        
        Function SFF_TVGetFullPath(ByVal hItem As DWord) As String
        ' Purpose:		given a node, gives the full path of the directory it represents
        ' Parameters:	hItem - handle to the node
        ' Returns:		string as standard windows directory.  eg C:\PROGRAM FILES\
        	Local strPath As String
        	Local hCurrent As DWord
        	hCurrent = hItem
        	Do Until hCurrent = 0
        		strPath = SFF_TVGetItem(hCurrent) & "\" & strPath
        		hCurrent = TreeView_GetParent(SFF_gudtSettings.hTreeview, hCurrent)
        	Loop
        	Function = strPath
        End Function
        
        '--------------------------------------------------------------------------------
        
        Function SFF_TVGetItem(ByVal hItem As DWord) As String
        ' Purpose:		gets the display text of a treeview node
        ' Parameters:	hItem - handle to the node
        ' Returns:		string containing the node display text
        	Local aszBuffer As Asciiz * %MAX_PATH + 1
        	Local udtTVItem AS TV_ITEM
        
        	udtTVItem.mask = %TVIF_TEXT Or %TVIF_HANDLE
        	udtTVItem.cchTextMax = SizeOf(aszBuffer)
        	udtTVItem.pszText = VarPtr(aszBuffer)
        	udtTVItem.hItem = hItem
        	TreeView_GetItem SFF_gudtSettings.hTreeview, udtTVItem
        	Function = aszBuffer
        End Function
        
        '--------------------------------------------------------------------------------
        
        Function SFF_TVGetState(ByVal hItem As DWord) As Long
        ' Purpose:		gets the state of a treeview node
        ' Parameters:	hItem - handle to the node
        ' Returns:		long containing the node state
        	Local udtTVItem AS TV_ITEM
        	Local lngState As Long
        	udtTVItem.mask = %TVIF_STATE Or %TVIF_HANDLE Or %TVIF_CHILDREN
        	udtTVItem.hItem = hItem
        	udtTVItem.stateMask = %TVIS_STATEIMAGEMASK
        	TreeView_GetItem SFF_gudtSettings.hTreeview, udtTVItem
        	lngState = udtTVItem.state
        	Shift Right lngState, 12
        	Function = lngState
        End Function
        
        '--------------------------------------------------------------------------------
        
        Function SFF_TVHasChildren(ByVal strPath As String) As Long
        ' Purpose:		determines whether a directory has child directories underneath
        ' Parameters:	strPath - string containing directory path to test
        ' Returns:		1 - has children
        '				0 - no children
        ' Notes:		ignores the system directories "." and ".."
        	Local hFindFile As DWord
        	Local udtWin32FindData As Win32_Find_Data
        	Local strSearch As String
        	Local lngIndex As Long
        	If Right$(strPath, 1) = "\" Then
        		strSearch = strPath & "*"
        	Else
        		strSearch = strPath & "\*"
        	End If
        	'Check For directories
        	hFindFile = FindFirstFile(ByVal StrPtr(strSearch), udtWin32FindData)
        	If hFindFile Then
        		Do
        			If udtWin32FindData.cFileName <> "." And udtWin32FindData.cFileName <> ".." Then
        				If (udtWin32FindData.dwFileAttributes And %FILE_ATTRIBUTE_DIRECTORY) = %FILE_ATTRIBUTE_DIRECTORY _
        				  And ((udtWin32FindData.dwFileAttributes And SFF_gudtSettings.dwdDirAttributeMask) Or (udtWin32FindData.dwFileAttributes = %FILE_ATTRIBUTE_DIRECTORY)) Then		'matches attributes?
        					FindClose hFindFile
        					Function = 1
        					Exit Function
        				End If
        			End If
        		Loop While FindNextFile(hFindFile, udtWin32FindData)
        		FindClose hFindFile
        	End If
        	Function = 0
        End Function
        
        '--------------------------------------------------------------------------------
        
        Function SFF_TVInsertItem(ByVal hParent As DWord, ByVal strItem As String, ByVal lngChildrenFlag As Long, ByVal lngIconIndex As Long, ByVal lngState As Long) As Long
        ' Purpose:		inserts a node into the treeview
        ' Parameters:	hParent - parent node to insert child under
        '				strItem - name of new node
        '				lngChildrenFlag - whether new node has children
        '				lngIconIndex - image to display with node
        '				lngState - state of new item
        ' Returns:		long handle to the new item
        	Local udtTV_Insertudt AS TV_INSERTSTRUCT
        	udtTV_Insertudt.hParent = hParent
        	udtTV_Insertudt.hInsertAfter = %TVI_SORT '%TVI_LAST
        	udtTV_Insertudt.Item.Item.mask = %TVIF_TEXT Or %TVIF_CHILDREN Or %TVIF_IMAGE Or %TVIF_SELECTEDIMAGE Or %TVIF_STATE
        	udtTV_Insertudt.Item.Item.pszText = StrPtr(strItem)
        	udtTV_Insertudt.Item.Item.cchTextMax = Len(strItem)
        	udtTV_Insertudt.Item.Item.cChildren = lngChildrenFlag
        	udtTV_Insertudt.Item.Item.iImage = lngIconIndex
        	udtTV_Insertudt.Item.Item.iSelectedImage = lngIconIndex
        	udtTV_Insertudt.Item.Item.stateMask = %TVIS_STATEIMAGEMASK
        	udtTV_Insertudt.Item.Item.state = INDEXTOSTATEIMAGEMASK(lngState)
        	Function = TreeView_InsertItem(SFF_gudtSettings.hTreeview, udtTV_Insertudt)
        End Function
        
        '--------------------------------------------------------------------------------
        
        Function SFF_TVRemoveSelected (ByVal hItem As Long) As Long
        ' Purpose:		removes treeview item from selected list
        ' Parameters:	hItem - node to remove
        ' Returns:		currently unused
        'On Error Resume Next
        	Local strPath As String
        	Local lngArrayRet As Long
        	strPath = UCase$(SFF_TVGetFullPath(hItem))
        	EnterCriticalSection SFF_gcsArray
        		Do
        			Array Scan SFF_gastrSelected(), From 1 To Len(strPath), Collate UCase, = strPath, To lngArrayRet
        			If lngArrayRet <> 0 Then
        				Array Delete SFF_gastrSelected(lngArrayRet - 1)
        				ReDim Preserve SFF_gastrSelected(0 To (UBound(SFF_gastrSelected) - 1))
        			End If
        		Loop Until lngArrayRet = 0
        	LeaveCriticalSection SFF_gcsArray
        End Function
        
        '--------------------------------------------------------------------------------
        
        Sub SFF_TVCloseTheThread()
        ' Purpose:		sets the instruction to close the TV add nodes thread and waits until closed
        ' Parameters:	none
        	SFF_gudtSettings.TVfCloseThread = %TRUE
        	Do Until SFF_gudtSettings.hTVThread = 0
        		Dialog DoEvents
        	Loop
        End Sub
        
        '--------------------------------------------------------------------------------
        
        Sub SFF_TVExpandNode(ByVal hItem As DWord)
        ' Purpose:		expands the node. If child nodes already loaded then exits
        ' Parameters:	hItem - node to open
        ' Notes:		starts a new thread to fill the treeview node.  Thread will close
        '				when finished or when instructed to close
        	Local hChildNode As DWord
        
        	If SFF_gudtSettings.hTVThread Then
        		If TreeView_GetParent(SFF_gudtSettings.hTreeview, hItem) = SFF_gudtSettings.hTVThreadItem Then EXIT Sub
        		'previous expansion not finished ... so stop and delete
        		SFF_TVCloseTheThread
        		TreeView_Expand SFF_gudtSettings.hTreeview, SFF_gudtSettings.hTVThreadItem, %TVE_COLLAPSE Or %TVE_COLLAPSERESET
        	End If
        
        	hChildNode = TreeView_GetChild(SFF_gudtSettings.hTreeview, hItem)
        	'check if child nodes exist.  if so stop expansion
        	If hChildNode = 0 Then
        		'display a "Loading" message as the first child under the node
        		SFF_gudtSettings.hTVLoadingMessage = SFF_TVInsertItem(hItem, "..Loading...", 0, SFF_GetIconIndex(ENVIRON$("TEMP") & $SFF_IconHourGlass), %SFF_UnChecked)
        		'Thread out the fill routine
        		SFF_gudtSettings.hTVThreadItem = hItem
        		SFF_gudtSettings.TVfCloseThread = %FALSE
        		Thread CREATE SFF_TVAddSubfilesToNode(SFF_gudtSettings.hTVThreadItem) To SFF_gudtSettings.hTVThread
        	End If
        End Sub
        
        '--------------------------------------------------------------------------------
        
        Sub SFF_TVFillRoot()
        ' Purpose:		fills the top level of the directory treeview
        ' Parameters:	none
        	Local aszDriveLetter As Asciiz * 4
        	Local lngIndex As Long
        	Local hItem As Long
        	Local strPath As String
        	Local lngState As Long
        '	Local lngi As Long
        	Local lngArrayRet As Long
        
        	For lngIndex = 65 To 90
        		If SFF_gudtSettings.strDriveString <> "" And InStr(UCase$(SFF_gudtSettings.strDriveString), CHR$(lngIndex)) = 0 Then ITERATE
        		aszDriveLetter = CHR$(lngIndex) & ":\"
        		If (2^ GetDriveType(aszDriveLetter) And SFF_gudtSettings.dwdDriveTypes) Then
        			lngState = %SFF_UnChecked
        			strPath = UCase$(aszDriveLetter)
        
        			'check whether any items on drive are preselected
        			EnterCriticalSection SFF_gcsArray
        				Array Scan SFF_gastrSelected(), From 1 To Len(strPath), Collate UCase, = strPath, To lngArrayRet
        				If lngArrayRet > 0 Then
        					If SFF_gastrSelected(lngArrayRet - 1) = strpath Then
        						lngState = %SFF_Checked
        					Else
        						lngState = %SFF_Greyed
        					End If
        				End If
        			LeaveCriticalSection SFF_gcsArray
        
        
        '			For lngi = 1 To UBound(SFF_gastrSelected())
        '				If InStr(SFF_gastrSelected(lngi), strPath) Then
        '					If SFF_gastrSelected(lngi) = strpath Then
        '						lngState = %SFF_Checked
        '						Exit For
        '					Else
        '						lngState = %SFF_Greyed
        '						Exit For
        '					End If
        '				End If
        '			Next lngi
        			hItem = SFF_TVInsertItem %TVI_ROOT, Left$(aszDriveLetter, 2), SFF_TVHasChildren(aszDriveLetter), SFF_GetIconIndex(BYCOPY aszDriveLetter), lngState
        			If lngState = %SFF_Checked Then
        				Call SFF_TVRemoveSelected (hItem)
        			End If
        		End If
        	Next lngIndex
        End Sub
        
        '--------------------------------------------------------------------------------
        
        Sub SFF_TVInitialise ()
        ' Purpose:		initialises the treeview.  loads system file/dir images and state images
        ' Parameters:	none
        	Local udtSHFileInfo AS SHFILEINFO
        	Local hImageList As Long
        	Local strFileName As String
        	Local hLib As DWord
        
        	'load system image list for directory images
        	SFF_gudtSettings.hTVImageList = SHGetFileInfo("C:\", 0, udtSHFileInfo, SizeOf(udtSHFileInfo), %SHGFI_USEFILEATTRIBUTES Or %SHGFI_SMALLICON Or %SHGFI_SYSICONINDEX)
        	TreeView_SetImageList SFF_gudtSettings.hTreeview, SFF_gudtSettings.hTVImageList, %TVSIL_NORMAL
        
        	'load custom checkbox images (3-state)
            SFF_gudtSettings.hTVStateImageList = ImageList_Create(16, 16, 1, 4, 1)
        
        	strFileName = ENVIRON$("TEMP") & "\" & $SFF_IconUnchecked
          	hLib = SHGetFileInfo ByVal StrPtr(strFileName), 0, udtSHFileInfo, SIZEOF(udtSHFileInfo), %SHGFI_SMALLICON Or %SHGFI_SYSICONINDEX
          	ImageList_AddIcon SFF_gudtSettings.hTVStateImageList, ImageList_GetIcon (hLib, udtSHFileInfo.iIcon, %ILD_NORMAL)
          	ImageList_AddIcon SFF_gudtSettings.hTVStateImageList, ImageList_GetIcon (hLib, udtSHFileInfo.iIcon, %ILD_NORMAL)
        
        	strFileName = ENVIRON$("TEMP") & "\" & $SFF_IconChecked
          	hLib = SHGetFileInfo ByVal StrPtr(strFileName), 0, udtSHFileInfo, SIZEOF(udtSHFileInfo), %SHGFI_SMALLICON Or %SHGFI_SYSICONINDEX
          	ImageList_AddIcon SFF_gudtSettings.hTVStateImageList, ImageList_GetIcon (hLib, udtSHFileInfo.iIcon, %ILD_NORMAL)
        
        	strFileName = ENVIRON$("TEMP") & "\" & $SFF_IconGreyed
          	hLib = SHGetFileInfo ByVal StrPtr(strFileName), 0, udtSHFileInfo, SIZEOF(udtSHFileInfo), %SHGFI_SMALLICON Or %SHGFI_SYSICONINDEX
          	ImageList_AddIcon SFF_gudtSettings.hTVStateImageList, ImageList_GetIcon (hLib, udtSHFileInfo.iIcon, %ILD_NORMAL)
        
            TreeView_SetImageList SFF_gudtSettings.hTreeview, SFF_gudtSettings.hTVStateImageList, %TVSIL_STATE
        End sub
        
        '--------------------------------------------------------------------------------
        '   ** File Listview Functions and Subs **
        '--------------------------------------------------------------------------------
        
        'Function SFF_LVChangeState (lngItem As Long, lngState As Long) As Long
        ' Purpose:		when a listview state is changed, the file either needs to be
        '				removed from the array or added to it.  There are also special cases.
        ' Parameters:	none - it uses the currently filled listview
        ' Returns:		currently unused
        
        '--------------------------------------------------------------------------------
        
        Function SFF_LVChangeParentState (ByVal lngItem As Long, ByVal lngState As Long) As Long
        ' Purpose:		when a listview state is changed, it may result in the parent treeview
        '				nodes requiring change as well.  eg all dirs/files selected and 1 is
        '				unselected causing a switch to a Greyed state all the way up the treeview
        ' Parameters:	none - it uses the currently filled listview
        ' Returns:		currently unused
        	Local udtLV_Item As LV_ITEM
        	Local udtTV_Item As TV_ITEM
        	Local lngItemState As Long
        	Local lngItemCount As Long
        	Local lngStateCount() As Long
        	Local lngUncheck As Long
        	Local lngi As Long
        	Local lngChildren As Long
        	Local lngChildrenState As Long
        	Local strPath As String
        	Local strFilePath As String
        	Local lngArrayRet As Long
        	Local aszBuffer As Asciiz * %MAX_PATH + 1
        	Local strCurrentItem As String
        	Local strDir As String
        	Local strParentPath As String
        	Local lngParentState As Long
        
        	'First a check is needed on whether listview is now all selected, all
        	'unselected or mixed
        	ReDim lngStateCount(1 To 2)
        	lngItemCount = ListView_GetItemCount(SFF_gudtSettings.hListview)
        	For lngi = 0 To lngItemCount - 1
        		udtLV_Item.mask = %LVIF_STATE
        		udtLV_Item.iItem = lngi
        		udtLV_Item.stateMask = %LVIS_STATEIMAGEMASK
        		ListView_GetItem SFF_gudtSettings.hListView, udtLV_Item
        		lngItemState = udtLV_Item.state
        		Shift Right lngItemState, 12
        		Incr lngStateCount(lngItemState)
        	Next lngi
        
        	'get parent treeview node for listview directory
        	udtTV_Item.mask  = %TVIF_HANDLE Or %TVIF_CHILDREN Or %TVIF_STATE
        	udtTV_Item.hItem = SFF_gudtSettings.hTVCurrentItem
        	TreeView_GetItem SFF_gudtSettings.hTreeView, udtTV_Item
        	lngParentState = udtTV_Item.state
        	Shift Right lngParentState, 12
        	lngChildren = udtTV_Item.cChildren
        
        	'get details on listview item changed
        	udtLV_Item.mask = %LVIF_TEXT
        	udtLV_Item.iItem = lngItem
        	udtLV_Item.cchTextMax = SizeOf(aszBuffer)
        	udtLV_Item.pszText = VarPtr(aszBuffer)
        	ListView_GetItem SFF_gudtSettings.hListView, udtLV_Item
        	strPath = SFF_TVGetFullPath(SFF_gudtSettings.hTVCurrentItem)
        	strCurrentItem = strPath & aszBuffer
        
        	'based on the result of the check, initiate
        	' (1) adding or removing items from selected array
        	' (2) parent state change
        	If lngStateCount(%SFF_Checked) = 0 Then
        		' all items are unchecked so need to clear directory state and remove file from array
        		EnterCriticalSection SFF_gcsArray
        			Array Scan SFF_gastrSelected(), Collate UCase, = strCurrentItem, To lngArrayRet
        			If lngArrayRet <> 0 Then
        				Array Delete SFF_gastrSelected(lngArrayRet - 1)
        				ReDim Preserve SFF_gastrSelected(0 To (UBound(SFF_gastrSelected) - 1))
        			End If
        		LeaveCriticalSection SFF_gcsArray
        
        		If lngChildren Then
        			lngChildrenState = SFF_TVCheckChildrenState (SFF_gudtSettings.hTVCurrentItem)
        			If lngChildrenState = %SFF_UnChecked Then
        				SFF_TVChangeState (SFF_gudtSettings.hTVCurrentItem, %SFF_UnChecked)
        				SFF_TVChangeParentState (SFF_gudtSettings.hTVCurrentItem, %SFF_UnChecked)
        			Else
        				SFF_TVChangeState (SFF_gudtSettings.hTVCurrentItem, %SFF_Greyed)
        				SFF_TVChangeParentState (SFF_gudtSettings.hTVCurrentItem, %SFF_Greyed)
        			End If
        		Else
        			SFF_TVChangeState (SFF_gudtSettings.hTVCurrentItem, %SFF_UnChecked)
        			SFF_TVChangeParentState (SFF_gudtSettings.hTVCurrentItem, %SFF_UnChecked)
        		End If
        	ElseIf lngStateCount(%SFF_UnChecked) = 0 Then
        		' all items are checked
        		EnterCriticalSection SFF_gcsArray
        			ReDim Preserve SFF_gastrSelected(0 To (UBound(SFF_gastrSelected) + 1))
        			SFF_gastrSelected(UBound(SFF_gastrSelected)) = UCase$(strCurrentItem)
        		LeaveCriticalSection SFF_gcsArray
        
        		If lngChildren Then
        			lngChildrenState = SFF_TVCheckChildrenState (SFF_gudtSettings.hTVCurrentItem)
        			If lngChildrenState = %SFF_Checked Then
        				SFF_TVChangeState (SFF_gudtSettings.hTVCurrentItem, %SFF_Checked)
        				SFF_TVChangeParentState (SFF_gudtSettings.hTVCurrentItem, %SFF_Checked)
        			Else
        				SFF_TVChangeState (SFF_gudtSettings.hTVCurrentItem, %SFF_Greyed)
        				SFF_TVChangeParentState (SFF_gudtSettings.hTVCurrentItem, %SFF_Greyed)
        			End If
        		Else
        			SFF_TVChangeState (SFF_gudtSettings.hTVCurrentItem, %SFF_Checked)
        			SFF_TVChangeParentState (SFF_gudtSettings.hTVCurrentItem, %SFF_Checked)
        		End If
        	Else
        		' there is a mix of checked and unchecked items.
        		If lngState = %SFF_Unchecked And lngStateCount(%SFF_Unchecked) = 1 Then
        			'first item unchecked - need to add all checked items to array
        			For lngi = 0 To lngStateCount(%SFF_Checked)
        				udtLV_Item.mask = %LVIF_TEXT
        				udtLV_Item.iItem = lngi
        				udtLV_Item.cchTextMax = SizeOf(aszBuffer)
        				udtLV_Item.pszText = VarPtr(aszBuffer)
        				ListView_GetItem SFF_gudtSettings.hListView, udtLV_Item
        				strFilePath = strPath & aszBuffer
        				If strCurrentItem <> strFilePath Then
        					'add all items except single unselected item
        					'EnterCriticalSection SFF_gcsArray
        						ReDim Preserve SFF_gastrSelected(0 To UBound(SFF_gastrSelected) + 1 )
        						SFF_gastrSelected(UBound(SFF_gastrSelected)) = UCase$(strFilePath)
        					'LeaveCriticalSection SFF_gcsArray
        				End If
        			Next lngi
        			'also need to make sure all child directories are also added
        
        			If lngParentState = %SFF_Checked Then
        				strParentPath = strPath
        				strDir = Dir$(strPath, SFF_gudtSettings.dwdDirAttributeMask)
        				Do Until Len(strDir) = 0
        					If (GetAttr (strParentPath & strDir) And %FILE_ATTRIBUTE_DIRECTORY) Then
        						strPath = strParentPath & strDir & "\"
        						EnterCriticalSection SFF_gcsArray
        							Array Scan SFF_gastrSelected(), Collate UCase, = strPath, To lngArrayRet
        							If lngArrayRet = 0 Then
        								ReDim Preserve SFF_gastrSelected(0 To (UBound(SFF_gastrSelected) + 1))
        								SFF_gastrSelected(UBound(SFF_gastrSelected)) = UCase$(strPath)
        							End If
        						LeaveCriticalSection SFF_gcsArray
        					End If
        					strDir = Dir$
        				Loop
        			End If
        
        		ElseIf lngState = %SFF_Unchecked Then
        			'remove item from array
        			EnterCriticalSection SFF_gcsArray
        				Array Scan SFF_gastrSelected(), Collate UCase, = strCurrentItem, To lngArrayRet
        				If lngArrayRet <> 0 Then
        					Array Delete SFF_gastrSelected(lngArrayRet - 1)
        					ReDim Preserve SFF_gastrSelected(0 To (UBound(SFF_gastrSelected) - 1))
        				End If
        			LeaveCriticalSection SFF_gcsArray
        		Else
        			'item is checked and needs to be added to array
        			EnterCriticalSection SFF_gcsArray
        				ReDim Preserve SFF_gastrSelected(0 To (UBound(SFF_gastrSelected) + 1))
        				SFF_gastrSelected(UBound(SFF_gastrSelected)) = UCase$(strCurrentItem)
        			LeaveCriticalSection SFF_gcsArray
        		End If
        
        		SFF_TVChangeState (SFF_gudtSettings.hTVCurrentItem, %SFF_Greyed)
        		SFF_TVChangeParentState (SFF_gudtSettings.hTVCurrentItem, %SFF_Greyed)
        	End If
        End Function
        
        '--------------------------------------------------------------------------------
        
        Function SFF_LVFill(ByVal hItem As DWord) As Long
        ' Purpose:		fills the with all files in the directory represented by a treeview node
        ' Parameters:	hItem - treeview node representing directory
        ' Returns:		currently unused
        	Local strArray() As String
        	Local strPath As String
        	Local lngResult As Long
          	Local strFile As String
        	Local lngState As Long
        	Local lngParentState As Long
        	Local lngi As Long
        	Local dwArray() As DWord
        	Local lngArrayRet As Long
        	Local strFilePath As String
        
        	'Get the dirs
        	strPath = SFF_TVGetFullPath(hItem)
        	strFile = Dir$(strPath, SFF_gudtSettings.dwdFileAttributeMask) '%SUBDIR)
        
        	lngParentState = SFF_TVGetState (hItem)
        	lngState = lngParentState
        	If lngState = 3 Then lngState = 1
        	Do Until Len(strFile) = 0
        		If SFF_gudtSettings.LVboolCloseThread = %TRUE Then
        			Goto SFF_LVFill_Abend
        		End If
        		If (GetAttr (strPath & strFile) And Not %FILE_ATTRIBUTE_DIRECTORY) Then
        			ReDim Preserve strArray(UBound(strArray) + 1)
        			ReDim Preserve dwArray(UBound(strArray) + 1)
        			strArray(UBound(strArray)) = UCase$(strFile)
        			dwArray (UBound(strArray)) = SFF_LVInsertItem strFile, _
        			  	SFF_GetIconIndex(strPath & strFile), _
        		   		lngState
        		End If
        		strFile = Dir$
        	Loop
        
        	' set state for items already selected
        	strPath = UCase$(SFF_TVGetFullPath(hItem))
        	If lngParentState = 3 And UBound(SFF_gastrSelected()) > 0 Then
        		For lngi = 0 To UBound(strArray)
        			strFilePath = strPath & strArray(lngi)
        			Array Scan SFF_gastrSelected(), Collate UCase, = strFilePath, To lngArrayRet
        			If lngArrayRet > 0 Then
        				ListView_SetItemState SFF_gudtSettings.hListView, lngi, IndexToStateImageMask(%SFF_Checked), %LVIS_STATEIMAGEMASK
        			End If
        		Next lngi
        	End If
        SFF_LVFill_Abend :
        	Thread Close SFF_gudtSettings.hLVThread To lngResult
        	SFF_gudtSettings.hLVThread = 0
        End Function
        
        '--------------------------------------------------------------------------------
        
        Function SFF_LVInsertItem(ByVal strItem As String, ByVal lngIconIndex As Long, ByVal lngState As Long) As Long
        ' Purpose:		inserts a node into the treeview
        ' Parameters:	strItem - name of new node
        '				lngIconIndex - image to display with item
        '				lngState - state of new item
        ' Returns:		index number of the new item
        	Local udtLV_Insertudt As LV_ITEM
        
        	udtLV_Insertudt.iItem = ListView_GetItemCount(SFF_gudtSettings.hListview)
        	udtLV_Insertudt.mask = %LVIF_TEXT Or %LVIF_IMAGE Or %LVIF_STATE
        	udtLV_Insertudt.pszText = StrPtr(strItem)
        	udtLV_Insertudt.cchTextMax = Len(strItem)
        	udtLV_Insertudt.iImage = lngIconIndex
        	udtLV_Insertudt.stateMask = %LVIS_STATEIMAGEMASK
        	udtLV_Insertudt.state = INDEXTOSTATEIMAGEMASK(lngState)
        	Function = ListView_InsertItem(SFF_gudtSettings.hListview, udtLV_Insertudt)
        	' remove this line if possible
            ListView_SetItemState SFF_gudtSettings.hListview , udtLV_Insertudt.iItem, INDEXTOSTATEIMAGEMASK(lngState), %LVIS_STATEIMAGEMASK
        End Function
        
        '--------------------------------------------------------------------------------
        
        Function SFF_LVRemoveSelected (ByVal lngItem As Long) As Long
        ' Purpose:		removes treeview item from selected list
        ' Parameters:	hItem - node to remove
        ' Returns:		currently unused
        	Local strPath As String
        	Local lngArrayRet As Long
        	Local udtLV_Item As LV_ITEM
        	Local aszBuffer As Asciiz * %MAX_PATH + 1
        	Local strListviewItem As String
        
        	'get details on listview item changed
        	udtLV_Item.mask = %LVIF_TEXT
        	udtLV_Item.iItem = lngItem
        	udtLV_Item.cchTextMax = SizeOf(aszBuffer)
        	udtLV_Item.pszText = VarPtr(aszBuffer)
        	ListView_GetItem SFF_gudtSettings.hListView, udtLV_Item
        	strPath = SFF_TVGetFullPath(SFF_gudtSettings.hTVCurrentItem)
        	strListviewItem = strPath & aszBuffer
        
        	EnterCriticalSection SFF_gcsArray
        		Do
        			Array Scan SFF_gastrSelected(), Collate UCase, = strListviewItem, To lngArrayRet
        			If lngArrayRet <> 0 Then
        				Array Delete SFF_gastrSelected(lngArrayRet - 1)
        				ReDim Preserve SFF_gastrSelected(0 To (UBound(SFF_gastrSelected) - 1))
        			End If
        		Loop Until lngArrayRet = 0
        	LeaveCriticalSection SFF_gcsArray
        End Function
        
        '--------------------------------------------------------------------------------
        
        Sub SFF_LVCloseTheThread()
        ' Purpose:		sets the instruction to close the TV add nodes thread and waits until closed
        ' Parameters:	none
        	SFF_gudtSettings.LVboolCloseThread = %TRUE
        	Do Until SFF_gudtSettings.hLVThread = 0
        		Dialog DoEvents
        	Loop
        End Sub
        
        '--------------------------------------------------------------------------------
        
        Sub SFF_LVInitialise ()
        ' Purpose:		initialises the listview
        '				- sets up listview for state images
        '				- loads system file/dir images and state images
        '				- adjusts the column length of the listview to cater for a scroll bar
        '				- sets up a single column
        ' Parameters:	none
        	Local udtLV_Column As LV_COLUMN
        	Local szText AS ASCIIZ * 255
            Local lngListX As Long
            Local lngListY As Long
            Local lngPixelsX As Long
            Local lngPixelsY As Long
        	Local lngStyle As Long
        	Local udtSHFileInfo As SHFILEINFO
        	Local hImageList As DWord
        	Local strFileName As String
        	Local hIcon As DWord
        
        	' setup listview for checkboxes
            lngStyle = SendMessage(SFF_gudtSettings.hListView, %LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0)
            lngStyle = lngStyle Or %LVS_EX_CHECKBOXES ' Or %LVS_EX_FULLROWSELECT
            Call SendMessage(SFF_gudtSettings.hListView, %LVM_SETEXTENDEDLISTVIEWSTYLE, 0, ByVal lngStyle)
        
        	'load system image list for file images
        	SFF_gudtSettings.hLVImageList = SHGetFileInfo("C:\", 0, udtSHFileInfo, SizeOf(udtSHFileInfo), %SHGFI_USEFILEATTRIBUTES Or %SHGFI_SMALLICON Or %SHGFI_SYSICONINDEX)
        	ListView_SetImageList SFF_gudtSettings.hListview, SFF_gudtSettings.hLVImageList, %LVSIL_SMALL
        
        	'load custom checkbox images (2-state)
            SFF_gudtSettings.hLVStateImageList = ImageList_Create(16, 16, 1, 2, 1)
        	strFileName = ENVIRON$("TEMP") & "\" & $SFF_IconUnchecked
          	hIcon = SHGetFileInfo ByVal StrPtr(strFileName), 0, udtSHFileInfo, SIZEOF(udtSHFileInfo), %SHGFI_SMALLICON Or %SHGFI_SYSICONINDEX
          	ImageList_AddIcon SFF_gudtSettings.hLVStateImageList, ImageList_GetIcon (hIcon, udtSHFileInfo.iIcon, %ILD_NORMAL)
        	strFileName = ENVIRON$("TEMP") & "\" & $SFF_IconChecked
          	hIcon = SHGetFileInfo ByVal StrPtr(strFileName), 0, udtSHFileInfo, SIZEOF(udtSHFileInfo), %SHGFI_SMALLICON Or %SHGFI_SYSICONINDEX
          	ImageList_AddIcon SFF_gudtSettings.hLVStateImageList, ImageList_GetIcon (hIcon, udtSHFileInfo.iIcon, %ILD_NORMAL)
        	ListView_SetImageList SFF_gudtSettings.hListview, SFF_gudtSettings.hLVStateImageList, %TVSIL_STATE
        
            ' Adjust for vertical scrollbar
            Control Get Size SFF_gudtSettings.hDlg, %SFF_ID_lvwFileList To lngListX, lngListY
            Dialog Units SFF_gudtSettings.hDlg, lngListX, lngListY To Pixels lngPixelsX, lngPixelsY
            lngPixelsX = lngPixelsX - 4 - GetSystemMetrics(%SM_CXVSCROLL)
        
            ' Define column
            szText = ""
            udtLV_Column.mask = %LVCF_FMT OR %LVCF_WIDTH OR %LVCF_TEXT
            udtLV_Column.fmt = %LVCFMT_LEFT
            udtLV_Column.cx = lngPixelsX
            udtLV_Column.cchTextMax = SizeOf(szText)
            udtLV_Column.pszText = VarPtr(szText)
            SendMessage SFF_gudtSettings.hListView, %LVM_INSERTCOLUMN, 0, VarPtr(udtLV_Column)
        End sub
        
        '--------------------------------------------------------------------------------
        
        Sub SFF_LVStartFillThread(ByVal hItem As DWord)
        ' Purpose:		starts separate thread to fill listview for a selected dir node
        ' Parameters:	hItem - node/dir to display in listview
        ' Notes:		starts a new thread to fill the listview.  Thread will close
        '				when finished or when instructed to close
        	Local udtLV_Column As LV_COLUMN
        	Local szText AS ASCIIZ * 100
        
        	If SFF_gudtSettings.hLVThread Then
        		If hItem = SFF_gudtSettings.hLVThreadItem Then Exit Sub
        		'previous expansion not finished ... so stop and delete
        		SFF_LVCloseTheThread
        	End If
        	'This should be safe, as selected files are not added/removed in displaying listview
        	ListView_DeleteAllItems SFF_gudtSettings.hListView
        
        	'display path of dir node in listview column title
            szText = SFF_TVGetFullPath (hItem)
            udtLV_Column.mask = %LVCF_TEXT
            udtLV_Column.pszText = VarPtr(szText)
            SendMessage SFF_gudtSettings.hListView, %LVM_SETCOLUMN, 0, VarPtr(udtLV_Column)
        
        	SFF_gudtSettings.hLVThreadItem = hItem
        	SFF_gudtSettings.LVboolCloseThread = %FALSE
        	Thread CREATE SFF_LVFill(SFF_gudtSettings.hLVThreadItem) To SFF_gudtSettings.hLVThread
        End Sub
        
        '--------------------------------------------------------------------------------
        ------------------
        Stuart Sanders
        stuart at bitshk dot com
        www.bitshk.com

        Comment

        Working...
        X
        😀
        🥰
        🤢
        😎
        😡
        👍
        👎