Browse for Folder: Difference between revisions

From NSIS Wiki
Jump to navigationJump to search
mNo edit summary
(Disabled callbacks on everything except NSIS 3 x86)
Line 1: Line 1:
{{PageAuthor|Anders}}
{{PageAuthor|Anders}}


This function displays the folder browser UI so the user can choose a file-system folder. You can specify a root folder or use "" for the default root (deskop). You can also set the initial selection if desired.
This function displays the folder browser UI so the user can choose a file-system folder. You can specify a root folder or use "" for the default root (deskop). You can also set the initial selection if desired if you are using NSIS 3 32-bit.


== Code ==
== Code ==
Line 14: Line 14:
!define TVGN_CARET 0x9
!define TVGN_CARET 0x9
!define BFFM_INITIALIZED 1
!define BFFM_INITIALIZED 1
!define BFFM_VALIDATEFAILEDA 3
!define BFFM_VALIDATEFAILEDW 4
!if "${NSIS_CHAR_SIZE}" > 1
!if "${NSIS_CHAR_SIZE}" > 1
!define BFFM_VALIDATEFAILED ${BFFM_VALIDATEFAILEDW}
!define /math BFFM_SETSELECTION ${WM_USER} + 103
!define /math BFFM_SETSELECTION ${WM_USER} + 103
!else
!else
!define BFFM_VALIDATEFAILED ${BFFM_VALIDATEFAILEDA}
!define /math BFFM_SETSELECTION ${WM_USER} + 102
!define /math BFFM_SETSELECTION ${WM_USER} + 102
!endif
!endif
 
 
Function SHParseDisplayName ; NSIS 2.51+ INPUT:Path OUTPUT:Pidl
Function SHParseDisplayName ; NSIS 2.51+ INPUT:Path OUTPUT:Pidl
Exch $1
Exch $1
Line 32: Line 36:
Exch $1
Exch $1
FunctionEnd
FunctionEnd
 
Function BrowseForFolder ; NSIS 2.51+ INPUT:RootPath, HeadingText, InitialPathSelection OUTPUT:Path
Function BrowseForFolder ; NSIS 2.51+ INPUT:RootPath, HeadingText, InitialPathSelection OUTPUT:Path
System::Store S
System::Store S
Line 48: Line 52:
!insertmacro BrowseForFolder_PathToPidl $1 $6
!insertmacro BrowseForFolder_PathToPidl $1 $6
!insertmacro BrowseForFolder_PathToPidl $3 $5
!insertmacro BrowseForFolder_PathToPidl $3 $5
!if "${NSIS_PTR_SIZE}" > 4 ; Callbacks currently not supported on AMD64
StrCpy $4 "p0"
StrCpy $R8 ""
StrCpy $R9 0
!else if ! 0b1 ; And 2.51 has buggy callback numbers
StrCpy $4 "p0"
StrCpy $R8 ""
StrCpy $R9 0
!else
System::Get "(p.R1, i.R2, p, p.R3)i R8R8" ; BFFCALLBACK
System::Get "(p.R1, i.R2, p, p.R3)i R8R8" ; BFFCALLBACK
Pop $R9
Pop $R9
StrCpy $4 "kR9"
!endif
System::Call '*(&t261 "")p.r7' ; pszDisplayName buffer
System::Call '*(&t261 "")p.r7' ; pszDisplayName buffer
System::Call '*(p $hwndparent, pr6, pr7, t r2, i 0x41, kR9, pr5, i)p.r8' ; BROWSEINFO struct
System::Call '*(p $hwndparent, pr6, pr7, t r2, i 0x41, $4, pr5, i)p.r8' ; BROWSEINFO struct  
System::Call 'SHELL32::SHBrowseForFolder(t)(pr8)p.r9' ; Using the (t) hack to force the correct A/W function
System::Call 'SHELL32::SHBrowseForFolder(t)(pr8)p.r9' ; Using the (t) hack to force the correct A/W function
BFFCALLBACK_loop:
BFFCALLBACK_loop:
Line 73: Line 88:
${EndIf}
${EndIf}
StrCpy $R8 0 ; Yep, the return value is in the same place as the callback id
StrCpy $R8 0 ; Yep, the return value is in the same place as the callback id
${IfThen} $R2 = ${BFFM_VALIDATEFAILED} ${|} StrCpy $R8 1 ${|}
System::Call $R9
System::Call $R9
goto BFFCALLBACK_loop
goto BFFCALLBACK_loop

Revision as of 23:54, 15 September 2016

Author: Anders (talk, contrib)


This function displays the folder browser UI so the user can choose a file-system folder. You can specify a root folder or use "" for the default root (deskop). You can also set the initial selection if desired if you are using NSIS 3 32-bit.

Code

