]> SALOME platform Git repositories - tools/eficas.git/blob - Pmw/Pmw_1_2/lib/PmwComboBox.py
Salome HOME
Modif V6_4_°
[tools/eficas.git] / Pmw / Pmw_1_2 / lib / PmwComboBox.py
1 # Based on iwidgets2.2.0/combobox.itk code.
2
3 import os
4 import string
5 import types
6 import Tkinter
7 import Pmw
8
9 class ComboBox(Pmw.MegaWidget):
10     def __init__(self, parent = None, **kw):
11
12         # Define the megawidget options.
13         INITOPT = Pmw.INITOPT
14         optiondefs = (
15             ('autoclear',          0,          INITOPT),
16             ('buttonaspect',       1.0,        INITOPT),
17             ('dropdown',           1,          INITOPT),
18             ('fliparrow',          0,          INITOPT),
19             ('history',            1,          INITOPT),
20             ('labelmargin',        0,          INITOPT),
21             ('labelpos',           None,       INITOPT),
22             ('listheight',         200,        INITOPT),
23             ('selectioncommand',   None,       None),
24             ('sticky',            'ew',        INITOPT),
25             ('unique',             1,          INITOPT),
26         )
27         self.defineoptions(kw, optiondefs)
28
29         # Initialise the base class (after defining the options).
30         Pmw.MegaWidget.__init__(self, parent)
31
32         # Create the components.
33         interior = self.interior()
34
35         self._entryfield = self.createcomponent('entryfield',
36                 (('entry', 'entryfield_entry'),), None,
37                 Pmw.EntryField, (interior,))
38         self._entryfield.grid(column=2, row=2, sticky=self['sticky'])
39         interior.grid_columnconfigure(2, weight = 1)
40         self._entryWidget = self._entryfield.component('entry')
41
42         if self['dropdown']:
43             self._isPosted = 0
44             interior.grid_rowconfigure(2, weight = 1)
45
46             # Create the arrow button.
47             self._arrowBtn = self.createcomponent('arrowbutton',
48                     (), None,
49                     Tkinter.Canvas, (interior,), borderwidth = 2,
50                     relief = 'raised',
51                     width = 16, height = 16)
52             if 'n' in self['sticky']:
53                 sticky = 'n'
54             else:
55                 sticky = ''
56             if 's' in self['sticky']:
57                 sticky = sticky + 's'
58             self._arrowBtn.grid(column=3, row=2, sticky = sticky)
59             self._arrowRelief = self._arrowBtn.cget('relief')
60
61             # Create the label.
62             self.createlabel(interior, childCols=2)
63
64             # Create the dropdown window.
65             self._popup = self.createcomponent('popup',
66                     (), None,
67                     Tkinter.Toplevel, (interior,))
68             self._popup.withdraw()
69             self._popup.overrideredirect(1)
70
71             # Create the scrolled listbox inside the dropdown window.
72             self._list = self.createcomponent('scrolledlist',
73                     (('listbox', 'scrolledlist_listbox'),), None,
74                     Pmw.ScrolledListBox, (self._popup,),
75                     hull_borderwidth = 2,
76                     hull_relief = 'raised',
77                     hull_height = self['listheight'],
78                     usehullsize = 1,
79                     listbox_exportselection = 0)
80             self._list.pack(expand=1, fill='both')
81             self.__listbox = self._list.component('listbox')
82
83             # Bind events to the arrow button.
84             self._arrowBtn.bind('<1>', self._postList)
85             self._arrowBtn.bind('<Configure>', self._drawArrow)
86             self._arrowBtn.bind('<3>', self._next)
87             self._arrowBtn.bind('<Shift-3>', self._previous)
88             self._arrowBtn.bind('<Down>', self._next)
89             self._arrowBtn.bind('<Up>', self._previous)
90             self._arrowBtn.bind('<Control-n>', self._next)
91             self._arrowBtn.bind('<Control-p>', self._previous)
92             self._arrowBtn.bind('<Shift-Down>', self._postList)
93             self._arrowBtn.bind('<Shift-Up>', self._postList)
94             self._arrowBtn.bind('<F34>', self._postList)
95             self._arrowBtn.bind('<F28>', self._postList)
96             self._arrowBtn.bind('<space>', self._postList)
97
98             # Bind events to the dropdown window.
99             self._popup.bind('<Escape>', self._unpostList)
100             self._popup.bind('<space>', self._selectUnpost)
101             self._popup.bind('<Return>', self._selectUnpost)
102             self._popup.bind('<ButtonRelease-1>', self._dropdownBtnRelease)
103             self._popup.bind('<ButtonPress-1>', self._unpostOnNextRelease)
104
105             # Bind events to the Tk listbox.
106             self.__listbox.bind('<Enter>', self._unpostOnNextRelease)
107
108             # Bind events to the Tk entry widget.
109             self._entryWidget.bind('<Configure>', self._resizeArrow)
110             self._entryWidget.bind('<Shift-Down>', self._postList)
111             self._entryWidget.bind('<Shift-Up>', self._postList)
112             self._entryWidget.bind('<F34>', self._postList)
113             self._entryWidget.bind('<F28>', self._postList)
114
115             # Need to unpost the popup if the entryfield is unmapped (eg: 
116             # its toplevel window is withdrawn) while the popup list is
117             # displayed.
118             self._entryWidget.bind('<Unmap>', self._unpostList)
119
120         else:
121             # Create the scrolled listbox below the entry field.
122             self._list = self.createcomponent('scrolledlist',
123                     (('listbox', 'scrolledlist_listbox'),), None,
124                     Pmw.ScrolledListBox, (interior,),
125                     selectioncommand = self._selectCmd)
126             self._list.grid(column=2, row=3, sticky='nsew')
127             self.__listbox = self._list.component('listbox')
128
129             # The scrolled listbox should expand vertically.
130             interior.grid_rowconfigure(3, weight = 1)
131
132             # Create the label.
133             self.createlabel(interior, childRows=2)
134
135         self._entryWidget.bind('<Down>', self._next)
136         self._entryWidget.bind('<Up>', self._previous)
137         self._entryWidget.bind('<Control-n>', self._next)
138         self._entryWidget.bind('<Control-p>', self._previous)
139         self.__listbox.bind('<Control-n>', self._next)
140         self.__listbox.bind('<Control-p>', self._previous)
141
142         if self['history']:
143             self._entryfield.configure(command=self._addHistory)
144
145         # Check keywords and initialise options.
146         self.initialiseoptions()
147
148     def destroy(self):
149         if self['dropdown'] and self._isPosted:
150             Pmw.popgrab(self._popup)
151         Pmw.MegaWidget.destroy(self)
152
153     #======================================================================
154
155     # Public methods
156
157     def get(self, first = None, last=None):
158         if first is None:
159             return self._entryWidget.get()
160         else:
161             return self._list.get(first, last)
162
163     def invoke(self):
164         if self['dropdown']:
165             self._postList()
166         else:
167             return self._selectCmd()
168
169     def selectitem(self, index, setentry=1):
170         if type(index) == types.StringType:
171             text = index
172             items = self._list.get(0, 'end')
173             if text in items:
174                 index = list(items).index(text)
175             else:
176                 raise IndexError, 'index "%s" not found' % text
177         elif setentry:
178             text = self._list.get(0, 'end')[index]
179
180         self._list.select_clear(0, 'end')
181         self._list.select_set(index, index)
182         self._list.activate(index)
183         self.see(index)
184         if setentry:
185             self._entryfield.setentry(text)
186
187     # Need to explicitly forward this to override the stupid
188     # (grid_)size method inherited from Tkinter.Frame.Grid.
189     def size(self):
190         return self._list.size()
191
192     # Need to explicitly forward this to override the stupid
193     # (grid_)bbox method inherited from Tkinter.Frame.Grid.
194     def bbox(self, index):
195         return self._list.bbox(index)
196
197     def clear(self):
198         self._entryfield.clear()
199         self._list.clear()
200
201     #======================================================================
202
203     # Private methods for both dropdown and simple comboboxes.
204
205     def _addHistory(self):
206         input = self._entryWidget.get()
207
208         if input != '':
209             index = None
210             if self['unique']:
211                 # If item is already in list, select it and return.
212                 items = self._list.get(0, 'end')
213                 if input in items:
214                     index = list(items).index(input)
215
216             if index is None:
217                 index = self._list.index('end')
218                 self._list.insert('end', input)
219
220             self.selectitem(index)
221             if self['autoclear']:
222                 self._entryWidget.delete(0, 'end')
223
224             # Execute the selectioncommand on the new entry.
225             self._selectCmd()
226
227     def _next(self, event):
228         size = self.size()
229         if size <= 1:
230             return
231
232         cursels = self.curselection()
233
234         if len(cursels) == 0:
235             index = 0
236         else:
237             index = string.atoi(cursels[0])
238             if index == size - 1:
239                 index = 0
240             else:
241                 index = index + 1
242
243         self.selectitem(index)
244
245     def _previous(self, event):
246         size = self.size()
247         if size <= 1:
248             return
249
250         cursels = self.curselection()
251
252         if len(cursels) == 0:
253             index = size - 1
254         else:
255             index = string.atoi(cursels[0])
256             if index == 0:
257                 index = size - 1
258             else:
259                 index = index - 1
260
261         self.selectitem(index)
262
263     def _selectCmd(self, event=None):
264
265         sels = self.getcurselection()
266         if len(sels) == 0:
267             item = None
268         else:
269             item = sels[0]
270             self._entryfield.setentry(item)
271
272         cmd = self['selectioncommand']
273         if callable(cmd):
274             if event is None:
275                 # Return result of selectioncommand for invoke() method.
276                 return cmd(item)
277             else:
278                 cmd(item)
279
280     #======================================================================
281
282     # Private methods for dropdown combobox.
283
284     def _drawArrow(self, event=None, sunken=0):
285         arrow = self._arrowBtn
286         if sunken:
287             self._arrowRelief = arrow.cget('relief')
288             arrow.configure(relief = 'sunken')
289         else:
290             arrow.configure(relief = self._arrowRelief)
291
292         if self._isPosted and self['fliparrow']:
293             direction = 'up'
294         else:
295             direction = 'down'
296         Pmw.drawarrow(arrow, self['entry_foreground'], direction, 'arrow')
297
298     def _postList(self, event = None):
299         self._isPosted = 1
300         self._drawArrow(sunken=1)
301
302         # Make sure that the arrow is displayed sunken.
303         self.update_idletasks()
304
305         x = self._entryfield.winfo_rootx()
306         y = self._entryfield.winfo_rooty() + \
307             self._entryfield.winfo_height()
308         w = self._entryfield.winfo_width() + self._arrowBtn.winfo_width()
309         h =  self.__listbox.winfo_height()
310         sh = self.winfo_screenheight()
311
312         if y + h > sh and y > sh / 2:
313             y = self._entryfield.winfo_rooty() - h
314
315         self._list.configure(hull_width=w)
316
317         Pmw.setgeometryanddeiconify(self._popup, '+%d+%d' % (x, y))
318
319         # Grab the popup, so that all events are delivered to it, and
320         # set focus to the listbox, to make keyboard navigation
321         # easier.
322         Pmw.pushgrab(self._popup, 1, self._unpostList)
323         self.__listbox.focus_set()
324
325         self._drawArrow()
326
327         # Ignore the first release of the mouse button after posting the
328         # dropdown list, unless the mouse enters the dropdown list.
329         self._ignoreRelease = 1
330
331     def _dropdownBtnRelease(self, event):
332         if (event.widget == self._list.component('vertscrollbar') or
333                 event.widget == self._list.component('horizscrollbar')):
334             return
335
336         if self._ignoreRelease:
337             self._unpostOnNextRelease()
338             return
339
340         self._unpostList()
341
342         if (event.x >= 0 and event.x < self.__listbox.winfo_width() and
343                 event.y >= 0 and event.y < self.__listbox.winfo_height()):
344             self._selectCmd()
345
346     def _unpostOnNextRelease(self, event = None):
347         self._ignoreRelease = 0
348
349     def _resizeArrow(self, event):
350         bw = (string.atoi(self._arrowBtn['borderwidth']) + 
351                 string.atoi(self._arrowBtn['highlightthickness']))
352         newHeight = self._entryfield.winfo_reqheight() - 2 * bw
353         newWidth = int(newHeight * self['buttonaspect'])
354         self._arrowBtn.configure(width=newWidth, height=newHeight)
355         self._drawArrow()
356
357     def _unpostList(self, event=None):
358         if not self._isPosted:
359             # It is possible to get events on an unposted popup.  For
360             # example, by repeatedly pressing the space key to post
361             # and unpost the popup.  The <space> event may be
362             # delivered to the popup window even though
363             # Pmw.popgrab() has set the focus away from the
364             # popup window.  (Bug in Tk?)
365             return
366
367         # Restore the focus before withdrawing the window, since
368         # otherwise the window manager may take the focus away so we
369         # can't redirect it.  Also, return the grab to the next active
370         # window in the stack, if any.
371         Pmw.popgrab(self._popup)
372         self._popup.withdraw()
373
374         self._isPosted = 0
375         self._drawArrow()
376
377     def _selectUnpost(self, event):
378         self._unpostList()
379         self._selectCmd()
380
381 Pmw.forwardmethods(ComboBox, Pmw.ScrolledListBox, '_list')
382 Pmw.forwardmethods(ComboBox, Pmw.EntryField, '_entryfield')