1#---------------------------------------------------------------------- 2# Name: wx.lib.dialogs 3# Purpose: ScrolledMessageDialog, MultipleChoiceDialog and 4# function wrappers for the common dialogs by Kevin Altis. 5# 6# Author: Various 7# 8# Created: 3-January-2002 9# Copyright: (c) 2002-2018 by Total Control Software 10# Licence: wxWindows license 11# Tags: phoenix-port 12#---------------------------------------------------------------------- 13# 12/01/2003 - Jeff Grimmett (grimmtooth@softhome.net) 14# 15# o Updated for 2.5 compatibility. 16# 17# 12/18/2003 - Jeff Grimmett (grimmtooth@softhome.net) 18# 19# o wxScrolledMessageDialog -> ScrolledMessageDialog 20# o wxMultipleChoiceDialog -> MultipleChoiceDialog 21# 22 23import wx 24import wx.lib.layoutf as layoutf 25 26#---------------------------------------------------------------------- 27 28class ScrolledMessageDialog(wx.Dialog): 29 def __init__(self, parent, msg, caption, 30 pos=wx.DefaultPosition, size=(500,300), 31 style=wx.DEFAULT_DIALOG_STYLE): 32 wx.Dialog.__init__(self, parent, -1, caption, pos, size, style) 33 x, y = pos 34 if x == -1 and y == -1: 35 self.CenterOnScreen(wx.BOTH) 36 37 self.text = text = wx.TextCtrl(self, -1, msg, 38 style=wx.TE_MULTILINE | wx.TE_READONLY) 39 40 ok = wx.Button(self, wx.ID_OK, "OK") 41 ok.SetDefault() 42 lc = layoutf.Layoutf('t=t5#1;b=t5#2;l=l5#1;r=r5#1', (self,ok)) 43 text.SetConstraints(lc) 44 45 lc = layoutf.Layoutf('b=b5#1;x%w50#1;w!80;h*', (self,)) 46 ok.SetConstraints(lc) 47 self.SetAutoLayout(1) 48 self.Layout() 49 50 51class MultipleChoiceDialog(wx.Dialog): 52 def __init__(self, parent, msg, title, lst, pos = wx.DefaultPosition, 53 size = (200,200), style = wx.DEFAULT_DIALOG_STYLE): 54 wx.Dialog.__init__(self, parent, -1, title, pos, size, style) 55 56 x, y = pos 57 if x == -1 and y == -1: 58 self.CenterOnScreen(wx.BOTH) 59 60 stat = wx.StaticText(self, -1, msg) 61 self.lbox = wx.ListBox(self, 100, wx.DefaultPosition, wx.DefaultSize, 62 lst, wx.LB_MULTIPLE) 63 64 ok = wx.Button(self, wx.ID_OK, "OK") 65 ok.SetDefault() 66 cancel = wx.Button(self, wx.ID_CANCEL, "Cancel") 67 68 dlgsizer = wx.BoxSizer(wx.VERTICAL) 69 dlgsizer.Add(stat, 0, wx.ALL, 4) 70 dlgsizer.Add(self.lbox, 1, wx.EXPAND | wx.ALL, 4) 71 72 btnsizer = wx.StdDialogButtonSizer() 73 btnsizer.AddButton(ok) 74 btnsizer.AddButton(cancel) 75 btnsizer.Realize() 76 77 dlgsizer.Add(btnsizer, 0, wx.ALL | wx.ALIGN_RIGHT, 4) 78 79 self.SetSizer(dlgsizer) 80 81 self.lst = lst 82 self.Layout() 83 84 def GetValue(self): 85 return self.lbox.GetSelections() 86 87 def GetValueString(self): 88 sel = self.lbox.GetSelections() 89 val = [ self.lst[i] for i in sel ] 90 return tuple(val) 91 92 93#---------------------------------------------------------------------- 94""" 95function wrappers for wxPython system dialogs 96Author: Kevin Altis 97Date: 2003-1-2 98Rev: 3 99 100This is the third refactor of the PythonCard dialog.py module 101for inclusion in the main wxPython distribution. There are a number of 102design decisions and subsequent code refactoring to be done, so I'm 103releasing this just to get some feedback. 104 105rev 3: 106- result dictionary replaced by DialogResults class instance 107- should message arg be replaced with msg? most wxWindows dialogs 108 seem to use the abbreviation? 109 110rev 2: 111- All dialog classes have been replaced by function wrappers 112- Changed arg lists to more closely match wxWindows docs and wx.lib.dialogs 113- changed 'returned' value to the actual button id the user clicked on 114- added a returnedString value for the string version of the return value 115- reworked colorDialog and fontDialog so you can pass in just a color or font 116 for the most common usage case 117- probably need to use colour instead of color to match the English English 118 spelling in wxWindows (sigh) 119- I still think we could lose the parent arg and just always use None 120""" 121 122class DialogResults: 123 def __init__(self, returned): 124 self.returned = returned 125 self.accepted = returned in (wx.ID_OK, wx.ID_YES) 126 self.returnedString = returnedString(returned) 127 128 def __repr__(self): 129 return str(self.__dict__) 130 131def returnedString(ret): 132 if ret == wx.ID_OK: 133 return "Ok" 134 elif ret == wx.ID_CANCEL: 135 return "Cancel" 136 elif ret == wx.ID_YES: 137 return "Yes" 138 elif ret == wx.ID_NO: 139 return "No" 140 141 142## findDialog was created before wxPython got a Find/Replace dialog 143## but it may be instructive as to how a function wrapper can 144## be added for your own custom dialogs 145## this dialog is always modal, while wxFindReplaceDialog is 146## modeless and so doesn't lend itself to a function wrapper 147def findDialog(parent=None, searchText='', wholeWordsOnly=0, caseSensitive=0): 148 dlg = wx.Dialog(parent, -1, "Find", wx.DefaultPosition, (380, 120)) 149 150 wx.StaticText(dlg, -1, 'Find what:', (7, 10)) 151 wSearchText = wx.TextCtrl(dlg, -1, searchText, (80, 7), (195, -1)) 152 wSearchText.SetValue(searchText) 153 wx.Button(dlg, wx.ID_OK, "Find Next", (285, 5), wx.DefaultSize).SetDefault() 154 wx.Button(dlg, wx.ID_CANCEL, "Cancel", (285, 35), wx.DefaultSize) 155 156 wWholeWord = wx.CheckBox(dlg, -1, 'Match whole word only', 157 (7, 35), wx.DefaultSize, wx.NO_BORDER) 158 159 if wholeWordsOnly: 160 wWholeWord.SetValue(1) 161 162 wCase = wx.CheckBox(dlg, -1, 'Match case', (7, 55), wx.DefaultSize, wx.NO_BORDER) 163 164 if caseSensitive: 165 wCase.SetValue(1) 166 167 wSearchText.SetSelection(0, len(wSearchText.GetValue())) 168 wSearchText.SetFocus() 169 170 result = DialogResults(dlg.ShowModal()) 171 result.searchText = wSearchText.GetValue() 172 result.wholeWordsOnly = wWholeWord.GetValue() 173 result.caseSensitive = wCase.GetValue() 174 dlg.Destroy() 175 return result 176 177 178def colorDialog(parent=None, colorData=None, color=None): 179 if colorData: 180 dialog = wx.ColourDialog(parent, colorData) 181 else: 182 dialog = wx.ColourDialog(parent) 183 dialog.GetColourData().SetChooseFull(1) 184 185 if color is not None: 186 dialog.GetColourData().SetColour(color) 187 188 result = DialogResults(dialog.ShowModal()) 189 result.colorData = dialog.GetColourData() 190 result.color = result.colorData.GetColour().Get() 191 dialog.Destroy() 192 return result 193 194 195## it is easier to just duplicate the code than 196## try and replace color with colour in the result 197def colourDialog(parent=None, colourData=None, colour=None): 198 if colourData: 199 dialog = wx.ColourDialog(parent, colourData) 200 else: 201 dialog = wx.ColourDialog(parent) 202 dialog.GetColourData().SetChooseFull(1) 203 204 if colour is not None: 205 dialog.GetColourData().SetColour(color) 206 207 result = DialogResults(dialog.ShowModal()) 208 result.colourData = dialog.GetColourData() 209 result.colour = result.colourData.GetColour().Get() 210 dialog.Destroy() 211 return result 212 213 214def fontDialog(parent=None, fontData=None, font=None): 215 if fontData is None: 216 fontData = wx.FontData() 217 fontData.SetColour(wx.BLACK) 218 fontData.SetInitialFont(wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)) 219 220 if font is not None: 221 fontData.SetInitialFont(font) 222 223 dialog = wx.FontDialog(parent, fontData) 224 result = DialogResults(dialog.ShowModal()) 225 226 if result.accepted: 227 fontData = dialog.GetFontData() 228 result.fontData = fontData 229 result.color = fontData.GetColour().Get() 230 result.colour = result.color 231 result.font = fontData.GetChosenFont() 232 else: 233 result.color = None 234 result.colour = None 235 result.font = None 236 237 dialog.Destroy() 238 return result 239 240 241def textEntryDialog(parent=None, message='', title='', defaultText='', 242 style=wx.OK | wx.CANCEL): 243 dialog = wx.TextEntryDialog(parent, message, title, defaultText, style) 244 result = DialogResults(dialog.ShowModal()) 245 result.text = dialog.GetValue() 246 dialog.Destroy() 247 return result 248 249 250def messageDialog(parent=None, message='', title='Message box', 251 aStyle = wx.OK | wx.CANCEL | wx.CENTRE, 252 pos=wx.DefaultPosition): 253 dialog = wx.MessageDialog(parent, message, title, aStyle, pos) 254 result = DialogResults(dialog.ShowModal()) 255 dialog.Destroy() 256 return result 257 258 259## KEA: alerts are common, so I'm providing a class rather than 260## requiring the user code to set up the right icons and buttons 261## the with messageDialog function 262def alertDialog(parent=None, message='', title='Alert', pos=wx.DefaultPosition): 263 return messageDialog(parent, message, title, wx.ICON_EXCLAMATION | wx.OK, pos) 264 265 266def scrolledMessageDialog(parent=None, message='', title='', pos=wx.DefaultPosition, 267 size=(500,300)): 268 269 dialog = ScrolledMessageDialog(parent, message, title, pos, size) 270 result = DialogResults(dialog.ShowModal()) 271 dialog.Destroy() 272 return result 273 274 275def fileDialog(parent=None, title='Open', directory='', filename='', wildcard='*.*', 276 style=wx.FD_OPEN | wx.FD_MULTIPLE): 277 278 dialog = wx.FileDialog(parent, title, directory, filename, wildcard, style) 279 result = DialogResults(dialog.ShowModal()) 280 if result.accepted: 281 result.paths = dialog.GetPaths() 282 else: 283 result.paths = None 284 dialog.Destroy() 285 return result 286 287 288## openFileDialog and saveFileDialog are convenience functions 289## they represent the most common usages of the fileDialog 290## with the most common style options 291def openFileDialog(parent=None, title='Open', directory='', filename='', 292 wildcard='All Files (*.*)|*.*', 293 style=wx.FD_OPEN | wx.FD_MULTIPLE): 294 return fileDialog(parent, title, directory, filename, wildcard, style) 295 296 297def saveFileDialog(parent=None, title='Save', directory='', filename='', 298 wildcard='All Files (*.*)|*.*', 299 style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT): 300 return fileDialog(parent, title, directory, filename, wildcard, style) 301 302 303def dirDialog(parent=None, message='Choose a directory', path='', style=0, 304 pos=wx.DefaultPosition, size=wx.DefaultSize): 305 306 dialog = wx.DirDialog(parent, message, path, style, pos, size) 307 result = DialogResults(dialog.ShowModal()) 308 if result.accepted: 309 result.path = dialog.GetPath() 310 else: 311 result.path = None 312 dialog.Destroy() 313 return result 314 315directoryDialog = dirDialog 316 317 318def singleChoiceDialog(parent=None, message='', title='', lst=[], 319 style=wx.OK | wx.CANCEL | wx.CENTRE): 320 dialog = wx.SingleChoiceDialog(parent, message, title, list(lst), style | wx.DEFAULT_DIALOG_STYLE) 321 result = DialogResults(dialog.ShowModal()) 322 result.selection = dialog.GetStringSelection() 323 dialog.Destroy() 324 return result 325 326 327def multipleChoiceDialog(parent=None, message='', title='', lst=[], 328 pos=wx.DefaultPosition, size=wx.DefaultSize): 329 330 dialog = wx.MultiChoiceDialog(parent, message, title, lst, 331 wx.CHOICEDLG_STYLE, pos) 332 result = DialogResults(dialog.ShowModal()) 333 result.selection = tuple([lst[i] for i in dialog.GetSelections()]) 334 dialog.Destroy() 335 return result 336 337 338 339#--------------------------------------------------------------------------- 340 341try: 342 wx.CANCEL_DEFAULT 343 wx.OK_DEFAULT 344except AttributeError: 345 wx.CANCEL_DEFAULT = 0 346 wx.OK_DEFAULT = 0 347 348 349 350class MultiMessageDialog(wx.Dialog): 351 """ 352 A dialog like :class:`wx.MessageDialog`, but with an optional 2nd message string 353 that is shown in a scrolled window, and also allows passing in the icon to 354 be shown instead of the stock error, question, etc. icons. The btnLabels 355 can be used if you'd like to change the stock labels on the buttons, it's 356 a dictionary mapping stock IDs to label strings. 357 """ 358 CONTENT_MAX_W = 550 359 CONTENT_MAX_H = 350 360 361 def __init__(self, parent, message, caption = "Message Box", msg2="", 362 style = wx.OK | wx.CANCEL, pos = wx.DefaultPosition, icon=None, 363 btnLabels=None): 364 if 'wxMac' not in wx.PlatformInfo: 365 title = caption # the caption will be displayed inside the dialog on Macs 366 else: 367 title = "" 368 369 wx.Dialog.__init__(self, parent, -1, title, pos, 370 style = wx.DEFAULT_DIALOG_STYLE | style & (wx.STAY_ON_TOP | wx.DIALOG_NO_PARENT)) 371 372 bitmap = None 373 isize = (32,32) 374 375 # was an icon passed to us? 376 if icon is not None: 377 if isinstance(icon, wx.Icon): 378 bitmap = wx.Bitmap() 379 bitmap.CopyFromIcon(icon) 380 elif isinstance(icon, wx.Image): 381 bitmap = wx.Bitmap(icon) 382 else: 383 assert isinstance(icon, wx.Bitmap) 384 bitmap = icon 385 386 else: 387 # check for icons in the style flags 388 artid = None 389 if style & wx.ICON_ERROR or style & wx.ICON_HAND: 390 artid = wx.ART_ERROR 391 elif style & wx.ICON_EXCLAMATION: 392 artid = wx.ART_WARNING 393 elif style & wx.ICON_QUESTION: 394 artid = wx.ART_QUESTION 395 elif style & wx.ICON_INFORMATION: 396 artid = wx.ART_INFORMATION 397 398 if artid is not None: 399 bitmap = wx.ArtProvider.GetBitmap(artid, wx.ART_MESSAGE_BOX, isize) 400 401 if bitmap: 402 bitmap = wx.StaticBitmap(self, -1, bitmap) 403 else: 404 bitmap = isize # will be a spacer when added to the sizer 405 406 # Sizer to contain the icon, text area and buttons 407 sizer = wx.BoxSizer(wx.HORIZONTAL) 408 sizer.Add(bitmap, 0, wx.TOP|wx.LEFT, 12) 409 sizer.Add((10,10)) 410 411 # Make the text area 412 messageSizer = wx.BoxSizer(wx.VERTICAL) 413 if 'wxMac' in wx.PlatformInfo and caption: 414 caption = wx.StaticText(self, -1, caption) 415 caption.SetFont(wx.Font(18, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) 416 messageSizer.Add(caption) 417 messageSizer.Add((10,10)) 418 419 stext = wx.StaticText(self, -1, message) 420 #stext.SetLabelMarkup(message) Wrap() causes all markup to be lost, so don't try to use it yet... 421 stext.Wrap(self.CONTENT_MAX_W) 422 messageSizer.Add(stext) 423 424 if msg2: 425 messageSizer.Add((15,15)) 426 t = wx.TextCtrl(self, style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_RICH|wx.TE_DONTWRAP) 427 t.SetValue(msg2) 428 429 # Set size to be used by the sizer based on the message content, 430 # with good maximums 431 dc = wx.ClientDC(t) 432 dc.SetFont(t.GetFont()) 433 w,h,_ = dc.GetFullMultiLineTextExtent(msg2) 434 w = min(self.CONTENT_MAX_W, 10 + w + wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)) 435 h = min(self.CONTENT_MAX_H, 10 + h) 436 t.SetMinSize((w,h)) 437 messageSizer.Add(t, 0, wx.EXPAND) 438 439 # Make the buttons 440 buttonSizer = self.CreateStdDialogButtonSizer( 441 style & (wx.OK | wx.CANCEL | wx.YES_NO | wx.NO_DEFAULT 442 | wx.CANCEL_DEFAULT | wx.YES_DEFAULT | wx.OK_DEFAULT 443 )) 444 self.Bind(wx.EVT_BUTTON, self.OnButton) 445 if btnLabels: 446 for sid, label in btnLabels.iteritems(): 447 btn = self.FindWindow(sid) 448 if btn: 449 btn.SetLabel(label) 450 messageSizer.Add(wx.Size(1, 15)) 451 messageSizer.Add(buttonSizer, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, 12) 452 453 sizer.Add(messageSizer, 0, wx.LEFT | wx.RIGHT | wx.TOP, 12) 454 self.SetSizer(sizer) 455 self.Fit() 456 if parent: 457 self.CenterOnParent() 458 else: 459 self.CenterOnScreen() 460 461 for c in self.Children: 462 if isinstance(c, wx.Button): 463 wx.CallAfter(c.SetFocus) 464 break 465 466 467 def OnButton(self, evt): 468 if self.IsModal(): 469 self.EndModal(evt.EventObject.Id) 470 else: 471 self.Close() 472 473 474 475 476def MultiMessageBox(message, caption, msg2="", style=wx.OK, parent=None, 477 icon=None, btnLabels=None): 478 """ 479 A function like :class:`wx.MessageBox` which uses :class:`MultiMessageDialog`. 480 """ 481 #if not style & wx.ICON_NONE and not style & wx.ICON_MASK: 482 if not style & wx.ICON_MASK: 483 if style & wx.YES: 484 style |= wx.ICON_QUESTION 485 else: 486 style |= wx.ICON_INFORMATION 487 488 dlg = MultiMessageDialog(parent, message, caption, msg2, style, 489 icon=icon, btnLabels=btnLabels) 490 ans = dlg.ShowModal() 491 dlg.Destroy() 492 493 if ans == wx.ID_OK: 494 return wx.OK 495 elif ans == wx.ID_YES: 496 return wx.YES 497 elif ans == wx.ID_NO: 498 return wx.NO 499 elif ans == wx.ID_CANCEL: 500 return wx.CANCEL 501 502 print("unexpected return code from MultiMessageDialog??") 503 return wx.CANCEL 504 505 506#--------------------------------------------------------------------------- 507 508if __name__ == '__main__': 509 app = wx.App() 510 MultiMessageBox("Hello World", "howdy", "This is a MultiMessageBox \ntest. With a multi-line message.") 511 app.MainLoop() 512