#@PydevCodeAnalysisIgnore """Easy to use dialogs. Message(msg) -- display a message and an OK button. AskString(prompt, default) -- ask for a string, display OK and Cancel buttons. AskPassword(prompt, default) -- like AskString(), but shows text as bullets. AskYesNoCancel(question, default) -- display a question and Yes, No and Cancel buttons. GetArgv(optionlist, commandlist) -- fill a sys.argv-like list using a dialog AskFileForOpen(...) -- Ask the user for an existing file AskFileForSave(...) -- Ask the user for an output file AskFolder(...) -- Ask the user to select a folder bar = Progress(label, maxvalue) -- Display a progress bar bar.set(value) -- Set value bar.inc( *amount ) -- increment value by amount (default=1) bar.label( *newlabel ) -- get or set text label. More documentation in each function. This module uses DLOG resources 260 and on. Based upon STDWIN dialogs with the same names and functions. """ from __future__ import division from __future__ import print_function import os import ctypes import ctypes.wintypes as wintypes from EasyDialogsResources import resources __all__ = ['Message', 'AskString', 'AskPassword', 'AskYesNoCancel', 'GetArgv', 'AskFileForOpen', 'AskFileForSave', 'AskFolder', 'ProgressBar'] # Load required Windows DLLs comdlg32 = ctypes.windll.comdlg32 kernel32 = ctypes.windll.kernel32 ole32 = ctypes.windll.ole32 shell32 = ctypes.windll.shell32 user32 = ctypes.windll.user32 # Windows Constants BFFM_INITIALIZED = 1 BFFM_SETOKTEXT = 1129 BFFM_SETSELECTIONA = 1126 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) DialogProc = ctypes.WINFUNCTYPE(ctypes.c_int, wintypes.HWND, ctypes.c_uint, wintypes.WPARAM, wintypes.LPARAM) EnumChildProc = ctypes.WINFUNCTYPE(ctypes.c_int, wintypes.HWND, wintypes.LPARAM) LPOFNHOOKPROC = ctypes.WINFUNCTYPE(ctypes.c_int, wintypes.HWND, ctypes.c_uint, wintypes.WPARAM, wintypes.LPARAM) # Windows types LPCTSTR = ctypes.c_char_p LPTSTR = ctypes.c_char_p LPVOID = ctypes.c_voidp TCHAR = ctypes.c_char class OSVERSIONINFO(ctypes.Structure): _fields_ = [ ("dwOSVersionInfoSize", wintypes.DWORD), ("dwMajorVersion", wintypes.DWORD), ("dwMinorVersion", wintypes.DWORD), ("dwBuildNumber", wintypes.DWORD), ("dwPlatformId", wintypes.DWORD), ("szCSDVersion", TCHAR*128) ] def __init__(self): ctypes.Structure.__init__(self) self.dwOSVersionInfoSize = ctypes.sizeof(self) class OPENFILENAME(ctypes.Structure): _fields_ = [ ("lStructSize", wintypes.DWORD), ("hwndOwner", wintypes.HWND), ("hInstance", LPCTSTR), ("lpstrFilter", LPCTSTR), ("lpstrCustomFilter", LPTSTR), ("nMaxCustFilter", wintypes.DWORD), ("nFilterIndex", wintypes.DWORD), ("lpstrFile", LPTSTR), ("nMaxFile", wintypes.DWORD), ("lpstrFileTitle", LPTSTR), ("nMaxFileTitle", wintypes.DWORD), ("lpstrInitialDir", LPCTSTR), ("lpstrTitle", LPCTSTR), ("flags", wintypes.DWORD), ("nFileOffset", wintypes.WORD), ("nFileExtension", wintypes.WORD), ("lpstrDefExt", LPCTSTR), ("lCustData", wintypes.LPARAM), ("lpfnHook", LPOFNHOOKPROC), ("lpTemplateName", LPCTSTR), ("pvReserved", LPVOID), ("dwReserved", wintypes.DWORD), ("FlagsEx", wintypes.DWORD) ] def __init__(self): ctypes.Structure.__init__(self) versionInfo = OSVERSIONINFO() kernel32.GetVersionExA(ctypes.byref(versionInfo)) if versionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT: if (versionInfo.dwMajorVersion, versionInfo.dwMinorVersion) == (4, 0): self.lStructSize = OPENFILENAME_SIZE_VERSION_400 elif versionInfo.dwMajorVersion >= 5: self.lStructSize = ctypes.sizeof(OPENFILENAME) else: raise RuntimeError('Windows NT 4.0 or later is required.') else: self.lStructSize = ctypes.sizeof(OPENFILENAME) - 12 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) ] # Windows macro emulation def HIWORD(x): return (x >> 16) & 0xffff def LOWORD(x): return x & 0xffff def MAKELONG(a, b): return (a & 0xffff) | ((b & 0xffff) << 16) MAKELPARAM = MAKELONG # 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 EnumChildWindows(hwnd): childWindows = [] def enumChildProc(hwnd, lParam): childWindows.append(hwnd) return True enumChildProc = EnumChildProc(enumChildProc) user32.EnumChildWindows(hwnd, enumChildProc, 0) return childWindows def GetText(hwnd): length = user32.SendMessageA(hwnd, WM_GETTEXTLENGTH, 0, 0) + 1 buffer = ctypes.c_char_p('\0' * length) user32.SendMessageA(hwnd, WM_GETTEXT, length, buffer) return buffer.value 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 # Mac API emulation def AutoSizeDialog(dialog, center=True): classNameBuffer = ctypes.c_buffer(1024) children = [] editBoxes = [] for child in EnumChildWindows(dialog): children.append(child) user32.GetClassNameA(child, classNameBuffer, 1024) if classNameBuffer.value.lower() == 'edit': editBoxes.append(child) editBoxes.sort(lambda x, y: cmp(GetWindowRect(x).top, GetWindowRect(y).top)) for editBox in editBoxes: text = GetText(editBox) user32.SetWindowTextA(editBox, 'X\r\nX') if user32.SendMessageA(editBox, EM_GETLINECOUNT, 0, 0) < 2: user32.SetWindowTextA(editBox, text) else: lineHeight = HIWORD(user32.SendMessageA(editBox, EM_POSFROMCHAR, 3, 0)) - HIWORD(user32.SendMessageA(editBox, EM_POSFROMCHAR, 0, 0)) user32.SetWindowTextA(editBox, text) newHeight = user32.SendMessageA(editBox, EM_GETLINECOUNT, 0, 0)*lineHeight + 2*LOWORD(user32.SendMessageA(editBox, EM_GETMARGINS, 0, 0)) editBoxRect = GetWindowRect(editBox) oldHeight = height(editBoxRect) if newHeight != oldHeight: topLeft = wintypes.POINT(editBoxRect.left, editBoxRect.top) user32.ScreenToClient(dialog, ctypes.byref(topLeft)) user32.MoveWindow(editBox, topLeft.x, topLeft.y, width(editBoxRect), newHeight, False) for child in children: childRect = GetWindowRect(child) if childRect.top >= editBoxRect.bottom: topLeft = wintypes.POINT(childRect.left, childRect.top) user32.ScreenToClient(dialog, ctypes.byref(topLeft)) user32.MoveWindow(child, topLeft.x, topLeft.y + newHeight - oldHeight, width(childRect), height(childRect), False ) dRect = GetWindowRect(dialog) dRect.bottom += newHeight - oldHeight topLeft = wintypes.POINT(dRect.left, dRect.top) user32.MoveWindow(dialog, topLeft.x, topLeft.y, width(dRect), height(dRect), False) user32.InvalidateRect(dialog, None, True) if center: CenterWindow(dialog) # Mac EasyDialogs emulation def crlf2lf(text): if '\r\n' in text: text = '\n'.join(text.split('\r\n')) return text def lf2crlf(text): if '\n' in text: text = '\r\n'.join(text.split('\n')) if len(text) > 253: text = text[:253] + '\205' return text def Message(msg, id=260, ok=None): """Display a MESSAGE string. Return when the user clicks the OK button or presses Return. The MESSAGE string can be at most 255 characters long. """ def DlgProc(hwnd, uMsg, wParam, lParam): if uMsg == WM_INITDIALOG: user32.SetWindowTextA(user32.GetDlgItem(hwnd, 1002), lf2crlf(msg)) if ok: user32.SetWindowTextA(user32.GetDlgItem(hwnd, IDOK), ok) AutoSizeDialog(hwnd) return False if uMsg == WM_COMMAND and LOWORD(wParam) == IDOK: user32.EndDialog(hwnd, IDOK) return True if uMsg == WM_COMMAND and LOWORD(wParam) == IDCANCEL: user32.EndDialog(hwnd, IDCANCEL) return True else: return False # This next line is needed to prevent gc of the callback myDialogProc = DialogProc(DlgProc) return user32.DialogBoxIndirectParamA(0, resources[id], 0, myDialogProc, 0) def AskString(prompt, default = '', id=261, ok=None, cancel=None): """Display a PROMPT string and a text entry field with a DEFAULT string. Return the contents of the text entry field when the user clicks the OK button or presses Return. Return None when the user clicks the Cancel button. If omitted, DEFAULT is empty. The PROMPT and DEFAULT strings, as well as the return value, can be at most 255 characters long. """ result = [None] def DlgProc(hwnd, uMsg, wParam, lParam): if uMsg == WM_INITDIALOG: user32.SetWindowTextA(user32.GetDlgItem(hwnd, 1003), lf2crlf(prompt)) user32.SetWindowTextA(user32.GetDlgItem(hwnd, 1004), lf2crlf(default)) if ok: user32.SetWindowTextA(user32.GetDlgItem(hwnd, IDOK), ok) if cancel: user32.SetWindowTextA(user32.GetDlgItem(hwnd, IDCANCEL), cancel) AutoSizeDialog(hwnd) return False if uMsg == WM_COMMAND and LOWORD(wParam) == IDOK: result[0] = crlf2lf(GetText(user32.GetDlgItem(hwnd, 1004))) user32.EndDialog(hwnd, IDOK) return True if uMsg == WM_COMMAND and LOWORD(wParam) == IDCANCEL: user32.EndDialog(hwnd, IDCANCEL) return True else: return False # This next line is needed to prevent gc of the callback myDialogProc = DialogProc(DlgProc) user32.DialogBoxIndirectParamA(0, resources[id], 0, myDialogProc, 0) return result[0] def AskPassword(prompt, default='', id=264, ok=None, cancel=None): """Display a PROMPT string and a text entry field with a DEFAULT string. The string is displayed as bullets only. Return the contents of the text entry field when the user clicks the OK button or presses Return. Return None when the user clicks the Cancel button. If omitted, DEFAULT is empty. The PROMPT and DEFAULT strings, as well as the return value, can be at most 255 characters long. """ return AskString(prompt, default=default, id=id, ok=ok, cancel=cancel) def AskYesNoCancel(question, default=0, yes=None, no=None, cancel=None, id=262): """Display a QUESTION string which can be answered with Yes or No. Return 1 when the user clicks the Yes button. Return 0 when the user clicks the No button. Return -1 when the user clicks the Cancel button. When the user presses Return, the DEFAULT value is returned. If omitted, this is 0 (No). The QUESTION string can be at most 255 characters. """ def DlgProc(hwnd, uMsg, wParam, lParam): if uMsg == WM_INITDIALOG: user32.SetWindowTextA(user32.GetDlgItem(hwnd, 1005), lf2crlf(question)) if yes is not None: button = user32.GetDlgItem(hwnd, IDYES) if yes == '': user32.ShowWindow(button, False) else: user32.SetWindowTextA(button, yes) if no is not None: button = user32.GetDlgItem(hwnd, IDNO) if no == '': user32.ShowWindow(button, False) else: user32.SetWindowTextA(button, no) if cancel is not None: button = user32.GetDlgItem(hwnd, IDCANCEL) if cancel == '': user32.ShowWindow(button, False) else: user32.SetWindowTextA(button, cancel) button = None if default == 1: button = user32.GetDlgItem(hwnd, IDYES) elif default == 0: button = user32.GetDlgItem(hwnd, IDNO) elif default == -1: button = user32.GetDlgItem(hwnd, IDCANCEL) if button: style = user32.GetWindowLongA(button, GWL_STYLE) user32.SetWindowLongA(button, GWL_STYLE, style | BS_DEFPUSHBUTTON) user32.SetFocus(button) AutoSizeDialog(hwnd) return False if uMsg == WM_COMMAND and LOWORD(wParam) == IDYES: user32.EndDialog(hwnd, 1) return True elif uMsg == WM_COMMAND and LOWORD(wParam) == IDNO: user32.EndDialog(hwnd, 0) return True elif uMsg == WM_COMMAND and LOWORD(wParam) == IDCANCEL: user32.EndDialog(hwnd, -1) return True else: return False # This next line is needed to prevent gc of the callback myDialogProc = DialogProc(DlgProc) return user32.DialogBoxIndirectParamA(0, resources[id], 0, myDialogProc, 0) class ProgressBar: def __init__(self, title="Working...", maxval=0, label="", id=263): self._label = lf2crlf(label) def DlgProc(hwnd, uMsg, wParam, lParam): if uMsg == WM_INITDIALOG: user32.SetWindowTextA(hwnd, title) user32.SetWindowTextA(user32.GetDlgItem(hwnd, 1002), DlgProc.label) if uMsg == WM_COMMAND and LOWORD(wParam) == IDOK: user32.EndDialog(hwnd, IDOK) return True if uMsg == WM_COMMAND and LOWORD(wParam) == IDCANCEL: user32.EndDialog(hwnd, IDCANCEL) return True else: return False # Why a function attribute? Originally nested scopes # were used to get at self._label. This caused a cyclic reference # and since ProgressBar has a __del__ method (which closes the # window) it would never be collected (and hence the window # wouldn't close). Think of label as being like self. When this # function it used it will be used as a bound method where the # label is the object. DlgProc.label = self._label # This next line is needed to prevent gc of the callback self.wrappedDlgProc = DialogProc(DlgProc) self.hwnd = user32.CreateDialogIndirectParamA(0, resources[id], 0, self.wrappedDlgProc, 0) AutoSizeDialog(self.hwnd) user32.ShowWindow(self.hwnd, SW_SHOWNORMAL) self.set(0, maxval) def __del__(self): user32.DestroyWindow(self.hwnd) def title(self, newstr=""): """title(text) - Set title of progress window""" user32.SetWindowTextA(self.hwnd, newstr) self._pump() def label( self, *newstr ): """label(text) - Set text in progress box""" if newstr: self._label = lf2crlf(newstr[0]) user32.SetWindowTextA(user32.GetDlgItem(self.hwnd, 1002), self._label) AutoSizeDialog(self.hwnd, center=False) self._pump() def _pump(self): msg = wintypes.MSG() while user32.PeekMessageA(ctypes.byref(msg), 0, 0, 0, PM_REMOVE): if not user32.IsDialogMessage(self.hwnd, ctypes.byref(msg)): user32.TranslateMessage(ctypes.byref(msg)) user32.DispatchMessageA(ctypes.byref(msg)) if not user32.IsWindowVisible(self.hwnd): raise KeyboardInterrupt def _update(self, value): maxval = self.maxval progbar = user32.GetDlgItem(self.hwnd, 1003) if maxval == 0: # an indeterminate bar # make the bar grow and wrap around in case Marquee bars aren't supported pass pos = user32.SendMessageA(progbar, PBM_GETPOS, 0, 0) user32.SendMessageA(progbar, PBM_SETRANGE, 0, MAKELPARAM(0, 10)) user32.SendMessageA(progbar, PBM_SETPOS, (pos+1) % 10, 0) else: # a determinate bar if maxval > 32767: value = int(value/(maxval/32767.0)) maxval = 32767 maxval = int(maxval) value = int(value) if maxval > 65535: user32.SendMessageA(progbar, PBM_SETRANGE32, 0, maxval) else: user32.SendMessageA(progbar, PBM_SETRANGE, 0, MAKELPARAM(0, maxval)) user32.SendMessageA(progbar, PBM_SETPOS, value, 0) # set the bar length self._pump() def set(self, value, max=None): """set(value) - Set progress bar position""" if max != None: self.maxval = max bar = user32.GetDlgItem(self.hwnd, 1003) if max <= 0: # indeterminate bar self.maxval = 0 style = user32.GetWindowLongA(bar, GWL_STYLE) if not (style & PBS_MARQUEE): user32.SetWindowLongA(bar, GWL_STYLE, style | PBS_MARQUEE) user32.SendMessageA(bar, PBM_SETMARQUEE, 1, 10) else: # determinate bar user32.SendMessageA(bar, PBM_SETMARQUEE, 0, 0) style = user32.GetWindowLongA(bar, GWL_STYLE) if style & PBS_MARQUEE: user32.SetWindowLongA(bar, GWL_STYLE, style - PBS_MARQUEE) if value < 0: value = 0 elif value > self.maxval: value = self.maxval self.curval = value self._update(value) def inc(self, n=1): """inc(amt) - Increment progress bar position""" self.set(self.curval + n) def AskFileForOpen( message=None, typeList=None, # From here on the order is not documented version=None, defaultLocation=None, dialogOptionFlags=None, location=None, clientName=None, windowTitle=None, actionButtonLabel=None, cancelButtonLabel=None, preferenceKey=None, popupExtension=None, eventProc=None, previewProc=None, filterProc=None, wanted=None, multiple=None, # the following are for implementation use only defaultfn='', defaultext=None, fn=comdlg32.GetOpenFileNameA): """Display a dialog asking the user for a file to open. wanted is the return type wanted: FSSpec, FSRef, unicode or string (default) the other arguments can be looked up in Apple's Navigation Services documentation Windows differences: typeList is used for the same purpose, but file type handling is different between Windows and Mac, so the form of this argument is different. In an attempt to remain as similar as possible, a list of extensions can be supplied (e.g., ['*', 'txt', 'bat']). A more complete form is also allowed: [('All Files (*.*)', '*.*'), ('C Files (*.c, *.h)', '*.c;*.h')]. The first item in each tuple is the text description presented to the user. The second item in each tuple is a semi-colon seperated list of standard Windows wildcard patterns that will match files described in the text description. The folowing parameters are ignored on Windows: clientName, dialogOptionFlags, eventProc, filterProc, popupExtension, preferenceKey, previewProc, version, wanted """ ofn = OPENFILENAME() filename = defaultfn + '\0'*1024 ofn.lpstrFile = filename ofn.nMaxFile = len(filename) ofn.flags = OFN_ENABLEHOOK | OFN_EXPLORER | OFN_ENABLESIZING | OFN_OVERWRITEPROMPT if multiple: ofn.flags |= OFN_ALLOWMULTISELECT ofn.lpstrTitle = windowTitle ofn.lpstrInitialDir = defaultLocation if typeList and filter(None, typeList): lpstrFilter = '' for typeSpec in typeList: try: lpstrFilter += '*.'+typeSpec+'\0*.'+typeSpec+'\0' if defaultext and not ofn.lpstrDefExt: ofn.lpstrDefExt = typeSpec except TypeError: lpstrFilter += '%s\0%s\0' % typeSpec if defaultext and not ofn.lpstrDefExt: ext = os.path.splitext(typeSpec[1])[1].lstrip('.') if ext != '*': ofn.lpstrDefExt = ext ofn.lpstrFilter = lpstrFilter + '\0' else: ofn.lpstrFilter = 'All Files (*.*)\0*.*\0\0' if message: ofn.flags |= OFN_ENABLETEMPLATEHANDLE ofn.hInstance = resources[270] hookProcInitDone = [] def hookProc(hdlg, uiMsg, wParam, lParam): if uiMsg == WM_INITDIALOG: dialog = user32.GetParent(hdlg) if message: handle = user32.FindWindowExA(hdlg, 0, 0, 0) user32.SetWindowTextA(handle, message) if actionButtonLabel: user32.SendMessageA(dialog, CDM_SETCONTROLTEXT, IDOK, actionButtonLabel) if cancelButtonLabel: user32.SendMessageA(dialog, CDM_SETCONTROLTEXT, IDCANCEL, cancelButtonLabel) elif uiMsg == WM_NOTIFY: if not hookProcInitDone: hookProcInitDone.append(True) dialog = user32.GetParent(hdlg) desktop = user32.GetDesktopWindow() if message: clientRect = wintypes.RECT() user32.GetClientRect(dialog, ctypes.byref(clientRect)) dlgRect = wintypes.RECT() user32.GetWindowRect(hdlg, ctypes.byref(dlgRect)) user32.SetWindowPos(hdlg, 0, 0, 0, width(clientRect), height(dlgRect), SWP_NOMOVE | SWP_NOZORDER) handle = user32.GetDlgItem(hdlg, IDC_STATIC) user32.SetWindowPos(handle, 0, 0, 0, width(clientRect), height(dlgRect), SWP_NOMOVE | SWP_NOZORDER) if location: x, y = location desktopRect = wintypes.RECT() user32.GetWindowRect(desktop, ctypes.byref(desktopRect)) user32.SetWindowPos(dialog, 0, desktopRect.left + x, desktopRect.top + y, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER) else: # center CenterWindow(dialog) return 0 ofn.lpfnHook = LPOFNHOOKPROC(hookProc) if fn(ctypes.byref(ofn)): filenames = filter(None, filename.split('\0')) if len(filenames) > 1: dir, filenames = filenames[0], filenames[1:] return map(lambda fn: os.path.join(dir, fn), filenames) elif multiple: return filenames else: return filenames[0] else: return None def AskFileForSave( message=None, savedFileName=None, # From here on the order is not documented version=None, defaultLocation=None, dialogOptionFlags=None, location=None, clientName=None, windowTitle=None, actionButtonLabel=None, cancelButtonLabel=None, preferenceKey=None, popupExtension=None, eventProc=None, fileType=None, fileCreator=None, wanted=None, multiple=None): """Display a dialog asking the user for a filename to save to. wanted is the return type wanted: FSSpec, FSRef, unicode or string (default) the other arguments can be looked up in Apple's Navigation Services documentation Windows differences: fileType is used for the same purpose, but file type handling is different between Windows and Mac, so the form of this argument is different. In an attempt to remain as similar as possible, an extensions can be supplied (e.g., 'txt'). A more complete form is also allowed: ('Text Files (*.txt)', '*.txt'). The first item in the tuple is the text description presented to the user. The second item in the tuple is a standard Windows wildcard pattern that will match files described in the text description. The folowing parameters are ignored on Windows: clientName, dialogOptionFlags, eventProc, fileCreator, filterProc, multiple, popupExtension, preferenceKey, previewProc, version, wanted """ return AskFileForOpen( message=message, typeList=[fileType], version=version, defaultLocation=defaultLocation, dialogOptionFlags=dialogOptionFlags, location=location, clientName=clientName, windowTitle=windowTitle, actionButtonLabel=actionButtonLabel, cancelButtonLabel=cancelButtonLabel, preferenceKey=preferenceKey, popupExtension=popupExtension, eventProc=eventProc, wanted=wanted, multiple=multiple, defaultfn=savedFileName or '', defaultext=True, fn=comdlg32.GetSaveFileNameA) def AskFolder( message=None, # From here on the order is not documented version=None, defaultLocation=None, dialogOptionFlags=None, location=None, clientName=None, windowTitle=None, actionButtonLabel=None, cancelButtonLabel=None, preferenceKey=None, popupExtension=None, eventProc=None, filterProc=None, wanted=None, multiple=None): """Display a dialog asking the user for select a folder. wanted is the return type wanted: FSSpec, FSRef, unicode or string (default) the other arguments can be looked up in Apple's Navigation Services documentation Windows differences: The folowing parameters are ignored on Windows: clientName, dialogOptionFlags, eventProc, filterProc, multiple, popupExtension, preferenceKey, version, wanted """ 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: cancelButton = user32.GetDlgItem(hwnd, IDCANCEL) if cancelButton: user32.SetWindowTextA(cancelButton, cancelButtonLabel) if windowTitle: user32.SetWindowTextA(hwnd, windowTitle) if defaultLocation: user32.SendMessageW(hwnd, BFFM_SETSELECTIONA, 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 #~ browseInfo.ulFlags = BIF_EDITBOX # BIF_NEWDIALOGSTYLE is not used because it only works with apartment # threading. Using this style with COINIT_MULTITHREADED can cause problems. pidl = shell32.SHBrowseForFolder(ctypes.byref(browseInfo)) if not pidl: result = None else: path = ctypes.c_char_p('\0' * (MAX_PATH+1)) shell32.SHGetPathFromIDList(pidl, path) ole32.CoTaskMemFree(pidl) result = path.value return result ARGV_ID=265 ARGV_ITEM_OK=1 ARGV_ITEM_CANCEL=2 ARGV_OPTION_GROUP=3 ARGV_OPTION_EXPLAIN=4 ARGV_OPTION_VALUE=5 ARGV_OPTION_ADD=6 ARGV_COMMAND_GROUP=7 ARGV_COMMAND_EXPLAIN=8 ARGV_COMMAND_ADD=9 ARGV_ADD_OLDFILE=10 ARGV_ADD_NEWFILE=11 ARGV_ADD_FOLDER=12 ARGV_CMDLINE_GROUP=13 ARGV_CMDLINE_DATA=14 def _setmenu(control, items): for item in items: if type(item) == type(()): label = item[0] else: label = item if label[-1] == '=' or label[-1] == ':': label = label[:-1] user32.SendMessageA(control, CB_ADDSTRING, 0, label) user32.SendMessageA(control, CB_SETCURSEL, 0, 0) def _selectoption(d, optionlist, idx): if idx < 0 or idx >= len(optionlist): user32.MessageBeep(-1) return option = optionlist[idx] if type(option) == type(()): if len(option) == 4: help = option[2] elif len(option) > 1: help = option[-1] else: help = '' else: help = '' h = user32.GetDlgItem(d, ARGV_OPTION_EXPLAIN) if help and len(help) > 250: help = help[:250] + '...' user32.SetWindowTextA(h, help) hasvalue = 0 if type(option) == type(()): label = option[0] else: label = option if label[-1] == '=' or label[-1] == ':': hasvalue = 1 h = user32.GetDlgItem(d, ARGV_OPTION_VALUE) user32.SetWindowTextA(h, '') if hasvalue: user32.ShowWindow(h, SW_SHOW) #~ d.SelectDialogItemText(ARGV_OPTION_VALUE, 0, 0) else: user32.ShowWindow(h, SW_HIDE) def GetArgv(optionlist=None, commandlist=None, addoldfile=1, addnewfile=1, addfolder=1, id=ARGV_ID): commandLineContents = [] def DlgProc(hwnd, uMsg, wParam, lParam): if uMsg == WM_INITDIALOG: if optionlist: _setmenu(user32.GetDlgItem(hwnd, ARGV_OPTION_GROUP), optionlist) _selectoption(hwnd, optionlist, 0) else: for id in (30, ARGV_OPTION_GROUP, ARGV_OPTION_VALUE, ARGV_OPTION_ADD): user32.EnableWindow(user32.GetDlgItem(hwnd, id), False) if commandlist: _setmenu(user32.GetDlgItem(hwnd, ARGV_COMMAND_GROUP), commandlist) if type(commandlist[0]) == type(()) and len(commandlist[0]) > 1: help = commandlist[0][-1] h = user32.GetDlgItem(hwnd, ARGV_COMMAND_EXPLAIN) user32.SetWindowTextA(h, help) else: for id in (70, ARGV_COMMAND_GROUP, ARGV_COMMAND_ADD): user32.EnableWindow(user32.GetDlgItem(hwnd, id), False) if not addoldfile: user32.EnableWindow(user32.GetDlgItem(hwnd, ARGV_ADD_OLDFILE), False) if not addnewfile: user32.EnableWindow(user32.GetDlgItem(hwnd, ARGV_ADD_NEWFILE), False) if not addfolder: user32.EnableWindow(user32.GetDlgItem(hwnd, ARGV_ADD_FOLDER), False) CenterWindow(hwnd) return False elif uMsg == WM_COMMAND and LOWORD(wParam) == IDOK: h = user32.GetDlgItem(hwnd, ARGV_CMDLINE_DATA) commandLineContents.append(GetText(h)) user32.EndDialog(hwnd, IDOK) return True elif uMsg == WM_COMMAND and LOWORD(wParam) == IDCANCEL: user32.EndDialog(hwnd, IDCANCEL) return True if uMsg == WM_COMMAND: stringstoadd = [] n = LOWORD(wParam) if n == ARGV_OPTION_GROUP: idx = user32.SendMessageA(lParam, CB_GETCURSEL, 0, 0) _selectoption(hwnd, optionlist, idx) return True elif n == ARGV_OPTION_ADD: idx = user32.SendMessageA(user32.GetDlgItem(hwnd, ARGV_OPTION_GROUP), CB_GETCURSEL, 0, 0) if 0 <= idx < len(optionlist): option = optionlist[idx] if type(option) == type(()): option = option[0] if option[-1] == '=' or option[-1] == ':': option = option[:-1] h = user32.GetDlgItem(hwnd, ARGV_OPTION_VALUE) value = GetText(h) else: value = '' if len(option) == 1: stringtoadd = '-' + option else: stringtoadd = '--' + option stringstoadd[:] = [stringtoadd] if value: stringstoadd.append(value) else: user32.MessageBeep(-1) elif n == ARGV_COMMAND_GROUP: idx = user32.SendMessageA(lParam, CB_GETCURSEL, 0, 0) if 0 <= idx < len(commandlist) and type(commandlist[idx]) == type(()) and \ len(commandlist[idx]) > 1: help = commandlist[idx][-1] h = user32.GetDlgItem(hwnd, ARGV_COMMAND_EXPLAIN) user32.SetWindowTextA(h, help) elif n == ARGV_COMMAND_ADD: idx = user32.SendMessageA(user32.GetDlgItem(hwnd, ARGV_COMMAND_GROUP), CB_GETCURSEL, 0, 0) if 0 <= idx < len(commandlist): command = commandlist[idx] if type(command) == type(()): command = command[0] stringstoadd = [command] else: user32.MessageBeep(-1) elif n == ARGV_ADD_OLDFILE: pathname = AskFileForOpen() if pathname: stringstoadd = [pathname] elif n == ARGV_ADD_NEWFILE: pathname = AskFileForSave() if pathname: stringstoadd = [pathname] elif n == ARGV_ADD_FOLDER: pathname = AskFolder() if pathname: stringstoadd = [pathname] for stringtoadd in stringstoadd: if '"' in stringtoadd or "'" in stringtoadd or " " in stringtoadd: stringtoadd = repr(stringtoadd) h = user32.GetDlgItem(hwnd, ARGV_CMDLINE_DATA) oldstr = GetText(h) if oldstr and oldstr[-1] != ' ': oldstr = oldstr + ' ' oldstr = oldstr + stringtoadd if oldstr[-1] != ' ': oldstr = oldstr + ' ' user32.SetWindowTextA(h, oldstr) user32.SendMessageA(user32.GetDlgItem(hwnd, ARGV_CMDLINE_DATA), EM_SETSEL, 0, -1) return False # This next line is needed to prevent gc of the callback myDialogProc = DialogProc(DlgProc) if IDOK == user32.DialogBoxIndirectParamA(0, resources[id], 0, myDialogProc, 0): oldstr = commandLineContents[0] tmplist = oldstr.split() newlist = [] while tmplist: item = tmplist[0] del tmplist[0] if item[0] == '"': while item[-1] != '"': if not tmplist: raise RuntimeError("Unterminated quoted argument") item = item + ' ' + tmplist[0] del tmplist[0] item = item[1:-1] if item[0] == "'": while item[-1] != "'": if not tmplist: raise RuntimeError("Unterminated quoted argument") item = item + ' ' + tmplist[0] del tmplist[0] item = item[1:-1] newlist.append(item) return newlist else: raise SystemExit def test(): import time class empty: pass Carbon = empty() Carbon.File = empty() Carbon.File.FSSpec = None Carbon.File.FSRef = None MacOS = empty() Message("Testing EasyDialogs.") optionlist = (('v', 'Verbose'), ('verbose', 'Verbose as long option'), ('flags=', 'Valued option'), ('f:', 'Short valued option')) commandlist = (('start', 'Start something'), ('stop', 'Stop something')) argv = GetArgv(optionlist=optionlist, commandlist=commandlist, addoldfile=0) Message("Command line: %s"%' '.join(argv)) for i in range(len(argv)): print(('arg[%d] = %r' % (i, argv[i]))) ok = AskYesNoCancel("Do you want to proceed?") ok = AskYesNoCancel("Do you want to identify?", yes="Identify", no="No") if ok > 0: s = AskString("Enter your first name", "Joe") s2 = AskPassword("Okay %s, tell us your nickname"%s, s, cancel="None") if not s2: Message("%s has no secret nickname"%s) else: Message("Hello everybody!!\nThe secret nickname of %s is %s!!!"%(s, s2)) else: s = 'Anonymous' rv = AskFileForOpen(message="Gimme a file, %s"%s, wanted=Carbon.File.FSSpec) Message("rv: %s"%rv) rv = AskFileForSave(wanted=Carbon.File.FSRef, savedFileName="%s.txt"%s) Message("rv: %s"%rv) # was: Message("rv.as_pathname: %s"%rv.as_pathname()) rv = AskFolder() Message("Folder name: %s"%rv) text = ( "Working Hard...", "Hardly Working..." , "So far, so good!", "Keep on truckin'" ) bar = ProgressBar("Progress, progress...", 0, label="Ramping up...") try: if hasattr(MacOS, 'SchedParams'): appsw = MacOS.SchedParams(1, 0) for i in xrange(20): bar.inc() time.sleep(0.05) bar.set(0,100) for i in xrange(100): bar.set(i) time.sleep(0.05) if i % 10 == 0: bar.label(text[(i//10) % 4]) bar.label("Done.") time.sleep(1.0) # give'em a chance to see "Done." finally: del bar if hasattr(MacOS, 'SchedParams'): MacOS.SchedParams(*appsw) if __name__ == '__main__': try: test() except KeyboardInterrupt: Message("Operation Canceled.")