]> SALOME platform Git repositories - tools/eficas.git/blob - Pmw/Pmw_1_2/lib/PmwNoteBook.py
Salome HOME
Modif V6_4_°
[tools/eficas.git] / Pmw / Pmw_1_2 / lib / PmwNoteBook.py
1 import string
2 import types
3 import Tkinter
4 import Pmw
5
6 class NoteBook(Pmw.MegaArchetype):
7
8     def __init__(self, parent = None, **kw):
9
10         # Define the megawidget options.
11         INITOPT = Pmw.INITOPT
12         optiondefs = (
13             ('hull_highlightthickness',  0,           None),
14             ('hull_borderwidth',         0,           None),
15             ('arrownavigation',          1,           INITOPT),
16             ('borderwidth',              2,           INITOPT),
17             ('createcommand',            None,        None),
18             ('lowercommand',             None,        None),
19             ('pagemargin',               4,           INITOPT),
20             ('raisecommand',             None,        None),
21             ('tabpos',                   'n',         INITOPT),
22         )
23         self.defineoptions(kw, optiondefs, dynamicGroups = ('Page', 'Tab'))
24
25         # Initialise the base class (after defining the options).
26         Pmw.MegaArchetype.__init__(self, parent, Tkinter.Canvas)
27
28         self.bind('<Map>', self._handleMap)
29         self.bind('<Configure>', self._handleConfigure)
30
31         tabpos = self['tabpos']
32         if tabpos is not None and tabpos != 'n':
33             raise ValueError, \
34                 'bad tabpos option %s:  should be n or None' % repr(tabpos)
35         self._withTabs = (tabpos is not None)
36         self._pageMargin = self['pagemargin']
37         self._borderWidth = self['borderwidth']
38
39         # Use a dictionary as a set of bits indicating what needs to
40         # be redisplayed the next time _layout() is called.  If
41         # dictionary contains 'topPage' key, the value is the new top
42         # page to be displayed.  None indicates that all pages have
43         # been deleted and that _layout() should draw a border under where
44         # the tabs should be.
45         self._pending = {}
46         self._pending['size'] = 1
47         self._pending['borderColor'] = 1
48         self._pending['topPage'] = None
49         if self._withTabs:
50             self._pending['tabs'] = 1
51
52         self._canvasSize = None       # This gets set by <Configure> events
53
54         # Set initial height of space for tabs
55         if self._withTabs:
56             self.tabBottom = 35
57         else:
58             self.tabBottom = 0
59
60         self._lightBorderColor, self._darkBorderColor = \
61                 Pmw.Color.bordercolors(self, self['hull_background'])
62
63         self._pageNames   = []        # List of page names
64
65         # Map from page name to page info.  Each item is itself a
66         # dictionary containing the following items:
67         #   page           the Tkinter.Frame widget for the page
68         #   created        set to true the first time the page is raised
69         #   tabbutton      the Tkinter.Button widget for the button (if any)
70         #   tabreqwidth    requested width of the tab
71         #   tabreqheight   requested height of the tab
72         #   tabitems       the canvas items for the button: the button
73         #                  window item, the lightshadow and the darkshadow
74         #   left           the left and right canvas coordinates of the tab
75         #   right
76         self._pageAttrs   = {}
77
78         # Name of page currently on top (actually displayed, using
79         # create_window, not pending).  Ignored if current top page
80         # has been deleted or new top page is pending.  None indicates
81         # no pages in notebook.
82         self._topPageName = None
83
84         # Canvas items used:
85         #   Per tab: 
86         #       top and left shadow
87         #       right shadow
88         #       button
89         #   Per notebook: 
90         #       page
91         #       top page
92         #       left shadow
93         #       bottom and right shadow
94         #       top (one or two items)
95
96         # Canvas tags used:
97         #   lighttag      - top and left shadows of tabs and page
98         #   darktag       - bottom and right shadows of tabs and page
99         #                   (if no tabs then these are reversed)
100         #                   (used to color the borders by recolorborders)
101
102         # Create page border shadows.
103         if self._withTabs:
104             self._pageLeftBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
105                 fill = self._lightBorderColor, tags = 'lighttag')
106             self._pageBottomRightBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
107                 fill = self._darkBorderColor, tags = 'darktag')
108             self._pageTop1Border = self.create_polygon(0, 0, 0, 0, 0, 0,
109                 fill = self._darkBorderColor, tags = 'lighttag')
110             self._pageTop2Border = self.create_polygon(0, 0, 0, 0, 0, 0,
111                 fill = self._darkBorderColor, tags = 'lighttag')
112         else:
113             self._pageLeftBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
114                 fill = self._darkBorderColor, tags = 'darktag')
115             self._pageBottomRightBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
116                 fill = self._lightBorderColor, tags = 'lighttag')
117             self._pageTopBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
118                 fill = self._darkBorderColor, tags = 'darktag')
119
120         # Check keywords and initialise options.
121         self.initialiseoptions()
122
123     def insert(self, pageName, before = 0, **kw):
124         if self._pageAttrs.has_key(pageName):
125             msg = 'Page "%s" already exists.' % pageName
126             raise ValueError, msg
127
128         # Do this early to catch bad <before> spec before creating any items.
129         beforeIndex = self.index(before, 1)
130
131         pageOptions = {}
132         if self._withTabs:
133             # Default tab button options.
134             tabOptions = {
135                 'text' : pageName,
136                 'borderwidth' : 0,
137             }
138
139         # Divide the keyword options into the 'page_' and 'tab_' options.
140         for key in kw.keys():
141             if key[:5] == 'page_':
142                 pageOptions[key[5:]] = kw[key]
143                 del kw[key]
144             elif self._withTabs and key[:4] == 'tab_':
145                 tabOptions[key[4:]] = kw[key]
146                 del kw[key]
147             else:
148                 raise KeyError, 'Unknown option "' + key + '"'
149
150         # Create the frame to contain the page.
151         page = apply(self.createcomponent, (pageName,
152                 (), 'Page',
153                 Tkinter.Frame, self._hull), pageOptions)
154
155         attributes = {}
156         attributes['page'] = page
157         attributes['created'] = 0
158
159         if self._withTabs:
160             # Create the button for the tab.
161             def raiseThisPage(self = self, pageName = pageName):
162                 self.selectpage(pageName)
163             tabOptions['command'] = raiseThisPage
164             tab = apply(self.createcomponent, (pageName + '-tab',
165                     (), 'Tab',
166                     Tkinter.Button, self._hull), tabOptions)
167
168             if self['arrownavigation']:
169                 # Allow the use of the arrow keys for Tab navigation:
170                 def next(event, self = self, pageName = pageName):
171                     self.nextpage(pageName)
172                 def prev(event, self = self, pageName = pageName):
173                     self.previouspage(pageName)
174                 tab.bind('<Left>', prev)
175                 tab.bind('<Right>', next)
176
177             attributes['tabbutton'] = tab
178             attributes['tabreqwidth'] = tab.winfo_reqwidth()
179             attributes['tabreqheight'] = tab.winfo_reqheight()
180
181             # Create the canvas item to manage the tab's button and the items
182             # for the tab's shadow.
183             windowitem = self.create_window(0, 0, window = tab, anchor = 'nw')
184             lightshadow = self.create_polygon(0, 0, 0, 0, 0, 0,
185                 tags = 'lighttag', fill = self._lightBorderColor)
186             darkshadow = self.create_polygon(0, 0, 0, 0, 0, 0,
187                 tags = 'darktag', fill = self._darkBorderColor)
188             attributes['tabitems'] = (windowitem, lightshadow, darkshadow)
189             self._pending['tabs'] = 1
190
191         self._pageAttrs[pageName] = attributes
192         self._pageNames.insert(beforeIndex, pageName)
193
194         # If this is the first page added, make it the new top page
195         # and call the create and raise callbacks.
196         if self.getcurselection() is None:
197             self._pending['topPage'] = pageName
198             self._raiseNewTop(pageName)
199
200         self._layout()
201         return page
202                 
203     def add(self, pageName, **kw):
204         return apply(self.insert, (pageName, len(self._pageNames)), kw)
205
206     def delete(self, *pageNames):
207         newTopPage = 0
208         for page in pageNames:
209             pageIndex = self.index(page)
210             pageName = self._pageNames[pageIndex]
211             pageInfo = self._pageAttrs[pageName]
212
213             if self.getcurselection() == pageName:
214                 if len(self._pageNames) == 1:
215                     newTopPage = 0
216                     self._pending['topPage'] = None
217                 elif pageIndex == len(self._pageNames) - 1:
218                     newTopPage = 1
219                     self._pending['topPage'] = self._pageNames[pageIndex - 1]
220                 else:
221                     newTopPage = 1
222                     self._pending['topPage'] = self._pageNames[pageIndex + 1]
223
224             if self._topPageName == pageName:
225                 self._hull.delete(self._topPageItem)
226                 self._topPageName = None
227                                 
228             if self._withTabs:
229                 self.destroycomponent(pageName + '-tab')
230                 apply(self._hull.delete, pageInfo['tabitems'])
231             self.destroycomponent(pageName)
232             del self._pageAttrs[pageName]
233             del self._pageNames[pageIndex]
234
235         # If the old top page was deleted and there are still pages
236         # left in the notebook, call the create and raise callbacks.
237         if newTopPage:
238             pageName = self._pending['topPage']
239             self._raiseNewTop(pageName)
240
241         if self._withTabs:
242             self._pending['tabs'] = 1
243         self._layout()
244
245     def page(self, pageIndex):
246         pageName = self._pageNames[self.index(pageIndex)]
247         return self._pageAttrs[pageName]['page']
248
249     def pagenames(self):
250         return list(self._pageNames)
251
252     def getcurselection(self):
253         if self._pending.has_key('topPage'):
254             return self._pending['topPage']
255         else:
256             return self._topPageName
257
258     def tab(self, pageIndex):
259         if self._withTabs:
260             pageName = self._pageNames[self.index(pageIndex)]
261             return self._pageAttrs[pageName]['tabbutton']
262         else:
263             return None
264
265     def index(self, index, forInsert = 0):
266         listLength = len(self._pageNames)
267         if type(index) == types.IntType:
268             if forInsert and index <= listLength:
269                 return index
270             elif not forInsert and index < listLength:
271                 return index
272             else:
273                 raise ValueError, 'index "%s" is out of range' % index
274         elif index is Pmw.END:
275             if forInsert:
276                 return listLength
277             elif listLength > 0:
278                 return listLength - 1
279             else:
280                 raise ValueError, 'NoteBook has no pages'
281         elif index is Pmw.SELECT:
282             if listLength == 0:
283                 raise ValueError, 'NoteBook has no pages'
284             return self._pageNames.index(self.getcurselection())
285         else:
286             if index in self._pageNames:
287                 return self._pageNames.index(index)
288             validValues = 'a name, a number, Pmw.END or Pmw.SELECT'
289             raise ValueError, \
290                 'bad index "%s": must be %s' % (index, validValues)
291
292     def selectpage(self, page):
293         pageName = self._pageNames[self.index(page)]
294         oldTopPage = self.getcurselection()
295         if pageName != oldTopPage:
296             self._pending['topPage'] = pageName
297             if oldTopPage == self._topPageName:
298                 self._hull.delete(self._topPageItem)
299             cmd = self['lowercommand']
300             if cmd is not None:
301                 cmd(oldTopPage)
302             self._raiseNewTop(pageName)
303
304             self._layout()
305
306         # Set focus to the tab of new top page:
307         if self._withTabs and self['arrownavigation']:
308             self._pageAttrs[pageName]['tabbutton'].focus_set()
309
310     def previouspage(self, pageIndex = None):
311         if pageIndex is None:
312             curpage = self.index(Pmw.SELECT)
313         else:
314             curpage = self.index(pageIndex)
315         if curpage > 0:
316             self.selectpage(curpage - 1)
317
318     def nextpage(self, pageIndex = None):
319         if pageIndex is None:
320             curpage = self.index(Pmw.SELECT)
321         else:
322             curpage = self.index(pageIndex)
323         if curpage < len(self._pageNames) - 1:
324             self.selectpage(curpage + 1)
325
326     def setnaturalsize(self, pageNames = None):
327         self.update_idletasks()
328         maxPageWidth = 1
329         maxPageHeight = 1
330         if pageNames is None:
331             pageNames = self.pagenames()
332         for pageName in pageNames:
333             pageInfo = self._pageAttrs[pageName]
334             page = pageInfo['page']
335             w = page.winfo_reqwidth()
336             h = page.winfo_reqheight()
337             if maxPageWidth < w:
338                 maxPageWidth = w
339             if maxPageHeight < h:
340                 maxPageHeight = h
341         pageBorder = self._borderWidth + self._pageMargin
342         width = maxPageWidth + pageBorder * 2
343         height = maxPageHeight + pageBorder * 2
344
345         if self._withTabs:
346             maxTabHeight = 0
347             for pageInfo in self._pageAttrs.values():
348                 if maxTabHeight < pageInfo['tabreqheight']:
349                     maxTabHeight = pageInfo['tabreqheight']
350             height = height + maxTabHeight + self._borderWidth * 1.5
351
352         # Note that, since the hull is a canvas, the width and height
353         # options specify the geometry *inside* the borderwidth and
354         # highlightthickness.
355         self.configure(hull_width = width, hull_height = height)
356
357     def recolorborders(self):
358         self._pending['borderColor'] = 1
359         self._layout()
360
361     def _handleMap(self, event):
362         self._layout()
363
364     def _handleConfigure(self, event):
365         self._canvasSize = (event.width, event.height)
366         self._pending['size'] = 1
367         self._layout()
368
369     def _raiseNewTop(self, pageName):
370         if not self._pageAttrs[pageName]['created']:
371             self._pageAttrs[pageName]['created'] = 1
372             cmd = self['createcommand']
373             if cmd is not None:
374                 cmd(pageName)
375         cmd = self['raisecommand']
376         if cmd is not None:
377             cmd(pageName)
378
379     # This is the vertical layout of the notebook, from top (assuming
380     # tabpos is 'n'):
381     #     hull highlightthickness (top)
382     #     hull borderwidth (top)
383     #     borderwidth (top border of tabs)
384     #     borderwidth * 0.5 (space for bevel)
385     #     tab button (maximum of requested height of all tab buttons)
386     #     borderwidth (border between tabs and page)
387     #     pagemargin (top)
388     #     the page itself
389     #     pagemargin (bottom)
390     #     borderwidth (border below page)
391     #     hull borderwidth (bottom)
392     #     hull highlightthickness (bottom)
393     #
394     # canvasBorder is sum of top two elements.
395     # tabBottom is sum of top five elements.
396     #
397     # Horizontal layout (and also vertical layout when tabpos is None):
398     #     hull highlightthickness
399     #     hull borderwidth
400     #     borderwidth
401     #     pagemargin
402     #     the page itself
403     #     pagemargin
404     #     borderwidth
405     #     hull borderwidth
406     #     hull highlightthickness
407     #
408     def _layout(self):
409         if not self.winfo_ismapped() or self._canvasSize is None:
410             # Don't layout if the window is not displayed, or we
411             # haven't yet received a <Configure> event.
412             return
413
414         hullWidth, hullHeight = self._canvasSize
415         borderWidth = self._borderWidth
416         canvasBorder = string.atoi(self._hull['borderwidth']) + \
417             string.atoi(self._hull['highlightthickness'])
418         if not self._withTabs:
419             self.tabBottom = canvasBorder
420         oldTabBottom = self.tabBottom
421
422         if self._pending.has_key('borderColor'):
423             self._lightBorderColor, self._darkBorderColor = \
424                     Pmw.Color.bordercolors(self, self['hull_background'])
425
426         # Draw all the tabs.
427         if self._withTabs and (self._pending.has_key('tabs') or
428                 self._pending.has_key('size')):
429             # Find total requested width and maximum requested height
430             # of tabs.
431             sumTabReqWidth = 0
432             maxTabHeight = 0
433             for pageInfo in self._pageAttrs.values():
434                 sumTabReqWidth = sumTabReqWidth + pageInfo['tabreqwidth']
435                 if maxTabHeight < pageInfo['tabreqheight']:
436                     maxTabHeight = pageInfo['tabreqheight']
437             if maxTabHeight != 0:
438                 # Add the top tab border plus a bit for the angled corners
439                 self.tabBottom = canvasBorder + maxTabHeight + borderWidth * 1.5
440
441             # Prepare for drawing the border around each tab button.
442             tabTop = canvasBorder
443             tabTop2 = tabTop + borderWidth
444             tabTop3 = tabTop + borderWidth * 1.5
445             tabBottom2 = self.tabBottom
446             tabBottom = self.tabBottom + borderWidth
447
448             numTabs = len(self._pageNames)
449             availableWidth = hullWidth - 2 * canvasBorder - \
450                 numTabs * 2 * borderWidth
451             x = canvasBorder
452             cumTabReqWidth = 0
453             cumTabWidth = 0
454
455             # Position all the tabs.
456             for pageName in self._pageNames:
457                 pageInfo = self._pageAttrs[pageName]
458                 (windowitem, lightshadow, darkshadow) = pageInfo['tabitems']
459                 if sumTabReqWidth <= availableWidth:
460                     tabwidth = pageInfo['tabreqwidth']
461                 else:
462                     # This ugly calculation ensures that, when the
463                     # notebook is not wide enough for the requested
464                     # widths of the tabs, the total width given to
465                     # the tabs exactly equals the available width,
466                     # without rounding errors.
467                     cumTabReqWidth = cumTabReqWidth + pageInfo['tabreqwidth']
468                     tmp = (2*cumTabReqWidth*availableWidth + sumTabReqWidth) \
469                             / (2 * sumTabReqWidth)
470                     tabwidth = tmp - cumTabWidth
471                     cumTabWidth = tmp
472
473                 # Position the tab's button canvas item.
474                 self.coords(windowitem, x + borderWidth, tabTop3)
475                 self.itemconfigure(windowitem,
476                     width = tabwidth, height = maxTabHeight)
477
478                 # Make a beautiful border around the tab.
479                 left = x
480                 left2 = left + borderWidth
481                 left3 = left + borderWidth * 1.5
482                 right = left + tabwidth + 2 * borderWidth
483                 right2 = left + tabwidth + borderWidth
484                 right3 = left + tabwidth + borderWidth * 0.5
485
486                 self.coords(lightshadow, 
487                     left, tabBottom2, left, tabTop2, left2, tabTop,
488                     right2, tabTop, right3, tabTop2, left3, tabTop2,
489                     left2, tabTop3, left2, tabBottom,
490                     )
491                 self.coords(darkshadow, 
492                     right2, tabTop, right, tabTop2, right, tabBottom2,
493                     right2, tabBottom, right2, tabTop3, right3, tabTop2,
494                     )
495                 pageInfo['left'] = left
496                 pageInfo['right'] = right
497
498                 x = x + tabwidth + 2 * borderWidth
499
500         # Redraw shadow under tabs so that it appears that tab for old
501         # top page is lowered and that tab for new top page is raised.
502         if self._withTabs and (self._pending.has_key('topPage') or
503                 self._pending.has_key('tabs') or self._pending.has_key('size')):
504
505             if self.getcurselection() is None:
506                 # No pages, so draw line across top of page area.
507                 self.coords(self._pageTop1Border,
508                     canvasBorder, self.tabBottom,
509                     hullWidth - canvasBorder, self.tabBottom,
510                     hullWidth - canvasBorder - borderWidth,
511                         self.tabBottom + borderWidth,
512                     borderWidth + canvasBorder, self.tabBottom + borderWidth,
513                     )
514
515                 # Ignore second top border.
516                 self.coords(self._pageTop2Border, 0, 0, 0, 0, 0, 0)
517             else:
518                 # Draw two lines, one on each side of the tab for the
519                 # top page, so that the tab appears to be raised.
520                 pageInfo = self._pageAttrs[self.getcurselection()]
521                 left = pageInfo['left']
522                 right = pageInfo['right']
523                 self.coords(self._pageTop1Border,
524                     canvasBorder, self.tabBottom,
525                     left, self.tabBottom,
526                     left + borderWidth, self.tabBottom + borderWidth,
527                     canvasBorder + borderWidth, self.tabBottom + borderWidth,
528                     )
529
530                 self.coords(self._pageTop2Border,
531                     right, self.tabBottom,
532                     hullWidth - canvasBorder, self.tabBottom,
533                     hullWidth - canvasBorder - borderWidth,
534                         self.tabBottom + borderWidth,
535                     right - borderWidth, self.tabBottom + borderWidth,
536                     )
537
538             # Prevent bottom of dark border of tabs appearing over
539             # page top border.
540             self.tag_raise(self._pageTop1Border)
541             self.tag_raise(self._pageTop2Border)
542
543         # Position the page border shadows.
544         if self._pending.has_key('size') or oldTabBottom != self.tabBottom:
545
546             self.coords(self._pageLeftBorder,
547                 canvasBorder, self.tabBottom,
548                 borderWidth + canvasBorder,
549                     self.tabBottom + borderWidth,
550                 borderWidth + canvasBorder,
551                     hullHeight - canvasBorder - borderWidth,
552                 canvasBorder, hullHeight - canvasBorder,
553                 )
554
555             self.coords(self._pageBottomRightBorder,
556                 hullWidth - canvasBorder, self.tabBottom,
557                 hullWidth - canvasBorder, hullHeight - canvasBorder,
558                 canvasBorder, hullHeight - canvasBorder,
559                 borderWidth + canvasBorder,
560                     hullHeight - canvasBorder - borderWidth,
561                 hullWidth - canvasBorder - borderWidth,
562                     hullHeight - canvasBorder - borderWidth,
563                 hullWidth - canvasBorder - borderWidth,
564                     self.tabBottom + borderWidth,
565                 )
566
567             if not self._withTabs:
568                 self.coords(self._pageTopBorder,
569                     canvasBorder, self.tabBottom,
570                     hullWidth - canvasBorder, self.tabBottom,
571                     hullWidth - canvasBorder - borderWidth,
572                         self.tabBottom + borderWidth,
573                     borderWidth + canvasBorder, self.tabBottom + borderWidth,
574                     )
575
576         # Color borders.
577         if self._pending.has_key('borderColor'):
578             self.itemconfigure('lighttag', fill = self._lightBorderColor)
579             self.itemconfigure('darktag', fill = self._darkBorderColor)
580
581         newTopPage = self._pending.get('topPage')
582         pageBorder = borderWidth + self._pageMargin
583
584         # Raise new top page.
585         if newTopPage is not None:
586             self._topPageName = newTopPage
587             self._topPageItem = self.create_window(
588                 pageBorder + canvasBorder, self.tabBottom + pageBorder,
589                 window = self._pageAttrs[newTopPage]['page'],
590                 anchor = 'nw',
591                 )
592
593         # Change position of top page if tab height has changed.
594         if self._topPageName is not None and oldTabBottom != self.tabBottom:
595             self.coords(self._topPageItem,
596                     pageBorder + canvasBorder, self.tabBottom + pageBorder)
597
598         # Change size of top page if,
599         #   1) there is a new top page.
600         #   2) canvas size has changed, but not if there is no top
601         #      page (eg:  initially or when all pages deleted).
602         #   3) tab height has changed, due to difference in the height of a tab
603         if (newTopPage is not None or \
604                 self._pending.has_key('size') and self._topPageName is not None
605                 or oldTabBottom != self.tabBottom):
606             self.itemconfigure(self._topPageItem,
607                 width = hullWidth - 2 * canvasBorder - pageBorder * 2,
608                 height = hullHeight - 2 * canvasBorder - pageBorder * 2 -
609                     (self.tabBottom - canvasBorder),
610                 )
611
612         self._pending = {}
613
614 # Need to do forwarding to get the pack, grid, etc methods. 
615 # Unfortunately this means that all the other canvas methods are also
616 # forwarded.
617 Pmw.forwardmethods(NoteBook, Tkinter.Canvas, '_hull')