#!/usr/bin/env python # -*- coding: utf-8 -*- # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab # to work around tk_chooseDirectory not properly returning unicode paths on Windows # need to use a dialog that can be hacked up to actually return full unicode paths # originally based on AskFolder from EasyDialogs for Windows but modified to fix it # to actually use unicode for path # The original license for EasyDialogs is as follows # # Copyright (c) 2003-2005 Jimmy Retzlaff # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. """ AskFolder(...) -- Ask the user to select a folder Windows specific """ import os import ctypes from ctypes import POINTER, byref, cdll, c_int, windll from ctypes.wintypes import LPCWSTR, LPWSTR import ctypes.wintypes as wintypes __all__ = ['AskFolder'] # Load required Windows DLLs ole32 = ctypes.windll.ole32 shell32 = ctypes.windll.shell32 user32 = ctypes.windll.user32 # Windows Constants BFFM_INITIALIZED = 1 BFFM_SETOKTEXT = 1129 BFFM_SETSELECTIONA = 1126 BFFM_SETSELECTIONW = 1127 BIF_EDITBOX = 16 BS_DEFPUSHBUTTON = 1 CB_ADDSTRING = 323 CB_GETCURSEL = 327 CB_SETCURSEL = 334 CDM_SETCONTROLTEXT = 1128 EM_GETLINECOUNT = 186 EM_GETMARGINS = 212 EM_POSFROMCHAR = 214 EM_SETSEL = 177 GWL_STYLE = -16 IDC_STATIC = -1 IDCANCEL = 2 IDNO = 7 IDOK = 1 IDYES = 6 MAX_PATH = 260 OFN_ALLOWMULTISELECT = 512 OFN_ENABLEHOOK = 32 OFN_ENABLESIZING = 8388608 OFN_ENABLETEMPLATEHANDLE = 128 OFN_EXPLORER = 524288 OFN_OVERWRITEPROMPT = 2 OPENFILENAME_SIZE_VERSION_400 = 76 PBM_GETPOS = 1032 PBM_SETMARQUEE = 1034 PBM_SETPOS = 1026 PBM_SETRANGE = 1025 PBM_SETRANGE32 = 1030 PBS_MARQUEE = 8 PM_REMOVE = 1 SW_HIDE = 0 SW_SHOW = 5 SW_SHOWNORMAL = 1 SWP_NOACTIVATE = 16 SWP_NOMOVE = 2 SWP_NOSIZE = 1 SWP_NOZORDER = 4 VER_PLATFORM_WIN32_NT = 2 WM_COMMAND = 273 WM_GETTEXT = 13 WM_GETTEXTLENGTH = 14 WM_INITDIALOG = 272 WM_NOTIFY = 78 # Windows function prototypes BrowseCallbackProc = ctypes.WINFUNCTYPE(ctypes.c_int, wintypes.HWND, ctypes.c_uint, wintypes.LPARAM, wintypes.LPARAM) # Windows types LPCTSTR = ctypes.c_char_p LPTSTR = ctypes.c_char_p LPVOID = ctypes.c_voidp TCHAR = ctypes.c_char class BROWSEINFO(ctypes.Structure): _fields_ = [ ("hwndOwner", wintypes.HWND), ("pidlRoot", LPVOID), ("pszDisplayName", LPTSTR), ("lpszTitle", LPCTSTR), ("ulFlags", ctypes.c_uint), ("lpfn", BrowseCallbackProc), ("lParam", wintypes.LPARAM), ("iImage", ctypes.c_int) ] # Utilities def CenterWindow(hwnd): desktopRect = GetWindowRect(user32.GetDesktopWindow()) myRect = GetWindowRect(hwnd) x = width(desktopRect) // 2 - width(myRect) // 2 y = height(desktopRect) // 2 - height(myRect) // 2 user32.SetWindowPos(hwnd, 0, desktopRect.left + x, desktopRect.top + y, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER ) def GetWindowRect(hwnd): rect = wintypes.RECT() user32.GetWindowRect(hwnd, ctypes.byref(rect)) return rect def width(rect): return rect.right-rect.left def height(rect): return rect.bottom-rect.top def AskFolder( message=None, version=None, defaultLocation=None, location=None, windowTitle=None, actionButtonLabel=None, cancelButtonLabel=None, multiple=None): """Display a dialog asking the user for select a folder. modified to use unicode strings as much as possible returns unicode path """ def BrowseCallback(hwnd, uMsg, lParam, lpData): if uMsg == BFFM_INITIALIZED: if actionButtonLabel: label = unicode(actionButtonLabel, errors='replace') user32.SendMessageW(hwnd, BFFM_SETOKTEXT, 0, label) if cancelButtonLabel: label = unicode(cancelButtonLabel, errors='replace') cancelButton = user32.GetDlgItem(hwnd, IDCANCEL) if cancelButton: user32.SetWindowTextW(cancelButton, label) if windowTitle: title = unicode(windowTitle, erros='replace') user32.SetWindowTextW(hwnd, title) if defaultLocation: user32.SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, defaultLocation.replace('/', '\\')) if location: x, y = location desktopRect = wintypes.RECT() user32.GetWindowRect(0, ctypes.byref(desktopRect)) user32.SetWindowPos(hwnd, 0, desktopRect.left + x, desktopRect.top + y, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER) else: CenterWindow(hwnd) return 0 # This next line is needed to prevent gc of the callback callback = BrowseCallbackProc(BrowseCallback) browseInfo = BROWSEINFO() browseInfo.pszDisplayName = ctypes.c_char_p('\0' * (MAX_PATH+1)) browseInfo.lpszTitle = message browseInfo.lpfn = callback pidl = shell32.SHBrowseForFolder(ctypes.byref(browseInfo)) if not pidl: result = None else: path = LPCWSTR(u" " * (MAX_PATH+1)) shell32.SHGetPathFromIDListW(pidl, path) ole32.CoTaskMemFree(pidl) result = path.value return result