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