rttk-queerscriptors/game/EasyDialogsWin.py

1078 lines
40 KiB
Python
Raw Normal View History

2022-08-23 16:26:22 +00:00
#@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.")