Commit | Line | Data |
---|---|---|
920dae64 AT |
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) |