| 1 | # |
| 2 | __version__ = '$Id: PmwFileDialog.py,v 1.2 2002/08/23 15:03:35 gregm Exp $' |
| 3 | # |
| 4 | # Filename dialogs using Pmw |
| 5 | # |
| 6 | # (C) Rob W.W. Hooft, Nonius BV, 1998 |
| 7 | # |
| 8 | # Modifications: |
| 9 | # |
| 10 | # J. Willem M. Nissink, Cambridge Crystallographic Data Centre, 8/2002 |
| 11 | # Added optional information pane at top of dialog; if option |
| 12 | # 'info' is specified, the text given will be shown (in blue). |
| 13 | # Modified example to show both file and directory-type dialog |
| 14 | # |
| 15 | # No Guarantees. Distribute Freely. |
| 16 | # Please send bug-fixes/patches/features to <r.hooft@euromail.com> |
| 17 | # |
| 18 | ################################################################################ |
| 19 | import os,fnmatch,time |
| 20 | import Tkinter,Pmw |
| 21 | #Pmw.setversion("0.8.5") |
| 22 | |
| 23 | def _errorpop(master,text): |
| 24 | d=Pmw.MessageDialog(master, |
| 25 | title="Error", |
| 26 | message_text=text, |
| 27 | buttons=("OK",)) |
| 28 | d.component('message').pack(ipadx=15,ipady=15) |
| 29 | d.activate() |
| 30 | d.destroy() |
| 31 | |
| 32 | class PmwFileDialog(Pmw.Dialog): |
| 33 | """File Dialog using Pmw""" |
| 34 | def __init__(self, parent = None, **kw): |
| 35 | # Define the megawidget options. |
| 36 | optiondefs = ( |
| 37 | ('filter', '*', self.newfilter), |
| 38 | ('directory', os.getcwd(), self.newdir), |
| 39 | ('filename', '', self.newfilename), |
| 40 | ('historylen',10, None), |
| 41 | ('command', None, None), |
| 42 | ('info', None, None), |
| 43 | ) |
| 44 | self.defineoptions(kw, optiondefs) |
| 45 | # Initialise base class (after defining options). |
| 46 | Pmw.Dialog.__init__(self, parent) |
| 47 | |
| 48 | self.withdraw() |
| 49 | |
| 50 | # Create the components. |
| 51 | interior = self.interior() |
| 52 | |
| 53 | if self['info'] is not None: |
| 54 | rowoffset=1 |
| 55 | dn = self.infotxt() |
| 56 | dn.grid(row=0,column=0,columnspan=2,padx=3,pady=3) |
| 57 | else: |
| 58 | rowoffset=0 |
| 59 | |
| 60 | dn = self.mkdn() |
| 61 | dn.grid(row=0+rowoffset,column=0,columnspan=2,padx=3,pady=3) |
| 62 | del dn |
| 63 | |
| 64 | # Create the directory list component. |
| 65 | dnb = self.mkdnb() |
| 66 | dnb.grid(row=1+rowoffset,column=0,sticky='news',padx=3,pady=3) |
| 67 | del dnb |
| 68 | |
| 69 | # Create the filename list component. |
| 70 | fnb = self.mkfnb() |
| 71 | fnb.grid(row=1+rowoffset,column=1,sticky='news',padx=3,pady=3) |
| 72 | del fnb |
| 73 | |
| 74 | # Create the filter entry |
| 75 | ft = self.mkft() |
| 76 | ft.grid(row=2+rowoffset,column=0,columnspan=2,padx=3,pady=3) |
| 77 | del ft |
| 78 | |
| 79 | # Create the filename entry |
| 80 | fn = self.mkfn() |
| 81 | fn.grid(row=3+rowoffset,column=0,columnspan=2,padx=3,pady=3) |
| 82 | fn.bind('<Return>',self.okbutton) |
| 83 | del fn |
| 84 | |
| 85 | # Buttonbox already exists |
| 86 | bb=self.component('buttonbox') |
| 87 | bb.add('OK',command=self.okbutton) |
| 88 | bb.add('Cancel',command=self.cancelbutton) |
| 89 | del bb |
| 90 | |
| 91 | Pmw.alignlabels([self.component('filename'), |
| 92 | self.component('filter'), |
| 93 | self.component('dirname')]) |
| 94 | |
| 95 | def infotxt(self): |
| 96 | """ Make information block component at the top """ |
| 97 | return self.createcomponent( |
| 98 | 'infobox', |
| 99 | (), None, |
| 100 | Tkinter.Label, (self.interior(),), |
| 101 | width=51, |
| 102 | relief='groove', |
| 103 | foreground='darkblue', |
| 104 | justify='left', |
| 105 | text=self['info'] |
| 106 | ) |
| 107 | |
| 108 | def mkdn(self): |
| 109 | """Make directory name component""" |
| 110 | return self.createcomponent( |
| 111 | 'dirname', |
| 112 | (), None, |
| 113 | Pmw.ComboBox, (self.interior(),), |
| 114 | entryfield_value=self['directory'], |
| 115 | entryfield_entry_width=40, |
| 116 | entryfield_validate=self.dirvalidate, |
| 117 | selectioncommand=self.setdir, |
| 118 | labelpos='w', |
| 119 | label_text='Directory:') |
| 120 | |
| 121 | def mkdnb(self): |
| 122 | """Make directory name box""" |
| 123 | return self.createcomponent( |
| 124 | 'dirnamebox', |
| 125 | (), None, |
| 126 | Pmw.ScrolledListBox, (self.interior(),), |
| 127 | label_text='directories', |
| 128 | labelpos='n', |
| 129 | hscrollmode='none', |
| 130 | dblclickcommand=self.selectdir) |
| 131 | |
| 132 | def mkft(self): |
| 133 | """Make filter""" |
| 134 | return self.createcomponent( |
| 135 | 'filter', |
| 136 | (), None, |
| 137 | Pmw.ComboBox, (self.interior(),), |
| 138 | entryfield_value=self['filter'], |
| 139 | entryfield_entry_width=40, |
| 140 | selectioncommand=self.setfilter, |
| 141 | labelpos='w', |
| 142 | label_text='Filter:') |
| 143 | |
| 144 | def mkfnb(self): |
| 145 | """Make filename list box""" |
| 146 | return self.createcomponent( |
| 147 | 'filenamebox', |
| 148 | (), None, |
| 149 | Pmw.ScrolledListBox, (self.interior(),), |
| 150 | label_text='files', |
| 151 | labelpos='n', |
| 152 | hscrollmode='none', |
| 153 | selectioncommand=self.singleselectfile, |
| 154 | dblclickcommand=self.selectfile) |
| 155 | |
| 156 | def mkfn(self): |
| 157 | """Make file name entry""" |
| 158 | return self.createcomponent( |
| 159 | 'filename', |
| 160 | (), None, |
| 161 | Pmw.ComboBox, (self.interior(),), |
| 162 | entryfield_value=self['filename'], |
| 163 | entryfield_entry_width=40, |
| 164 | entryfield_validate=self.filevalidate, |
| 165 | selectioncommand=self.setfilename, |
| 166 | labelpos='w', |
| 167 | label_text='Filename:') |
| 168 | |
| 169 | def dirvalidate(self,string): |
| 170 | if os.path.isdir(string): |
| 171 | return Pmw.OK |
| 172 | else: |
| 173 | return Pmw.PARTIAL |
| 174 | |
| 175 | def filevalidate(self,string): |
| 176 | if string=='': |
| 177 | return Pmw.PARTIAL |
| 178 | elif os.path.isfile(string): |
| 179 | return Pmw.OK |
| 180 | elif os.path.exists(string): |
| 181 | return Pmw.PARTIAL |
| 182 | else: |
| 183 | return Pmw.OK |
| 184 | |
| 185 | def okbutton(self): |
| 186 | """OK action: user thinks he has input valid data and wants to |
| 187 | proceed. This is also called by <Return> in the filename entry""" |
| 188 | fn=self.component('filename').get() |
| 189 | self.setfilename(fn) |
| 190 | if self.validate(fn): |
| 191 | self.canceled=0 |
| 192 | self.deactivate() |
| 193 | |
| 194 | def cancelbutton(self): |
| 195 | """Cancel the operation""" |
| 196 | self.canceled=1 |
| 197 | self.deactivate() |
| 198 | |
| 199 | def tidy(self,w,v): |
| 200 | """Insert text v into the entry and at the top of the list of |
| 201 | the combobox w, remove duplicates""" |
| 202 | if not v: |
| 203 | return |
| 204 | entry=w.component('entry') |
| 205 | entry.delete(0,'end') |
| 206 | entry.insert(0,v) |
| 207 | list=w.component('scrolledlist') |
| 208 | list.insert(0,v) |
| 209 | index=1 |
| 210 | while index<list.index('end'): |
| 211 | k=list.get(index) |
| 212 | if k==v or index>self['historylen']: |
| 213 | list.delete(index) |
| 214 | else: |
| 215 | index=index+1 |
| 216 | w.checkentry() |
| 217 | |
| 218 | def setfilename(self,value): |
| 219 | if not value: |
| 220 | return |
| 221 | value=os.path.join(self['directory'],value) |
| 222 | dir,fil=os.path.split(value) |
| 223 | self.configure(directory=dir,filename=value) |
| 224 | |
| 225 | c=self['command'] |
| 226 | if callable(c): |
| 227 | c() |
| 228 | |
| 229 | def newfilename(self): |
| 230 | """Make sure a newly set filename makes it into the combobox list""" |
| 231 | self.tidy(self.component('filename'),self['filename']) |
| 232 | |
| 233 | def setfilter(self,value): |
| 234 | self.configure(filter=value) |
| 235 | |
| 236 | def newfilter(self): |
| 237 | """Make sure a newly set filter makes it into the combobox list""" |
| 238 | self.tidy(self.component('filter'),self['filter']) |
| 239 | self.fillit() |
| 240 | |
| 241 | def setdir(self,value): |
| 242 | self.configure(directory=value) |
| 243 | |
| 244 | def newdir(self): |
| 245 | """Make sure a newly set dirname makes it into the combobox list""" |
| 246 | self.tidy(self.component('dirname'),self['directory']) |
| 247 | self.fillit() |
| 248 | |
| 249 | def singleselectfile(self): |
| 250 | """Single click in file listbox. Move file to "filename" combobox""" |
| 251 | cs=self.component('filenamebox').curselection() |
| 252 | if cs!=(): |
| 253 | value=self.component('filenamebox').get(cs) |
| 254 | self.setfilename(value) |
| 255 | |
| 256 | def selectfile(self): |
| 257 | """Take the selected file from the filename, normalize it, and OK""" |
| 258 | self.singleselectfile() |
| 259 | value=self.component('filename').get() |
| 260 | self.setfilename(value) |
| 261 | if value: |
| 262 | self.okbutton() |
| 263 | |
| 264 | def selectdir(self): |
| 265 | """Take selected directory from the dirnamebox into the dirname""" |
| 266 | cs=self.component('dirnamebox').curselection() |
| 267 | if cs!=(): |
| 268 | value=self.component('dirnamebox').get(cs) |
| 269 | dir=self['directory'] |
| 270 | if not dir: |
| 271 | dir=os.getcwd() |
| 272 | if value: |
| 273 | if value=='..': |
| 274 | dir=os.path.split(dir)[0] |
| 275 | else: |
| 276 | dir=os.path.join(dir,value) |
| 277 | self.configure(directory=dir) |
| 278 | self.fillit() |
| 279 | |
| 280 | def askfilename(self,directory=None,filter=None): |
| 281 | """The actual client function. Activates the dialog, and |
| 282 | returns only after a valid filename has been entered |
| 283 | (return value is that filename) or when canceled (return |
| 284 | value is None)""" |
| 285 | if directory!=None: |
| 286 | self.configure(directory=directory) |
| 287 | if filter!=None: |
| 288 | self.configure(filter=filter) |
| 289 | self.fillit() |
| 290 | self.canceled=1 # Needed for when user kills dialog window |
| 291 | self.activate() |
| 292 | if self.canceled: |
| 293 | return None |
| 294 | else: |
| 295 | return self.component('filename').get() |
| 296 | |
| 297 | lastdir="" |
| 298 | lastfilter=None |
| 299 | lasttime=0 |
| 300 | def fillit(self): |
| 301 | """Get the directory list and show it in the two listboxes""" |
| 302 | # Do not run unnecesarily |
| 303 | if self.lastdir==self['directory'] and self.lastfilter==self['filter'] and self.lasttime>os.stat(self.lastdir)[8]: |
| 304 | return |
| 305 | self.lastdir=self['directory'] |
| 306 | self.lastfilter=self['filter'] |
| 307 | self.lasttime=time.time() |
| 308 | dir=self['directory'] |
| 309 | if not dir: |
| 310 | dir=os.getcwd() |
| 311 | dirs=['..'] |
| 312 | files=[] |
| 313 | try: |
| 314 | fl=os.listdir(dir) |
| 315 | fl.sort() |
| 316 | except os.error,arg: |
| 317 | if arg[0] in (2,20): |
| 318 | return |
| 319 | raise |
| 320 | for f in fl: |
| 321 | if os.path.isdir(os.path.join(dir,f)): |
| 322 | dirs.append(f) |
| 323 | else: |
| 324 | filter=self['filter'] |
| 325 | if not filter: |
| 326 | filter='*' |
| 327 | if fnmatch.fnmatch(f,filter): |
| 328 | files.append(f) |
| 329 | self.component('filenamebox').setlist(files) |
| 330 | self.component('dirnamebox').setlist(dirs) |
| 331 | |
| 332 | def validate(self,filename): |
| 333 | """Validation function. Should return 1 if the filename is valid, |
| 334 | 0 if invalid. May pop up dialogs to tell user why. Especially |
| 335 | suited to subclasses: i.e. only return 1 if the file does/doesn't |
| 336 | exist""" |
| 337 | return 1 |
| 338 | |
| 339 | class PmwDirDialog(PmwFileDialog): |
| 340 | """Directory Dialog using Pmw""" |
| 341 | def __init__(self, parent = None, **kw): |
| 342 | # Define the megawidget options. |
| 343 | optiondefs = ( |
| 344 | ('directory', os.getcwd(), self.newdir), |
| 345 | ('historylen',10, None), |
| 346 | ('command', None, None), |
| 347 | ('info', None, None), |
| 348 | ) |
| 349 | self.defineoptions(kw, optiondefs) |
| 350 | # Initialise base class (after defining options). |
| 351 | Pmw.Dialog.__init__(self, parent) |
| 352 | |
| 353 | self.withdraw() |
| 354 | |
| 355 | # Create the components. |
| 356 | interior = self.interior() |
| 357 | |
| 358 | if self['info'] is not None: |
| 359 | rowoffset=1 |
| 360 | dn = self.infotxt() |
| 361 | dn.grid(row=0,column=0,columnspan=2,padx=3,pady=3) |
| 362 | else: |
| 363 | rowoffset=0 |
| 364 | |
| 365 | dn = self.mkdn() |
| 366 | dn.grid(row=1+rowoffset,column=0,columnspan=2,padx=3,pady=3) |
| 367 | dn.bind('<Return>',self.okbutton) |
| 368 | del dn |
| 369 | |
| 370 | # Create the directory list component. |
| 371 | dnb = self.mkdnb() |
| 372 | dnb.grid(row=0+rowoffset,column=0,columnspan=2,sticky='news',padx=3,pady=3) |
| 373 | del dnb |
| 374 | |
| 375 | # Buttonbox already exists |
| 376 | bb=self.component('buttonbox') |
| 377 | bb.add('OK',command=self.okbutton) |
| 378 | bb.add('Cancel',command=self.cancelbutton) |
| 379 | del bb |
| 380 | |
| 381 | lastdir="" |
| 382 | def fillit(self): |
| 383 | """Get the directory list and show it in the two listboxes""" |
| 384 | # Do not run unnecesarily |
| 385 | if self.lastdir==self['directory']: |
| 386 | return |
| 387 | self.lastdir=self['directory'] |
| 388 | dir=self['directory'] |
| 389 | if not dir: |
| 390 | dir=os.getcwd() |
| 391 | dirs=['..'] |
| 392 | try: |
| 393 | fl=os.listdir(dir) |
| 394 | fl.sort() |
| 395 | except os.error,arg: |
| 396 | if arg[0] in (2,20): |
| 397 | return |
| 398 | raise |
| 399 | for f in fl: |
| 400 | if os.path.isdir(os.path.join(dir,f)): |
| 401 | dirs.append(f) |
| 402 | self.component('dirnamebox').setlist(dirs) |
| 403 | |
| 404 | def okbutton(self): |
| 405 | """OK action: user thinks he has input valid data and wants to |
| 406 | proceed. This is also called by <Return> in the dirname entry""" |
| 407 | fn=self.component('dirname').get() |
| 408 | self.configure(directory=fn) |
| 409 | if self.validate(fn): |
| 410 | self.canceled=0 |
| 411 | self.deactivate() |
| 412 | |
| 413 | def askfilename(self,directory=None): |
| 414 | """The actual client function. Activates the dialog, and |
| 415 | returns only after a valid filename has been entered |
| 416 | (return value is that filename) or when canceled (return |
| 417 | value is None)""" |
| 418 | if directory!=None: |
| 419 | self.configure(directory=directory) |
| 420 | self.fillit() |
| 421 | self.activate() |
| 422 | if self.canceled: |
| 423 | return None |
| 424 | else: |
| 425 | return self.component('dirname').get() |
| 426 | |
| 427 | def dirvalidate(self,string): |
| 428 | if os.path.isdir(string): |
| 429 | return Pmw.OK |
| 430 | elif os.path.exists(string): |
| 431 | return Pmw.PARTIAL |
| 432 | else: |
| 433 | return Pmw.OK |
| 434 | |
| 435 | def validate(self,filename): |
| 436 | """Validation function. Should return 1 if the filename is valid, |
| 437 | 0 if invalid. May pop up dialogs to tell user why. Especially |
| 438 | suited to subclasses: i.e. only return 1 if the file does/doesn't |
| 439 | exist""" |
| 440 | if filename=='': |
| 441 | _errorpop(self.interior(),"Empty filename") |
| 442 | return 0 |
| 443 | if os.path.isdir(filename) or not os.path.exists(filename): |
| 444 | return 1 |
| 445 | else: |
| 446 | _errorpop(self.interior(),"This is not a directory") |
| 447 | return 0 |
| 448 | |
| 449 | class PmwExistingFileDialog(PmwFileDialog): |
| 450 | def filevalidate(self,string): |
| 451 | if os.path.isfile(string): |
| 452 | return Pmw.OK |
| 453 | else: |
| 454 | return Pmw.PARTIAL |
| 455 | |
| 456 | def validate(self,filename): |
| 457 | if os.path.isfile(filename): |
| 458 | return 1 |
| 459 | elif os.path.exists(filename): |
| 460 | _errorpop(self.interior(),"This is not a plain file") |
| 461 | return 0 |
| 462 | else: |
| 463 | _errorpop(self.interior(),"Please select an existing file") |
| 464 | return 0 |
| 465 | |
| 466 | class PmwExistingDirDialog(PmwDirDialog): |
| 467 | def dirvalidate(self,string): |
| 468 | if os.path.isdir(string): |
| 469 | return Pmw.OK |
| 470 | else: |
| 471 | return Pmw.PARTIAL |
| 472 | |
| 473 | def validate(self,filename): |
| 474 | if os.path.isdir(filename): |
| 475 | return 1 |
| 476 | elif os.path.exists(filename): |
| 477 | _errorpop(self.interior(),"This is not a directory") |
| 478 | return 0 |
| 479 | else: |
| 480 | _errorpop(self.interior(),"Please select an existing directory") |
| 481 | |
| 482 | if __name__=="__main__": |
| 483 | root=Tkinter.Tk() |
| 484 | root.withdraw() |
| 485 | Pmw.initialise() |
| 486 | |
| 487 | f0=PmwFileDialog(root) |
| 488 | f0.title('File name dialog') |
| 489 | n=f0.askfilename() |
| 490 | print '\nFilename : ',repr(n),'\n' |
| 491 | |
| 492 | f1=PmwDirDialog(root,info='This is a directory dialog') |
| 493 | f1.title('Directory name dialog') |
| 494 | while 1: |
| 495 | n=f1.askfilename() |
| 496 | if n is None: |
| 497 | break |
| 498 | print "Dirname : ",repr(n) |