]> SALOME platform Git repositories - tools/eficas.git/blob - Pmw/Pmw_1_2/lib/PmwScrolledText.py
Salome HOME
Modif V6_4_°
[tools/eficas.git] / Pmw / Pmw_1_2 / lib / PmwScrolledText.py
1 # Based on iwidgets2.2.0/scrolledtext.itk code.   
2
3 import Tkinter
4 import Pmw
5
6 class ScrolledText(Pmw.MegaWidget):
7     def __init__(self, parent = None, **kw):
8
9         # Define the megawidget options.
10         INITOPT = Pmw.INITOPT
11         optiondefs = (
12             ('borderframe',    0,            INITOPT),
13             ('columnheader',   0,            INITOPT),
14             ('hscrollmode',    'dynamic',    self._hscrollMode),
15             ('labelmargin',    0,            INITOPT),
16             ('labelpos',       None,         INITOPT),
17             ('rowcolumnheader',0,            INITOPT),
18             ('rowheader',      0,            INITOPT),
19             ('scrollmargin',   2,            INITOPT),
20             ('usehullsize',    0,            INITOPT),
21             ('vscrollmode',    'dynamic',    self._vscrollMode),
22         )
23         self.defineoptions(kw, optiondefs)
24
25         # Initialise the base class (after defining the options).
26         Pmw.MegaWidget.__init__(self, parent)
27
28         # Create the components.
29         interior = self.interior()
30
31         if self['usehullsize']:
32             interior.grid_propagate(0)
33
34         if self['borderframe']:
35             # Create a frame widget to act as the border of the text 
36             # widget.  Later, pack the text widget so that it fills
37             # the frame.  This avoids a problem in Tk, where window
38             # items in a text widget may overlap the border of the
39             # text widget.
40             self._borderframe = self.createcomponent('borderframe',
41                     (), None,
42                     Tkinter.Frame, (interior,),
43                     relief = 'sunken',
44                     borderwidth = 2,
45             )
46             self._borderframe.grid(row = 4, column = 4, sticky = 'news')
47
48             # Create the text widget.
49             self._textbox = self.createcomponent('text',
50                     (), None,
51                     Tkinter.Text, (self._borderframe,),
52                     highlightthickness = 0,
53                     borderwidth = 0,
54             )
55             self._textbox.pack(fill = 'both', expand = 1)
56
57             bw = self._borderframe.cget('borderwidth'),
58             ht = self._borderframe.cget('highlightthickness'),
59         else:
60             # Create the text widget.
61             self._textbox = self.createcomponent('text',
62                     (), None,
63                     Tkinter.Text, (interior,),
64             )
65             self._textbox.grid(row = 4, column = 4, sticky = 'news')
66
67             bw = self._textbox.cget('borderwidth'),
68             ht = self._textbox.cget('highlightthickness'),
69
70         # Create the header text widgets
71         if self['columnheader']:
72             self._columnheader = self.createcomponent('columnheader',
73                     (), 'Header',
74                     Tkinter.Text, (interior,),
75                     height=1,
76                     wrap='none',
77                     borderwidth = bw,
78                     highlightthickness = ht,
79             )
80             self._columnheader.grid(row = 2, column = 4, sticky = 'ew')
81             self._columnheader.configure(
82                     xscrollcommand = self._columnheaderscrolled)
83
84         if self['rowheader']:
85             self._rowheader = self.createcomponent('rowheader',
86                     (), 'Header',
87                     Tkinter.Text, (interior,),
88                     wrap='none',
89                     borderwidth = bw,
90                     highlightthickness = ht,
91             )
92             self._rowheader.grid(row = 4, column = 2, sticky = 'ns')
93             self._rowheader.configure(
94                     yscrollcommand = self._rowheaderscrolled)
95
96         if self['rowcolumnheader']:
97             self._rowcolumnheader = self.createcomponent('rowcolumnheader',
98                     (), 'Header',
99                     Tkinter.Text, (interior,),
100                     height=1,
101                     wrap='none',
102                     borderwidth = bw,
103                     highlightthickness = ht,
104             )
105             self._rowcolumnheader.grid(row = 2, column = 2, sticky = 'nsew')
106
107         interior.grid_rowconfigure(4, weight = 1, minsize = 0)
108         interior.grid_columnconfigure(4, weight = 1, minsize = 0)
109
110         # Create the horizontal scrollbar
111         self._horizScrollbar = self.createcomponent('horizscrollbar',
112                 (), 'Scrollbar',
113                 Tkinter.Scrollbar, (interior,),
114                 orient='horizontal',
115                 command=self._textbox.xview
116         )
117
118         # Create the vertical scrollbar
119         self._vertScrollbar = self.createcomponent('vertscrollbar',
120                 (), 'Scrollbar',
121                 Tkinter.Scrollbar, (interior,),
122                 orient='vertical',
123                 command=self._textbox.yview
124         )
125
126         self.createlabel(interior, childCols = 5, childRows = 5)
127
128         # Initialise instance variables.
129         self._horizScrollbarOn = 0
130         self._vertScrollbarOn = 0
131         self.scrollTimer = None
132         self._scrollRecurse = 0
133         self._horizScrollbarNeeded = 0
134         self._vertScrollbarNeeded = 0
135         self._textWidth = None
136
137         # These four variables avoid an infinite loop caused by the
138         # row or column header's scrollcommand causing the main text
139         # widget's scrollcommand to be called and vice versa.
140         self._textboxLastX = None
141         self._textboxLastY = None
142         self._columnheaderLastX = None
143         self._rowheaderLastY = None
144
145         # Check keywords and initialise options.
146         self.initialiseoptions()
147
148     def destroy(self):
149         if self.scrollTimer is not None:
150             self.after_cancel(self.scrollTimer)
151             self.scrollTimer = None
152         Pmw.MegaWidget.destroy(self)
153
154     # ======================================================================
155
156     # Public methods.
157
158     def clear(self):
159         self.settext('')
160
161     def importfile(self, fileName, where = 'end'):
162         file = open(fileName, 'r')
163         self._textbox.insert(where, file.read())
164         file.close()
165
166     def exportfile(self, fileName):
167         file = open(fileName, 'w')
168         file.write(self._textbox.get('1.0', 'end'))
169         file.close()
170
171     def settext(self, text):
172         disabled = (str(self._textbox.cget('state')) == 'disabled')
173         if disabled:
174             self._textbox.configure(state='normal')
175         self._textbox.delete('0.0', 'end')
176         self._textbox.insert('end', text)
177         if disabled:
178             self._textbox.configure(state='disabled')
179
180     # Override Tkinter.Text get method, so that if it is called with
181     # no arguments, return all text (consistent with other widgets).
182     def get(self, first=None, last=None):
183         if first is None:
184             return self._textbox.get('1.0', 'end')
185         else:
186             return self._textbox.get(first, last)
187
188     def getvalue(self):
189         return self.get()
190
191     def setvalue(self, text):
192         return self.settext(text)
193
194     def appendtext(self, text):
195         oldTop, oldBottom = self._textbox.yview()
196      
197         disabled = (str(self._textbox.cget('state')) == 'disabled')
198         if disabled:
199             self._textbox.configure(state='normal')
200         self._textbox.insert('end', text)
201         if disabled:
202             self._textbox.configure(state='disabled')
203      
204         if oldBottom == 1.0:
205             self._textbox.yview('moveto', 1.0)
206
207     # ======================================================================
208
209     # Configuration methods.
210
211     def _hscrollMode(self):
212         # The horizontal scroll mode has been configured.
213
214         mode = self['hscrollmode']
215
216         if mode == 'static':
217             if not self._horizScrollbarOn:
218                 self._toggleHorizScrollbar()
219         elif mode == 'dynamic':
220             if self._horizScrollbarNeeded != self._horizScrollbarOn:
221                 self._toggleHorizScrollbar()
222         elif mode == 'none':
223             if self._horizScrollbarOn:
224                 self._toggleHorizScrollbar()
225         else:
226             message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode
227             raise ValueError, message
228
229         self._configureScrollCommands()
230
231     def _vscrollMode(self):
232         # The vertical scroll mode has been configured.
233
234         mode = self['vscrollmode']
235
236         if mode == 'static':
237             if not self._vertScrollbarOn:
238                 self._toggleVertScrollbar()
239         elif mode == 'dynamic':
240             if self._vertScrollbarNeeded != self._vertScrollbarOn:
241                 self._toggleVertScrollbar()
242         elif mode == 'none':
243             if self._vertScrollbarOn:
244                 self._toggleVertScrollbar()
245         else:
246             message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode
247             raise ValueError, message
248
249         self._configureScrollCommands()
250
251     # ======================================================================
252
253     # Private methods.
254
255     def _configureScrollCommands(self):
256         # If both scrollmodes are not dynamic we can save a lot of
257         # time by not having to create an idle job to handle the
258         # scroll commands.
259
260         # Clean up previous scroll commands to prevent memory leak.
261         tclCommandName = str(self._textbox.cget('xscrollcommand'))
262         if tclCommandName != '':   
263             self._textbox.deletecommand(tclCommandName)
264         tclCommandName = str(self._textbox.cget('yscrollcommand'))
265         if tclCommandName != '':   
266             self._textbox.deletecommand(tclCommandName)
267
268         if self['hscrollmode'] == self['vscrollmode'] == 'dynamic':
269             self._textbox.configure(
270                     xscrollcommand=self._scrollBothLater,
271                     yscrollcommand=self._scrollBothLater
272             )
273         else:
274             self._textbox.configure(
275                     xscrollcommand=self._scrollXNow,
276                     yscrollcommand=self._scrollYNow
277             )
278
279     def _scrollXNow(self, first, last):
280         self._horizScrollbar.set(first, last)
281         self._horizScrollbarNeeded = ((first, last) != ('0', '1'))
282
283         # This code is the same as in _scrollBothNow.  Keep it that way.
284         if self['hscrollmode'] == 'dynamic':
285             currentWidth = self._textbox.winfo_width()
286             if self._horizScrollbarNeeded != self._horizScrollbarOn:
287                 if self._horizScrollbarNeeded or \
288                         self._textWidth != currentWidth:
289                     self._toggleHorizScrollbar()
290             self._textWidth = currentWidth
291
292         if self['columnheader']:
293             if self._columnheaderLastX != first:
294                 self._columnheaderLastX = first
295                 self._columnheader.xview('moveto', first)
296
297     def _scrollYNow(self, first, last):
298         if first == '0' and last == '0':
299             return
300         self._vertScrollbar.set(first, last)
301         self._vertScrollbarNeeded = ((first, last) != ('0', '1'))
302
303         if self['vscrollmode'] == 'dynamic':
304             if self._vertScrollbarNeeded != self._vertScrollbarOn:
305                 self._toggleVertScrollbar()
306
307         if self['rowheader']:
308             if self._rowheaderLastY != first:
309                 self._rowheaderLastY = first
310                 self._rowheader.yview('moveto', first)
311
312     def _scrollBothLater(self, first, last):
313         # Called by the text widget to set the horizontal or vertical
314         # scrollbar when it has scrolled or changed size or contents.
315
316         if self.scrollTimer is None:
317             self.scrollTimer = self.after_idle(self._scrollBothNow)
318
319     def _scrollBothNow(self):
320         # This performs the function of _scrollXNow and _scrollYNow.
321         # If one is changed, the other should be updated to match.
322         self.scrollTimer = None
323
324         # Call update_idletasks to make sure that the containing frame
325         # has been resized before we attempt to set the scrollbars. 
326         # Otherwise the scrollbars may be mapped/unmapped continuously.
327         self._scrollRecurse = self._scrollRecurse + 1
328         self.update_idletasks()
329         self._scrollRecurse = self._scrollRecurse - 1
330         if self._scrollRecurse != 0:
331             return
332
333         xview = self._textbox.xview()
334         yview = self._textbox.yview()
335
336         # The text widget returns a yview of (0.0, 0.0) just after it
337         # has been created. Ignore this.
338         if yview == (0.0, 0.0):
339             return
340
341         if self['columnheader']:
342             if self._columnheaderLastX != xview[0]:
343                 self._columnheaderLastX = xview[0]
344                 self._columnheader.xview('moveto', xview[0])
345         if self['rowheader']:
346             if self._rowheaderLastY != yview[0]:
347                 self._rowheaderLastY = yview[0]
348                 self._rowheader.yview('moveto', yview[0])
349
350         self._horizScrollbar.set(xview[0], xview[1])
351         self._vertScrollbar.set(yview[0], yview[1])
352
353         self._horizScrollbarNeeded = (xview != (0.0, 1.0))
354         self._vertScrollbarNeeded = (yview != (0.0, 1.0))
355
356         # If both horizontal and vertical scrollmodes are dynamic and
357         # currently only one scrollbar is mapped and both should be
358         # toggled, then unmap the mapped scrollbar.  This prevents a
359         # continuous mapping and unmapping of the scrollbars. 
360         if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and
361                 self._horizScrollbarNeeded != self._horizScrollbarOn and
362                 self._vertScrollbarNeeded != self._vertScrollbarOn and
363                 self._vertScrollbarOn != self._horizScrollbarOn):
364             if self._horizScrollbarOn:
365                 self._toggleHorizScrollbar()
366             else:
367                 self._toggleVertScrollbar()
368             return
369
370         if self['hscrollmode'] == 'dynamic':
371
372             # The following test is done to prevent continuous
373             # mapping and unmapping of the horizontal scrollbar. 
374             # This may occur when some event (scrolling, resizing
375             # or text changes) modifies the displayed text such
376             # that the bottom line in the window is the longest
377             # line displayed.  If this causes the horizontal
378             # scrollbar to be mapped, the scrollbar may "cover up"
379             # the bottom line, which would mean that the scrollbar
380             # is no longer required.  If the scrollbar is then
381             # unmapped, the bottom line will then become visible
382             # again, which would cause the scrollbar to be mapped
383             # again, and so on...
384             #
385             # The idea is that, if the width of the text widget
386             # has not changed and the scrollbar is currently
387             # mapped, then do not unmap the scrollbar even if it
388             # is no longer required.  This means that, during
389             # normal scrolling of the text, once the horizontal
390             # scrollbar has been mapped it will not be unmapped
391             # (until the width of the text widget changes).
392
393             currentWidth = self._textbox.winfo_width()
394             if self._horizScrollbarNeeded != self._horizScrollbarOn:
395                 if self._horizScrollbarNeeded or \
396                         self._textWidth != currentWidth:
397                     self._toggleHorizScrollbar()
398             self._textWidth = currentWidth
399
400         if self['vscrollmode'] == 'dynamic':
401             if self._vertScrollbarNeeded != self._vertScrollbarOn:
402                 self._toggleVertScrollbar()
403
404     def _columnheaderscrolled(self, first, last):
405         if self._textboxLastX != first:
406             self._textboxLastX = first
407             self._textbox.xview('moveto', first)
408
409     def _rowheaderscrolled(self, first, last):
410         if self._textboxLastY != first:
411             self._textboxLastY = first
412             self._textbox.yview('moveto', first)
413
414     def _toggleHorizScrollbar(self):
415
416         self._horizScrollbarOn = not self._horizScrollbarOn
417
418         interior = self.interior()
419         if self._horizScrollbarOn:
420             self._horizScrollbar.grid(row = 6, column = 4, sticky = 'news')
421             interior.grid_rowconfigure(5, minsize = self['scrollmargin'])
422         else:
423             self._horizScrollbar.grid_forget()
424             interior.grid_rowconfigure(5, minsize = 0)
425
426     def _toggleVertScrollbar(self):
427
428         self._vertScrollbarOn = not self._vertScrollbarOn
429
430         interior = self.interior()
431         if self._vertScrollbarOn:
432             self._vertScrollbar.grid(row = 4, column = 6, sticky = 'news')
433             interior.grid_columnconfigure(5, minsize = self['scrollmargin'])
434         else:
435             self._vertScrollbar.grid_forget()
436             interior.grid_columnconfigure(5, minsize = 0)
437
438     # Need to explicitly forward this to override the stupid
439     # (grid_)bbox method inherited from Tkinter.Frame.Grid.
440     def bbox(self, index):
441         return self._textbox.bbox(index)
442
443 Pmw.forwardmethods(ScrolledText, Tkinter.Text, '_textbox')