!include LogicLib.nsh
!include WinMessages.nsh ; WM_USER
!define TV_FIRST  0x1100
!define /math TVM_GETNEXTITEM ${TV_FIRST} + 10
!define /math TVM_SELECTITEM ${TV_FIRST} + 11
!define /math TVM_ENSUREVISIBLE ${TV_FIRST} + 20
!define TVGN_FIRSTVISIBLE 0x5
!define TVGN_CARET 0x9
!define BFFM_INITIALIZED 1
!define BFFM_VALIDATEFAILEDA 3
!define BFFM_VALIDATEFAILEDW 4
!if "${NSIS_CHAR_SIZE}" > 1
!define BFFM_VALIDATEFAILED ${BFFM_VALIDATEFAILEDW}
!define /math BFFM_SETSELECTION ${WM_USER} + 103
!else
!define BFFM_VALIDATEFAILED ${BFFM_VALIDATEFAILEDA}
!define /math BFFM_SETSELECTION ${WM_USER} + 102
!endif
 
 
Function SHParseDisplayName ; NSIS 2.51+ INPUT:Path OUTPUT:Pidl
Exch $1
Push $2
System::Call 'SHELL32::SHParseDisplayName(w r1, p 0, *p 0r2, i 0, *i 0)i'
${If} $2 P= 0
	System::Call 'SHELL32::ILCreateFromPath(t r1)p.r2' ; SHParseDisplayName is XP+, this works everywhere but is not as clever
${EndIf}
StrCpy $1 $2
Pop $2
Exch $1
FunctionEnd
 
Function BrowseForFolder ; NSIS 2.51+ INPUT:RootPath, HeadingText, InitialPathSelection OUTPUT:Path
System::Store S
Pop $3 ; InitialPathSelection or ""
Pop $2 ; HeadingText
Pop $1 ; RootPath or ""
!macro BrowseForFolder_PathToPidl Path Pidl
StrCpy ${Pidl} ""
${If} "${Path}" != ""
	Push "${Path}"
	Call SHParseDisplayName
	Pop ${Pidl}
${EndIf}
!macroend
!insertmacro BrowseForFolder_PathToPidl $1 $6
!insertmacro BrowseForFolder_PathToPidl $3 $5
!if "${NSIS_PTR_SIZE}" > 4 ; Callbacks currently not supported on AMD64
StrCpy $4 "p0"
StrCpy $R8 ""
StrCpy $R9 0
!else if ! 0b1 ; And 2.51 has buggy callback numbers
StrCpy $4 "p0"
StrCpy $R8 ""
StrCpy $R9 0
!else
System::Get "(p.R1, i.R2, p, p.R3)i R8R8" ; BFFCALLBACK
Pop $R9
StrCpy $4 "kR9"
!endif
System::Call '*(&t261 "")p.r7' ; pszDisplayName buffer
System::Call '*(p $hwndparent, pr6, pr7, t r2, i 0x41, $4, pr5, i)p.r8' ; BROWSEINFO struct 
System::Call 'SHELL32::SHBrowseForFolder(t)(pr8)p.r9' ; Using the (t) hack to force the correct A/W function
BFFCALLBACK_loop:
	StrCmp $R8 "callback1" 0 BFFCALLBACK_done
	${If} $R2 = ${BFFM_INITIALIZED}
	${AndIf} $R3 P<> 0
		SendMessage $R1 ${BFFM_SETSELECTION} 0 $R3
		System::Store S
		StrCpy $2 0
		StrCpy $3 0
		loop: ; BFFM_SETSELECTION is buggy and does not scroll to the new item so we find the treeview and do it manually
			FindWindow $2 "" "" $R1 $2 ; Assuming SysTreeView32 is a grandchild when using BIF_NEWDIALOGSTYLE
			IntCmp 0 $2 done
			FindWindow $3 "SysTreeView32" "" $2
			IntCmp 0 $3 loop
			SendMessage $3 ${TVM_GETNEXTITEM} ${TVGN_CARET} 0 $4
			IntCmp 0 $3 done
			System::Call 'USER32::PostMessage(p$3,i${TVM_ENSUREVISIBLE},p0,p$4)'
		done:
		System::Store L
	${EndIf}
	StrCpy $R8 0 ; Yep, the return value is in the same place as the callback id
	${IfThen} $R2 = ${BFFM_VALIDATEFAILED} ${|} StrCpy $R8 1 ${|}
	System::Call $R9
	goto BFFCALLBACK_loop
BFFCALLBACK_done:
System::Free $R9
${If} $9 Z<> 0
	System::Call 'SHELL32::SHGetPathFromIDList(p r9, t.s)i'
	System::Call 'OLE32::CoTaskMemFree(p r9)'
${Else}
	Push "" ; Error/cancel, return empty string
${EndIf}
System::Call 'OLE32::CoTaskMemFree(p r5)'
System::Call 'OLE32::CoTaskMemFree(p r6)'
System::Free $7
System::Free $8
System::Store L
FunctionEnd

Example

Section
Push "$Profile"
Push "Hello World"
Push "$AppData"
Call BrowseForFolder
Pop $0
DetailPrint BrowseForFolder=$0
SectionEnd