| 1 | #---------------------------------------------------------------------- |
| 2 | # Name: wxPython.lib.filebrowsebutton |
| 3 | # Purpose: Composite controls that provide a Browse button next to |
| 4 | # either a wxTextCtrl or a wxComboBox. The Browse button |
| 5 | # launches a wxFileDialog and loads the result into the |
| 6 | # other control. |
| 7 | # |
| 8 | # Author: Mike Fletcher |
| 9 | # |
| 10 | # RCS-ID: $Id$ |
| 11 | # Copyright: (c) 2000 by Total Control Software |
| 12 | # Licence: wxWindows license |
| 13 | #---------------------------------------------------------------------- |
| 14 | # 12/02/2003 - Jeff Grimmett (grimmtooth@softhome.net) |
| 15 | # |
| 16 | # o 2.5 Compatability changes |
| 17 | # |
| 18 | |
| 19 | import os |
| 20 | import types |
| 21 | |
| 22 | import wx |
| 23 | |
| 24 | #---------------------------------------------------------------------- |
| 25 | |
| 26 | class FileBrowseButton(wx.Panel): |
| 27 | """ |
| 28 | A control to allow the user to type in a filename or browse with |
| 29 | the standard file dialog to select file |
| 30 | """ |
| 31 | def __init__ (self, parent, id= -1, |
| 32 | pos = wx.DefaultPosition, |
| 33 | size = wx.DefaultSize, |
| 34 | style = wx.TAB_TRAVERSAL, |
| 35 | labelText= "File Entry:", |
| 36 | buttonText= "Browse", |
| 37 | toolTip= "Type filename or click browse to choose file", |
| 38 | # following are the values for a file dialog box |
| 39 | dialogTitle = "Choose a file", |
| 40 | startDirectory = ".", |
| 41 | initialValue = "", |
| 42 | fileMask = "*.*", |
| 43 | fileMode = wx.OPEN, |
| 44 | # callback for when value changes (optional) |
| 45 | changeCallback= lambda x:x |
| 46 | ): |
| 47 | """ |
| 48 | :param labelText: Text for label to left of text field |
| 49 | :param buttonText: Text for button which launches the file dialog |
| 50 | :param toolTip: Help text |
| 51 | :param dialogTitle: Title used in file dialog |
| 52 | :param startDirectory: Default directory for file dialog startup |
| 53 | :param fileMask: File mask (glob pattern, such as *.*) to use in file dialog |
| 54 | :param fileMode: wx.OPEN or wx.SAVE, indicates type of file dialog to use |
| 55 | :param changeCallback: callback receives all changes in value of control |
| 56 | """ |
| 57 | |
| 58 | # store variables |
| 59 | self.labelText = labelText |
| 60 | self.buttonText = buttonText |
| 61 | self.toolTip = toolTip |
| 62 | self.dialogTitle = dialogTitle |
| 63 | self.startDirectory = startDirectory |
| 64 | self.initialValue = initialValue |
| 65 | self.fileMask = fileMask |
| 66 | self.fileMode = fileMode |
| 67 | self.changeCallback = changeCallback |
| 68 | self.callCallback = True |
| 69 | |
| 70 | |
| 71 | # get background to match it |
| 72 | try: |
| 73 | self._bc = parent.GetBackgroundColour() |
| 74 | except: |
| 75 | pass |
| 76 | |
| 77 | # create the dialog |
| 78 | self.createDialog(parent, id, pos, size, style ) |
| 79 | # Setting a value causes the changeCallback to be called. |
| 80 | # In this case that would be before the return of the |
| 81 | # constructor. Not good. So a default value on |
| 82 | # SetValue is used to disable the callback |
| 83 | self.SetValue( initialValue, 0) |
| 84 | |
| 85 | |
| 86 | def createDialog( self, parent, id, pos, size, style ): |
| 87 | """Setup the graphic representation of the dialog""" |
| 88 | wx.Panel.__init__ (self, parent, id, pos, size, style) |
| 89 | # try to set the background colour |
| 90 | try: |
| 91 | self.SetBackgroundColour(self._bc) |
| 92 | except: |
| 93 | pass |
| 94 | box = wx.BoxSizer(wx.HORIZONTAL) |
| 95 | |
| 96 | self.label = self.createLabel( ) |
| 97 | box.Add( self.label, 0, wx.CENTER ) |
| 98 | |
| 99 | self.textControl = self.createTextControl() |
| 100 | box.Add( self.textControl, 1, wx.LEFT|wx.CENTER, 5) |
| 101 | |
| 102 | self.browseButton = self.createBrowseButton() |
| 103 | box.Add( self.browseButton, 0, wx.LEFT|wx.CENTER, 5) |
| 104 | |
| 105 | # add a border around the whole thing and resize the panel to fit |
| 106 | outsidebox = wx.BoxSizer(wx.VERTICAL) |
| 107 | outsidebox.Add(box, 1, wx.EXPAND|wx.ALL, 3) |
| 108 | outsidebox.Fit(self) |
| 109 | |
| 110 | self.SetAutoLayout(True) |
| 111 | self.SetSizer( outsidebox ) |
| 112 | self.Layout() |
| 113 | if type( size ) == types.TupleType: |
| 114 | size = apply( wx.Size, size) |
| 115 | self.SetDimensions(-1, -1, size.width, size.height, wx.SIZE_USE_EXISTING) |
| 116 | |
| 117 | # if size.width != -1 or size.height != -1: |
| 118 | # self.SetSize(size) |
| 119 | |
| 120 | def SetBackgroundColour(self,color): |
| 121 | wx.Panel.SetBackgroundColour(self,color) |
| 122 | self.label.SetBackgroundColour(color) |
| 123 | |
| 124 | def createLabel( self ): |
| 125 | """Create the label/caption""" |
| 126 | label = wx.StaticText(self, -1, self.labelText, style =wx.ALIGN_RIGHT ) |
| 127 | font = label.GetFont() |
| 128 | w, h, d, e = self.GetFullTextExtent(self.labelText, font) |
| 129 | label.SetSize((w+5, h)) |
| 130 | return label |
| 131 | |
| 132 | def createTextControl( self): |
| 133 | """Create the text control""" |
| 134 | textControl = wx.TextCtrl(self, -1) |
| 135 | textControl.SetToolTipString( self.toolTip ) |
| 136 | if self.changeCallback: |
| 137 | textControl.Bind(wx.EVT_TEXT, self.OnChanged) |
| 138 | textControl.Bind(wx.EVT_COMBOBOX, self.OnChanged) |
| 139 | return textControl |
| 140 | |
| 141 | def OnChanged(self, evt): |
| 142 | if self.callCallback and self.changeCallback: |
| 143 | self.changeCallback(evt) |
| 144 | |
| 145 | def createBrowseButton( self): |
| 146 | """Create the browse-button control""" |
| 147 | button =wx.Button(self, -1, self.buttonText) |
| 148 | button.SetToolTipString( self.toolTip ) |
| 149 | button.Bind(wx.EVT_BUTTON, self.OnBrowse) |
| 150 | return button |
| 151 | |
| 152 | |
| 153 | def OnBrowse (self, event = None): |
| 154 | """ Going to browse for file... """ |
| 155 | current = self.GetValue() |
| 156 | directory = os.path.split(current) |
| 157 | if os.path.isdir( current): |
| 158 | directory = current |
| 159 | current = '' |
| 160 | elif directory and os.path.isdir( directory[0] ): |
| 161 | current = directory[1] |
| 162 | directory = directory [0] |
| 163 | else: |
| 164 | directory = self.startDirectory |
| 165 | dlg = wx.FileDialog(self, self.dialogTitle, directory, current, |
| 166 | self.fileMask, self.fileMode) |
| 167 | |
| 168 | if dlg.ShowModal() == wx.ID_OK: |
| 169 | self.SetValue(dlg.GetPath()) |
| 170 | dlg.Destroy() |
| 171 | |
| 172 | |
| 173 | def GetValue (self): |
| 174 | """ |
| 175 | retrieve current value of text control |
| 176 | """ |
| 177 | return self.textControl.GetValue() |
| 178 | |
| 179 | def SetValue (self, value, callBack=1): |
| 180 | """set current value of text control""" |
| 181 | save = self.callCallback |
| 182 | self.callCallback = callBack |
| 183 | self.textControl.SetValue(value) |
| 184 | self.callCallback = save |
| 185 | |
| 186 | |
| 187 | def Enable (self, value): |
| 188 | """ Convenient enabling/disabling of entire control """ |
| 189 | self.label.Enable (value) |
| 190 | self.textControl.Enable (value) |
| 191 | return self.browseButton.Enable (value) |
| 192 | |
| 193 | def GetLabel( self ): |
| 194 | """ Retrieve the label's current text """ |
| 195 | return self.label.GetLabel() |
| 196 | |
| 197 | def SetLabel( self, value ): |
| 198 | """ Set the label's current text """ |
| 199 | rvalue = self.label.SetLabel( value ) |
| 200 | self.Refresh( True ) |
| 201 | return rvalue |
| 202 | |
| 203 | |
| 204 | |
| 205 | |
| 206 | class FileBrowseButtonWithHistory( FileBrowseButton ): |
| 207 | """ |
| 208 | with following additions: |
| 209 | __init__(..., history=None) |
| 210 | |
| 211 | history -- optional list of paths for initial history drop-down |
| 212 | (must be passed by name, not a positional argument) |
| 213 | If history is callable it will must return a list used |
| 214 | for the history drop-down |
| 215 | |
| 216 | changeCallback -- as for FileBrowseButton, but with a work-around |
| 217 | for win32 systems which don't appear to create wx.EVT_COMBOBOX |
| 218 | events properly. There is a (slight) chance that this work-around |
| 219 | will cause some systems to create two events for each Combobox |
| 220 | selection. If you discover this condition, please report it! |
| 221 | |
| 222 | As for a FileBrowseButton.__init__ otherwise. |
| 223 | |
| 224 | GetHistoryControl() |
| 225 | Return reference to the control which implements interfaces |
| 226 | required for manipulating the history list. See GetHistoryControl |
| 227 | documentation for description of what that interface is. |
| 228 | |
| 229 | GetHistory() |
| 230 | Return current history list |
| 231 | |
| 232 | SetHistory( value=(), selectionIndex = None ) |
| 233 | Set current history list, if selectionIndex is not None, select that index |
| 234 | |
| 235 | """ |
| 236 | def __init__( self, *arguments, **namedarguments): |
| 237 | self.history = namedarguments.get( "history" ) |
| 238 | if self.history: |
| 239 | del namedarguments["history"] |
| 240 | |
| 241 | self.historyCallBack=None |
| 242 | if callable(self.history): |
| 243 | self.historyCallBack=self.history |
| 244 | self.history=None |
| 245 | apply( FileBrowseButton.__init__, ( self,)+arguments, namedarguments) |
| 246 | |
| 247 | |
| 248 | def createTextControl( self): |
| 249 | """Create the text control""" |
| 250 | textControl = wx.ComboBox(self, -1, style = wx.CB_DROPDOWN ) |
| 251 | textControl.SetToolTipString( self.toolTip ) |
| 252 | textControl.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) |
| 253 | if self.changeCallback: |
| 254 | textControl.Bind(wx.EVT_TEXT, self.changeCallback) |
| 255 | textControl.Bind(wx.EVT_COMBOBOX, self.changeCallback) |
| 256 | if self.history: |
| 257 | history=self.history |
| 258 | self.history=None |
| 259 | self.SetHistory( history, control=textControl) |
| 260 | return textControl |
| 261 | |
| 262 | |
| 263 | def GetHistoryControl( self ): |
| 264 | """ |
| 265 | Return a pointer to the control which provides (at least) |
| 266 | the following methods for manipulating the history list: |
| 267 | |
| 268 | Append( item ) -- add item |
| 269 | Clear() -- clear all items |
| 270 | Delete( index ) -- 0-based index to delete from list |
| 271 | SetSelection( index ) -- 0-based index to select in list |
| 272 | |
| 273 | Semantics of the methods follow those for the wxComboBox control |
| 274 | """ |
| 275 | return self.textControl |
| 276 | |
| 277 | |
| 278 | def SetHistory( self, value=(), selectionIndex = None, control=None ): |
| 279 | """Set the current history list""" |
| 280 | if control is None: |
| 281 | control = self.GetHistoryControl() |
| 282 | if self.history == value: |
| 283 | return |
| 284 | self.history = value |
| 285 | # Clear history values not the selected one. |
| 286 | tempValue=control.GetValue() |
| 287 | # clear previous values |
| 288 | control.Clear() |
| 289 | control.SetValue(tempValue) |
| 290 | # walk through, appending new values |
| 291 | for path in value: |
| 292 | control.Append( path ) |
| 293 | if selectionIndex is not None: |
| 294 | control.SetSelection( selectionIndex ) |
| 295 | |
| 296 | |
| 297 | def GetHistory( self ): |
| 298 | """Return the current history list""" |
| 299 | if self.historyCallBack != None: |
| 300 | return self.historyCallBack() |
| 301 | else: |
| 302 | return list( self.history ) |
| 303 | |
| 304 | |
| 305 | def OnSetFocus(self, event): |
| 306 | """When the history scroll is selected, update the history""" |
| 307 | if self.historyCallBack != None: |
| 308 | self.SetHistory( self.historyCallBack(), control=self.textControl) |
| 309 | event.Skip() |
| 310 | |
| 311 | |
| 312 | if wx.Platform == "__WXMSW__": |
| 313 | def SetValue (self, value, callBack=1): |
| 314 | """ Convenient setting of text control value, works |
| 315 | around limitation of wx.ComboBox """ |
| 316 | save = self.callCallback |
| 317 | self.callCallback = callBack |
| 318 | self.textControl.SetValue(value) |
| 319 | self.callCallback = save |
| 320 | |
| 321 | # Hack to call an event handler |
| 322 | class LocalEvent: |
| 323 | def __init__(self, string): |
| 324 | self._string=string |
| 325 | def GetString(self): |
| 326 | return self._string |
| 327 | if callBack==1: |
| 328 | # The callback wasn't being called when SetValue was used ?? |
| 329 | # So added this explicit call to it |
| 330 | self.changeCallback(LocalEvent(value)) |
| 331 | |
| 332 | |
| 333 | class DirBrowseButton(FileBrowseButton): |
| 334 | def __init__(self, parent, id = -1, |
| 335 | pos = wx.DefaultPosition, size = wx.DefaultSize, |
| 336 | style = wx.TAB_TRAVERSAL, |
| 337 | labelText = 'Select a directory:', |
| 338 | buttonText = 'Browse', |
| 339 | toolTip = 'Type directory name or browse to select', |
| 340 | dialogTitle = '', |
| 341 | startDirectory = '.', |
| 342 | changeCallback = None, |
| 343 | dialogClass = wx.DirDialog): |
| 344 | FileBrowseButton.__init__(self, parent, id, pos, size, style, |
| 345 | labelText, buttonText, toolTip, |
| 346 | dialogTitle, startDirectory, |
| 347 | changeCallback = changeCallback) |
| 348 | self.dialogClass = dialogClass |
| 349 | # |
| 350 | |
| 351 | def OnBrowse(self, ev = None): |
| 352 | dialog = self.dialogClass(self, |
| 353 | message = self.dialogTitle, |
| 354 | defaultPath = self.startDirectory) |
| 355 | if dialog.ShowModal() == wx.ID_OK: |
| 356 | self.SetValue(dialog.GetPath()) |
| 357 | dialog.Destroy() |
| 358 | # |
| 359 | |
| 360 | |
| 361 | #---------------------------------------------------------------------- |
| 362 | |
| 363 | |
| 364 | if __name__ == "__main__": |
| 365 | #from skeletonbuilder import rulesfile |
| 366 | class SimpleCallback: |
| 367 | def __init__( self, tag ): |
| 368 | self.tag = tag |
| 369 | def __call__( self, event ): |
| 370 | print self.tag, event.GetString() |
| 371 | class DemoFrame( wx.Frame ): |
| 372 | def __init__(self, parent): |
| 373 | wx.Frame.__init__(self, parent, -1, "File entry with browse", size=(500,260)) |
| 374 | self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) |
| 375 | panel = wx.Panel (self,-1) |
| 376 | innerbox = wx.BoxSizer(wx.VERTICAL) |
| 377 | control = FileBrowseButton( |
| 378 | panel, |
| 379 | initialValue = "z:\\temp", |
| 380 | ) |
| 381 | innerbox.Add( control, 0, wx.EXPAND ) |
| 382 | middlecontrol = FileBrowseButtonWithHistory( |
| 383 | panel, |
| 384 | labelText = "With History", |
| 385 | initialValue = "d:\\temp", |
| 386 | history = ["c:\\temp", "c:\\tmp", "r:\\temp","z:\\temp"], |
| 387 | changeCallback= SimpleCallback( "With History" ), |
| 388 | ) |
| 389 | innerbox.Add( middlecontrol, 0, wx.EXPAND ) |
| 390 | middlecontrol = FileBrowseButtonWithHistory( |
| 391 | panel, |
| 392 | labelText = "History callback", |
| 393 | initialValue = "d:\\temp", |
| 394 | history = self.historyCallBack, |
| 395 | changeCallback= SimpleCallback( "History callback" ), |
| 396 | ) |
| 397 | innerbox.Add( middlecontrol, 0, wx.EXPAND ) |
| 398 | self.bottomcontrol = control = FileBrowseButton( |
| 399 | panel, |
| 400 | labelText = "With Callback", |
| 401 | style = wx.SUNKEN_BORDER|wx.CLIP_CHILDREN , |
| 402 | changeCallback= SimpleCallback( "With Callback" ), |
| 403 | ) |
| 404 | innerbox.Add( control, 0, wx.EXPAND) |
| 405 | self.bottommostcontrol = control = DirBrowseButton( |
| 406 | panel, |
| 407 | labelText = "Simple dir browse button", |
| 408 | style = wx.SUNKEN_BORDER|wx.CLIP_CHILDREN) |
| 409 | innerbox.Add( control, 0, wx.EXPAND) |
| 410 | ID = wx.NewId() |
| 411 | innerbox.Add( wx.Button( panel, ID,"Change Label", ), 1, wx.EXPAND) |
| 412 | self.Bind(wx.EVT_BUTTON, self.OnChangeLabel , id=ID) |
| 413 | ID = wx.NewId() |
| 414 | innerbox.Add( wx.Button( panel, ID,"Change Value", ), 1, wx.EXPAND) |
| 415 | self.Bind(wx.EVT_BUTTON, self.OnChangeValue, id=ID ) |
| 416 | panel.SetAutoLayout(True) |
| 417 | panel.SetSizer( innerbox ) |
| 418 | self.history={"c:\\temp":1, "c:\\tmp":1, "r:\\temp":1,"z:\\temp":1} |
| 419 | |
| 420 | def historyCallBack(self): |
| 421 | keys=self.history.keys() |
| 422 | keys.sort() |
| 423 | return keys |
| 424 | |
| 425 | def OnFileNameChangedHistory (self, event): |
| 426 | self.history[event.GetString ()]=1 |
| 427 | |
| 428 | def OnCloseMe(self, event): |
| 429 | self.Close(True) |
| 430 | def OnChangeLabel( self, event ): |
| 431 | self.bottomcontrol.SetLabel( "Label Updated" ) |
| 432 | def OnChangeValue( self, event ): |
| 433 | self.bottomcontrol.SetValue( "r:\\somewhere\\over\\the\\rainbow.htm" ) |
| 434 | |
| 435 | def OnCloseWindow(self, event): |
| 436 | self.Destroy() |
| 437 | |
| 438 | class DemoApp(wx.App): |
| 439 | def OnInit(self): |
| 440 | wx.InitAllImageHandlers() |
| 441 | frame = DemoFrame(None) |
| 442 | frame.Show(True) |
| 443 | self.SetTopWindow(frame) |
| 444 | return True |
| 445 | |
| 446 | def test( ): |
| 447 | app = DemoApp(0) |
| 448 | app.MainLoop() |
| 449 | print 'Creating dialog' |
| 450 | test( ) |
| 451 | |
| 452 | |
| 453 | |