1from tkinter import *
2import tkinter.filedialog
3import tkinter.messagebox
4
5from guppy.etc.Descriptor import property_nondata
6
7
8class MyVar(StringVar):
9    _default = 0.0
10
11    def set(self, value):
12        StringVar.set(self, '%.2g' % value)
13
14
15suffixes = ('', 'K', 'M', 'G', 'T')
16
17
18def sizestring(value):
19    value = float(value)
20    sign = 1
21    if value < 0:
22        sign = -1
23        value = - value
24    i = 0
25    while value > 99999:
26        value /= 1000
27        i += 1
28    s = str(int(round(value)))+suffixes[i]
29    if s.endswith('000'+suffixes[i]):
30        s = str(int(round(value/1000)))+suffixes[i+1]
31    if sign == -1:
32        s = '-' + s
33    return s
34
35
36def percentstring(value):
37    a = abs(value)
38    if 10 <= a <= 9999:
39        return '%d' % round(value)
40    elif 0.01 <= a <= 10:
41        return '%.2g' % value
42    elif a <= 1e-10:
43        return '0'
44    else:
45        return '%.0e' % value
46
47
48def stringsize(s):
49    if s.isdigit():
50        return int(s)
51    suf = s[-1:].upper()
52    mult = 1000
53    for su in suffixes[1:]:
54        if su == suf:
55            break
56        mult *= 1000
57    else:
58        raise ValueError
59    return int(s[:-1])*mult
60
61
62class Menu(Menu):
63    # A fix for the .delete() method in Menu.
64    # To delete commands defined in the menu items deleted.
65    # Also changed the comment: INDEX2 is actually INCLUDED.
66    def delete(self, index1, index2=None):
67        """Delete menu items between INDEX1 and INDEX2 (included)."""
68        if index2 is None:
69            index2 = index1
70        # First find out what entries have defined commands.
71        cmds = []
72        for i in range(self.index(index1), self.index(index2)+1):
73            c = str(self.entrycget(i, 'command'))
74            if c in self._tclCommands:
75                # I don't want to delete the command already, since it
76                # seems mystical to do that while the entry is not yet deleted.
77                cmds.append(c)
78        # Delete the menu entries.
79        self.tk.call(self._w, 'delete', index1, index2)
80        # Now that the menu entries have been deleted,
81        # we can delete their commands.
82        for c in cmds:
83            self.deletecommand(c)
84
85
86class SizeVar(StringVar):
87    _default = 0.0
88
89    def set(self, value):
90        self._value = value
91        s = sizestring(value)
92        StringVar.set(self, s)
93
94
95class ValueLabel(Label):
96    def __init__(self, *args, **kwds):
97        kwds['width'] = 10
98        Label.__init__(self, *args, **kwds)
99
100
101class ClickButton(Button):
102    # Button that runs the command directly at the click, not at release.
103    # And has auto-repeat.
104    def __init__(self, master, command, firstdelay=500, thendelay=150, **kwds):
105        Button.__init__(self, master, **kwds)
106        self._command = command
107        self._firstdelay = firstdelay
108        self._thendelay = thendelay
109        self.bind('<Button-1>', self._event_button)
110        self.bind('<ButtonRelease-1>', self._event_release)
111
112    def _event_button(self, event=None):
113        self._command()
114        if event is not None:
115            delay = self._firstdelay
116        else:
117            delay = self._thendelay
118        self._after = self.after(delay, self._event_button)
119
120    def _event_release(self, event):
121        self.after_cancel(self._after)
122        del self._after
123
124
125class Stats:
126    def __init__(self, mod, fn=None):
127        self.mod = mod
128        self.os = mod.os
129        self.hashlib = mod.hashlib
130        self.fn = fn
131
132    def clear_cache(self):
133        # It is intended to be transparently
134        # automagically reopened when needed.
135        self.stats = None
136        del self.stats
137
138    def get_stats(self):
139        self.open(self.fn)
140        return self.stats
141
142    stats = property_nondata(get_stats)
143
144    def collect(self):
145        if not self.fn:
146            return 0, 0
147
148        stat = self.os.stat(self.fn)
149        if stat == self.laststat:
150            return len(self), 0
151
152        with open(self.fn) as f:
153            str = f.read(self.lastfilesize)
154            md5 = self.hashlib.md5(str.encode('utf-8'))
155            digest = md5.digest()
156            if digest == self.lastdigest:
157                numoldstats = len(self)
158            else:
159                self.loadstr(str, reset=1)
160                numoldstats = 0
161
162            str = f.read()
163
164            self.laststat = self.os.fstat(f.fileno())
165        self.lastfilesize = self.laststat.st_size
166
167        md5.update(str.encode('utf-8'))
168        self.lastdigest = md5.digest()
169        self.loadstr(str)
170
171        numnewstats = len(self.stats)-numoldstats
172
173        return numoldstats, numnewstats
174
175    def open(self, fn):
176        if not fn:
177            self.len_stats = 0
178            self.stats = []
179            self.max_size = 0
180            self.fn = fn
181            return
182
183        with open(fn) as f:
184            str = f.read()
185            lastdigest = self.hashlib.md5(str.encode('utf-8')).digest()
186            laststat = self.os.fstat(f.fileno())
187        self.loadstr(str, reset=1)
188
189        # Update these only if there was no exception so far.
190        self.fn = fn
191        self.lastdigest = lastdigest
192        self.laststat = laststat
193        self.lastfilesize = laststat.st_size
194
195    def loadstr(self, str, reset=0):
196        stats = []
197        lines = str.split('\n')
198        del str
199        linesiter = iter(lines)
200        max_size = 0
201        while 1:
202            try:
203                st = self.mod.Use.load(linesiter)
204            except StopIteration:
205                break
206            stats.append(st)
207            if st.size > max_size:
208                max_size = st.size
209
210        # Only update self if there were no exception so far
211
212        if reset:
213            self.stats = []
214            self.max_size = 0
215
216        self.max_size = max(self.max_size, max_size)
217        self.stats.extend(stats)
218        self.len_stats = len(self.stats)
219
220    def __getitem__(self, idx):
221        return self.stats[idx]
222
223    def __len__(self):
224        try:
225            return self.len_stats
226        except AttributeError:
227            self.len_stats = len(self.stats)
228            return self.len_stats
229
230    def get_max_size(self):
231        return self.max_size
232
233
234class ProfileRow:
235    kindwidth = 30
236
237    def __init__(self, master, row, usecolor=1):
238        self.master = master
239        self.row = row
240        if usecolor:
241            colbg = Frame(master=master, bg='black', width=1,
242                          borderwidth=1, relief=GROOVE)
243            self.color = Label(master=colbg, bg='white',
244                               width=1, borderwidth=1, relief=GROOVE)
245            self.color.grid(row=0, column=0)
246            colbg.grid(row=row, column=0, sticky=NW)
247
248        self.rsizevar = SizeVar()
249        self.rsize = Label(
250            master=master, textvariable=self.rsizevar, width=6, anchor=E)
251        self.rpercentvar = StringVar()  # BBIntVar()
252        self.rpercent = Label(
253            master=master, textvariable=self.rpercentvar, width=3, anchor=E)
254        self.dsizevar = SizeVar()
255        self.dsize = Label(
256            master=master, textvariable=self.dsizevar, width=6, anchor=E)
257        self.dpercentvar = StringVar()  # BBIntVar()
258        self.dpercent = Label(
259            master=master, textvariable=self.dpercentvar, width=3, anchor=E)
260        self.kindvar = StringVar()
261        self.kind = Label(master=master, textvariable=self.kindvar, anchor=NW,
262                          width=self.kindwidth, justify=LEFT)
263
264        self.rsize.grid(row=row, column=1, sticky=NE)
265        self.rpercent.grid(row=row, column=2, sticky=NE)
266        self.dsize.grid(row=row, column=3, sticky=NE)
267        self.dpercent.grid(row=row, column=4, sticky=NE)
268        self.kind.grid(row=row, column=5, sticky=NW)
269
270    def set_color_size_percent_kind(self, color, rsize, rpercent, dsize, dpercent, kind):
271        self.set_color(color)
272        if color is not None:
273            self.set_color(color)
274        self.rsizevar.set(rsize)
275        if rpercent is None:
276            rpercent = ''
277        else:
278            rpercent = str(int(round(rpercent)))
279        self.rpercentvar.set(rpercent)
280
281        self.dsizevar.set(dsize)
282        dpercent = str(int(round(dpercent)))
283        self.dpercentvar.set(dpercent)
284
285        self.set_kind(kind)
286
287    def set_color(self, color):
288        self.color.configure(bg=color)
289
290    def set_kind(self, kind):
291        self.kindtext = kind
292
293        if len(kind) > self.kindwidth:
294            import textwrap
295            kind = textwrap.fill(kind, width=self.kindwidth)
296        self.kindvar.set(kind)
297
298    def clear(self):
299        self.set_color_size_percent_kind(self.master['bg'], 0, 0, 0, 0, '--')
300
301
302class AxisControl:
303    scale_table = [1, 2, 5]
304    while scale_table[-1] < 1e12:
305        scale_table.append(scale_table[-3] * 10)
306
307    def __init__(self, master,
308                 name,
309                 range,
310                 grid,
311                 unit,
312                 rangecommand,
313                 gridcommand,
314                 autocommand=None
315                 ):
316        small = 0
317
318        self.name = name
319        self.unit = unit
320        self.range = range
321        self.rangecommand = rangecommand
322
323        self.frame = frame = Frame(master, borderwidth=2, relief=GROOVE)
324
325        self.rangevar = SizeVar()
326        self.rangevar.set(range)
327        rangeval = Entry(master=self.frame,
328                         # anchor=E,
329                         width=4,
330                         textvar=self.rangevar,
331                         #font=('fixed', '16', 'bold'),
332                         #font=('terminal', '16', 'bold'),
333                         #font=('terminal', '14'),
334                         font=('fixed', '14'),
335                         # bg='black',fg='yellow'
336                         bg='#fdd'
337                         )
338        rangeval.bind('<KeyPress-Return>', self.event_range_enter)
339
340        namelabel = Menubutton(frame, text=name, relief='raised', anchor=W)
341
342        namemenu = Menu(namelabel)
343        namelabel['menu'] = namemenu
344
345        if autocommand:
346            self.autovar = BooleanVar()
347            self.autovar.set(True)
348            namemenu.add_checkbutton(
349                # autobutton = Checkbutton(frame,
350                label='Auto',
351                variable=self.autovar,
352                command=autocommand,
353                # relief=RAISED
354            )
355            autobutton = Checkbutton(frame,
356                                     text='Auto',
357                                     variable=self.autovar,
358                                     command=autocommand,
359                                     relief=RAISED
360                                     )
361
362        else:
363            self.autovar = None
364
365        if gridcommand:
366            self.gridvar = BooleanVar()
367            self.gridvar.set(grid)
368            namemenu.add_checkbutton(
369                label='Grid',
370                variable=self.gridvar,
371                command=lambda: gridcommand(self.gridvar.get()),
372            )
373
374            gridbutton = Checkbutton(frame,
375                                     text='Grid',
376                                     variable=self.gridvar,
377                                     command=lambda: gridcommand(
378                                         self.gridvar.get()),
379                                     relief=RAISED
380                                     )
381
382        rangelabel = Label(frame, text='Range')
383
384        if name == 'Y' and small:
385            padx = 5
386            pady = 0
387        else:
388            padx = 3
389            pady = 3
390        ud = Frame(frame)
391        rangeup = ClickButton(ud, text='+',
392                              pady=pady, padx=padx,
393                              font=('fixed', 8),
394                              command=lambda: self.range_button(1))
395
396        rangedown = ClickButton(ud, text='-',
397                                pady=pady, padx=padx,
398                                font=('fixed', 8),
399                                command=lambda: self.range_button(-1))
400
401        rangedown.grid(row=0, column=0)
402        rangeup.grid(row=0, column=1)
403        row = 0
404
405        if small and name == 'Y':
406            namelabel.grid(row=0, rowspan=1, column=0)
407            rangeup.grid(row=0, column=1, sticky=W)
408
409            autobutton.grid(row=1, column=0)
410
411            rangedown.grid(row=1, column=1, sticky=W)
412            rangeval.grid(row=2, column=0, columnspan=2,
413                          sticky=W, padx=3, pady=3)
414
415        elif small and name == 'X':
416            namelabel.grid(row=0, column=0)
417            rangeval.grid(row=0, column=1, sticky=W, padx=3, pady=3)
418            rangedown.grid(row=0, column=2, sticky=W)
419            rangeup.grid(row=0, column=3, sticky=W)
420
421        else:
422            namelabel.grid(row=row, column=0, sticky=N+W,
423                           ipadx=0, ipady=0, padx=2, pady=2)
424            rangelabel.grid(row=row, column=1, sticky=W)
425            ud.grid(row=row, column=2, padx=2)
426            row += 1
427
428            if gridcommand:
429                gridbutton.grid(row=row, column=0, sticky=W)
430
431            rangeval.grid(row=row, column=1, padx=3, pady=3)
432            if autocommand:
433                pass
434                autobutton.grid(row=row, column=2)
435
436    def cmd_range(self):
437        pass
438
439    def event_range_enter(self, event):
440        str = self.rangevar.get()
441        try:
442            rng = stringsize(str)
443            if rng not in self.scale_table:
444                if not 1 <= rng <= self.scale_table[-1]:
445                    raise ValueError
446        except ValueError:
447            self.frame.bell()
448            self.errorbox("""\
449Invalid range entry.
450It should be a positive integer with an optional multiplier:
451K, M, G, or T
452(1000, 1e6, 1e9, 1e12)
453Maximum range is 1T.""")
454
455            self.rangevar.set(self.range)
456        else:
457            if self.autovar:
458                self.autovar.set(False)
459            self.setrange(rng)
460
461    def auto_command(self):
462        pass
463
464    def errorbox(self, msg):
465        tkinter.messagebox.showerror(master=self.frame, message=msg)
466
467    def fit(self, range):
468        range = self.scale_by_table(range)
469        self.setrange(range)
470
471    def range_button(self, d):
472        if self.autovar:
473            self.autovar.set(False)
474        self.range_change(d)
475
476    def range_change(self, d):
477        range = self.range
478        srange = self.scale_by_table(range)
479        if srange > range:
480            if d > 0:
481                d -= 1
482        i = self.scale_table.index(srange)
483        i += d
484        if i >= len(self.scale_table):
485            i = len(self.scale_table) - 1
486        if i < 0:
487            i = 0
488
489        self.setrange(self.scale_table[i])
490
491    def setrange(self, range):
492        if range != self.range:
493            self.range = range
494            self.rangevar.set(range)
495            self.rangecommand(range)
496
497    def scale_by_table(self, s):
498        # Return the scale from table that is higher or equal to s
499        for ts in self.scale_table:
500            if ts >= s:
501                return ts
502        return self.scale_table[-1]
503
504
505WM = 1
506
507
508class Marker:
509    def __init__(self, d, tag, name, pos, poscommand=None):
510        self.d = d
511        self.tag = tag
512        self.name = name
513        self.xmarker = pos
514        self.butdown = 0
515        self.ocursor = d.ocursor
516        self.cursor = self.ocursor
517        self.poscommand = None
518        self.intpos = None
519        self.moving = 0
520        self.selected = 0
521        self.entered = 0
522        self.butdownselected = 0
523        self.motion_id = None
524        self.create()
525
526    def bind(self, sequence, function):
527        tag = self.tag
528        self.d.drawingarea.tag_bind(tag, sequence, function)
529        if WM:
530            self.xlabel.bind(sequence, function)
531        else:
532            self.d.xmarks.tag_bind(tag, sequence, function)
533
534    def coords(self, canx):
535        self.d.drawingarea.coords(self.tag,
536                                  canx, 0,
537                                  canx, -int(self.d.boty))
538
539        self.d.xmarks.coords(self.tag, canx, 10)
540
541    def create(self):
542        tag = self.tag
543        text = self.name
544        pos = 0
545
546        self.d.drawingarea.create_line(pos, 0, pos, 20-self.d.boty, stipple='gray12',
547                                       width=4, tags=(tag,))
548        if WM:
549            label = self.xlabel = Label(
550                self.d.xmarks, text=text, padx=2, pady=2, relief=RAISED)
551            self.d.xmarks.create_window(pos, 0, window=label, tags=(tag,))
552        else:
553            self.d.xmarks.create_text(pos, 0, text=text, tags=(tag,))
554
555        self.bind('<Button-1>', self.event_button_1)
556        self.bind('<ButtonRelease-1>', self.event_button_1_release)
557        self.bind('<Enter>', self.event_enter)
558        self.bind('<Leave>', self.event_leave)
559
560        self.d.drawingarea.bind('<Enter>', self.event_enter_movearea, add='+')
561        self.d.drawingarea.bind(
562            '<Button-1>', self.event_button_1_movearea, add='+')
563
564    def event_button_1(self, event):
565        self.butdown = 1
566        if self.selected:
567            self.butdownselected = 1
568            if self.moving:
569                self.event_stop_move(event)
570        else:
571            self.butdownselected = 0
572
573        self.has_moved = 0
574        self.event_selected(event)
575        self.event_start_move(event)
576
577    def event_button_1_movearea(self, event):
578        if not self.entered:
579            self.event_deselected(event)
580
581    def event_button_1_release(self, event):
582        self.butdown = 0
583        if self.has_moved == self.butdownselected:
584            if self.selected:
585                if self.moving and not (self.disloy <= event.y_root < self.dishiy):
586                    self.event_stop_move(None)
587                    self.setcursor(self.ocursor)
588            else:
589                self.setcursor(self.ocursor)
590            return
591        self.event_deselected(event)
592
593    def event_deselected(self, event):
594        if self.selected:
595            self.selected = 0
596            self.xlabel['relief'] = RAISED
597            if self.moving:
598                self.event_stop_move(event)
599
600    def event_enter(self, event):
601        self.entered = 1
602        if not self.moving:
603            if self.selected:
604                self.event_start_move(event)
605            else:
606                self.setcursor('hand2')
607
608    def event_enter_movearea(self, event):
609        if self.selected and not self.moving:
610            self.event_start_move(event)
611
612    def event_leave(self, event):
613        self.entered = 0
614        if not self.moving:
615            self.setcursor(self.ocursor)
616        elif not (self.fraloy <= event.y_root < self.frahiy):
617            pass
618
619    def event_motion(self, event):
620        self.has_moved = 1
621
622        inside = (self.fraloy <= event.y_root < self.frahiy)
623
624        if inside != self.inside:
625            self.inside = inside
626            if not inside:
627                self.out_event = event
628                self.event_stop_move(None)
629                if self.butdown:
630                    self.setcursor('circle')
631                    self.d.bind_motion(self.event_motion_downout)
632            else:
633                self.in_event = event
634                #self.delta += self.out_event.x_root - event.x_root
635                self.event_start_move(event)
636                return
637
638        if inside:
639            self.moved(event)
640            self.setxvars()
641
642    def event_motion_downout(self, event):
643        # We don't get an enter while button is pressed down
644        # Emulate an enter if we detect entering
645        inside = (self.fraloy <= event.y_root < self.frahiy)
646        if inside:
647            self.d.unbind_motion(self.event_motion_downout)
648            self.event_enter_movearea(event)
649
650    def event_selected(self, event):
651        for m in self.d.marks:
652            m.event_deselected(event)
653        self.selected = 1
654        self.xlabel['relief'] = SUNKEN
655
656    def event_start_move(self, event):
657        self.moving = 1
658        self.fralox = self.d.frame.winfo_rootx()
659        self.frahix = self.fralox + self.d.frame.winfo_width()
660
661        self.fraloy = self.d.frame.winfo_rooty()
662        self.frahiy = self.fraloy + self.d.frame.winfo_height()
663
664        self.dislox = self.d.drawingarea.winfo_rootx()
665        self.dishix = self.dislox + self.d.drawingarea.winfo_width()
666        self.disloy = self.d.drawingarea.winfo_rooty()
667        self.dishiy = self.disloy + self.d.drawingarea.winfo_height()
668
669        self.down_event = event
670        self.prev_event = event
671        self.down_xmarker = self.xmarker
672        self.down_xvfrac = self.d.drawingarea.xview()[0]
673        self.inside = 1
674        self.delta = 0
675        self.lift()
676
677        self.motion_id = self.d.bind_motion(self.event_motion)
678        self.moved(event)
679
680    def event_stop_move(self, event):
681        assert self.moving
682        self.moving = 0
683
684        self.d.unbind_motion(self.motion_id)
685        if event is not None:
686            self.moved(event)
687            self.setxvars()
688
689        if self.entered and not self.selected:
690            self.setcursor('hand2')
691        else:
692            self.setcursor(self.ocursor)
693
694    def lift(self):
695        self.d.xmarks.tag_raise(self.tag)
696        if WM:
697            self.xlabel.lift()
698        self.d.drawingarea.tag_raise(self.tag)
699
700    def move(self, sample):
701        canx = self.d.canxscaled(sample)
702        self.d.xview_pos(canx)
703        self.coords(canx)
704        self.xmarker = sample
705        self.lift()
706
707    def moved(self, event):
708        curx = event.x_root
709        cury = event.y_root
710        prevx = self.prev_event.x_root
711
712        if prevx > self.dishix and curx < self.dishix:
713            prevx = self.dishix
714        elif prevx < self.dislox and curx > self.dislox:
715            prevx = self.dislox
716
717        markx = self.d.canxscaled(self.xmarker) - \
718            self.d.drawingarea.canvasx(0) + self.dislox
719
720        dx = curx - prevx
721        l = r = 1
722
723        if self.xmarker >= self.d.numstats-1:
724            r = 0
725
726        if self.xmarker <= 0:
727            l = 0
728
729        stop = 0
730
731        # Should we allow to move it back or not
732        # if it is at an endpoint?
733        # Here we don't move it at all, to make marker pos correspond
734        # more closely with mouse position.
735
736        if ((r == 0 and curx > markx) or (l == 0 and curx < markx)):
737            l = r = 0
738
739        if self.butdown:
740            if curx > self.dishix:
741                l = 0
742            elif curx < self.dislox:
743                r = 0
744        else:
745            if not (self.dislox <= curx < self.dishix and
746                    self.disloy <= cury < self.dishiy):
747                l = r = 0
748                stop = 1
749
750        if l and r:
751            self.setcursor('sb_h_double_arrow')
752        elif l:
753            self.setcursor('sb_left_arrow')
754            if dx > 0:
755                dx = 0
756        elif r:
757            self.setcursor('sb_right_arrow')
758            if dx < 0:
759                dx = 0
760        else:
761            self.setcursor('dot')
762            dx = 0
763
764        self.prev_event = event
765
766        sample = self.d.limitx(self.xmarker + dx / self.d.xscale)
767        canx = self.d.canxscaled(sample)
768        self.d.xview_pos(canx)
769        self.coords(canx)
770        self.xmarker = sample
771        if stop and self.moving:
772            self.event_stop_move(None)
773
774    def set(self):
775        canx = self.d.canxscaled(self.xmarker)
776        self.coords(canx)
777        self.lift()
778
779    def set_poscommand(self, command):
780        self.poscommand = command
781        self.intpos = None
782
783    def setcursor(self, cursor):
784        if cursor != self.cursor:
785            self.xlabel['cursor'] = cursor
786            self.cursor = cursor
787        self.d.setcursor(cursor)
788
789    def setxvars(self):
790        if self.poscommand:
791            intpos = int(round(self.xmarker))
792            if intpos != self.intpos:
793                self.intpos = intpos
794                self.poscommand(intpos)
795
796
797class Display:
798    orgwidth = 300
799    orgheight = 300
800    minwidth = 30
801    minheight = 30
802
803    def __init__(self, master,
804                 scale_table,
805                 numkindrows,
806                 getkindcolor,
807                 xrange=100,
808                 yrange=100,
809                 xgrid=False,
810                 ygrid=False,
811                 graphtype='Bars',
812                 statype='Size',
813                 ):
814        self.master = master
815        self.scale_table = scale_table
816        self.numkindrows = numkindrows
817        self.getkindcolor = getkindcolor
818        self.xrange = xrange
819        self.yrange = yrange
820        self.xgrid = xgrid
821        self.var_xgrid = BooleanVar(xgrid)
822        self.var_xgrid.set(xgrid)
823        self.var_ygrid = BooleanVar(xgrid)
824        self.ygrid = ygrid
825        self.var_ygrid.set(ygrid)
826        self.graphtype = graphtype
827        self.statype = statype
828
829        self.numstats = 0
830        self.ymaxs = []
831        self.ymins = []
832        self.ymax = 1
833
834        # To get around problems with dynamic unbinding / unbinding of motion,
835        # I handle it myself. in the bind_motion method using the following.
836        self.bound_motions = {}
837        self.event_motion_id = None
838        #
839
840        self.frame = frame = Frame(master,
841                                   borderwidth=3,
842                                   relief=SUNKEN,
843                                   # relief=GROOVE,
844                                   # background='green'
845                                   )
846        #self.frame = frame = Frame(master,background='green')
847
848        bordercolor = '#ccc'
849        screencolor = '#e0e0e0'
850        xscrollincrement = 1
851        frame = Frame(self.frame)
852        frame.grid(row=0, column=0)
853        #move = Frame(frame, height=10,width=10,background='red', relief=RAISED)
854        #move = Button(self.frame, height=10,width=10,background='red')
855        self.drawingarea = C = Canvas(frame,
856                                      width=self.orgwidth,
857                                      height=self.orgheight,
858                                      xscrollincrement=xscrollincrement,
859                                      # background='black',
860                                      background=screencolor,
861                                      bd=0,
862                                      xscrollcommand=self.xscrollbar_set,
863                                      # confine=False,
864                                      )
865
866        #self.yctrlframe = Frame(frame, borderwidth=2,relief=GROOVE)
867        self.yscrollbar = Scrollbar(frame, orient=VERTICAL, width=10)
868        # self.yscrollbar['command']=self.drawingarea.yview
869        #self.drawingarea['yscrollcommand'] = self.yscrollbar_set
870        # self.yscrollbar.pack(side=RIGHT,fill=Y)
871        #self.yctrlframe.grid(row = 0, column = 0,sticky=N+S,padx=3,pady=3)
872
873        self.xaxis = Canvas(frame,
874                            width=C['width'],
875                            height=20,
876                            xscrollincrement=xscrollincrement,
877                            bd=0,
878                            background=bordercolor,
879                            #xscrollcommand = self.xscrollbar_set
880                            # confine=False,
881                            )
882        self.xmarks = Canvas(frame,
883                             width=C['width'],
884                             height=20,
885                             xscrollincrement=xscrollincrement,
886                             bd=0,
887                             background=bordercolor,
888                             #xscrollcommand = self.xscrollbar_set
889                             # confine=False,
890                             )
891        self.yaxis = Canvas(frame, height=C['height'], width=50,
892                            bd=0,
893                            background=bordercolor,
894                            )
895
896        self.xscrollbar = Scrollbar(frame, orient=HORIZONTAL,
897                                    command=self.drawingarea_xview,
898                                    width=12,
899                                    background=bordercolor,
900
901                                    )
902
903        xy = Canvas(frame, width=50, height=20, bd=0,
904                    background=bordercolor,
905                    )
906
907        var_yrange = SizeVar()
908        self.var_yrange = var_yrange
909
910        row = 0
911
912        Label(frame,
913              textvar=var_yrange,
914              bd=0,
915              relief=FLAT,
916              background=bordercolor).grid(
917                  row=row,
918                  column=0,
919                  sticky=W+E+N+S)
920
921        self.xscrollbar.grid(row=row, column=1, sticky=E+W)
922
923        row += 1
924
925        self.yunit = Label(frame,
926                           text='Bytes',
927                           bd=0,
928                           relief=FLAT,
929                           background=bordercolor)
930        self.yunit.grid(
931            row=row,
932            column=0,
933            sticky=W+E+N+S)
934
935        self.xmarks.grid(row=row, column=1, sticky=W+E+N)
936
937        row += 1
938
939        self.yaxis.grid(row=row, column=0)
940        C.grid(row=row, column=1, sticky=W+E)
941
942        row += 1
943
944        xy.grid(row=row, column=0)
945        self.xaxis.grid(row=row, column=1, sticky=W+E+N)
946
947        self.botx = float(C['width'])
948        self.boty = float(C['height'])
949        self.chdim = self.getchdim()
950        self.canx0 = 0
951        self.tmax = 0
952        self.xscale = self.botx / self.xrange
953        self.yscale = self.boty / self.yrange
954        self.xi0 = None
955
956        xy.create_line(0, 2, 44, 2)
957        xy.create_line(49, 6, 49, 22)
958        xy.create_text(25, 14, text='Sample')
959
960        self.setscrollregion()
961
962        self.ocursor = self.drawingarea['cursor']
963        self.cursor = self.ocursor
964
965        self.marks = []
966
967    def bind_motion(self, function):
968        if self.event_motion_id == None:
969            self.event_motion_id = self.frame.bind_all(
970                '<Motion>', self.event_motion, add='+')
971        self.bound_motions[function] = self.bound_motions.get(function, 0) + 1
972        return function
973
974    def event_motion(self, event):
975        for f in list(self.bound_motions.keys()):
976            f(event)
977
978    def unbind_motion(self, funcid):
979        n = self.bound_motions[funcid] - 1
980        if n == 0:
981            del self.bound_motions[funcid]
982        else:
983            self.bound_motions[funcid] = n
984
985    def new_xmarker(self, name=None, pos=0):
986        tag = 'M%d' % len(self.marks)
987        if name is None:
988            name = tag
989        m = Marker(self, tag, name, pos)
990        self.marks.append(m)
991        return m
992
993    def canxscaled(self, x):
994        return x * self.xscale + self.canx0
995
996    def canyscaled(self, y):
997        return - y * self.yscale
998
999    def cmd_xgrid(self):
1000        self.xgrid = self.var_xgrid.get()
1001        self.drawxaxis()
1002
1003    def cmd_ygrid(self):
1004        self.ygrid = self.var_ygrid.get()
1005        self.drawyaxis()
1006
1007    def cmd_yrange_auto(self):
1008        self.ymax = None
1009        self.yrange_auto()
1010
1011    def limitx(self, x):
1012        lo = 0
1013        hi = max(0, self.numstats-1)
1014        if x < lo:
1015            return lo
1016        if x > hi:
1017            return hi
1018        return x
1019
1020    def resize(self, dx, dy):
1021        x = self.botx + dx
1022        y = self.boty + dy
1023        if x < self.minwidth:
1024            x = self.minwidth
1025            dx = x - self.botx
1026        if y < self.minheight:
1027            y = self.minheight
1028            dy = y - self.boty
1029
1030        xv = self.drawingarea.xview()
1031        yv = self.drawingarea.yview()
1032
1033        self.drawingarea.configure(width=x, height=y)
1034        self.xaxis.configure(width=x)
1035        self.xmarks.configure(width=x)
1036        self.yaxis.configure(height=y)
1037
1038        xscale = float(x) / self.xrange
1039        yscale = float(y) / self.yrange
1040
1041        xscaleorg = self.drawingarea.canvasx(0)
1042        yscaleorg = 0
1043
1044        xq = xscale / self.xscale
1045        yq = yscale / self.yscale
1046
1047        self.drawingarea.scale("all", xscaleorg, yscaleorg, xq, yq)
1048        #self.drawingarea.scale("barsep",xscaleorg, yscaleorg, xq, yq)
1049        #self.drawingarea.scale("xmarker",xscaleorg, yscaleorg, xq, yq)
1050
1051        self.canx0 = xscaleorg + (self.canx0 - xscaleorg) * xq
1052
1053        self.botx = x
1054        self.boty = y
1055        self.xscale = xscale
1056        self.yscale = yscale
1057
1058        self.drawxaxis()
1059        self.drawyaxis()
1060
1061        self.setscrollregion()
1062
1063        # If the size changed much, the canvas may scroll though it shouldn't.
1064        # Notes 11 and 26 Oct 2005 .
1065        # I save the current scroll position.
1066        # The caller has to call the .moveback() method some time later.
1067        self.wantedpos = xv[0]
1068
1069        return dx, dy
1070
1071    def moveback(self):
1072        self.frame.update_idletasks()
1073        self.xview(MOVETO, self.wantedpos)
1074
1075    def draw():
1076        self.drawxaxis()
1077        self.drawyaxis()
1078
1079    def draw_stat(self, idx, stat):
1080        graphtype = self.graphtype
1081        statype = self.statype
1082
1083        rows = stat.get_rows_n_and_other(self.numkindrows, statype)
1084        if statype == 'Size':
1085            kindval = dict([(r.name, r.size) for r in rows])
1086        else:
1087            kindval = dict([(r.name, r.count) for r in rows])
1088        order = [r.name for r in rows]
1089
1090        order.reverse()
1091
1092        lastkindval = self.lastkindval
1093        self.lastkindval = kindval
1094
1095        C = self.drawingarea
1096
1097        yscale = self.yscale
1098        xscale = self.xscale
1099
1100        x0 = idx * xscale - 0.5 * xscale + self.canx0
1101        x1 = x0 + xscale
1102        ymax = 0
1103        ymin = 0
1104
1105        y = 0
1106
1107        bw = 0.05*xscale
1108
1109        ocolor = None
1110        for k in order:
1111            dy = kindval.get(k, 0)
1112            if not dy:
1113                continue
1114            color = self.getkindcolor(k)
1115
1116            if graphtype == 'Bars':
1117                line = C.create_rectangle(x0+bw, -y*yscale,
1118                                          x1-bw, -(y+dy)*yscale,
1119                                          fill=color,
1120                                          outline=color,
1121                                          width=0,
1122                                          tags=("a",))
1123                if color == ocolor:
1124                    C.create_line(x0, -(y)*yscale,
1125                                  x1, -(y)*yscale,
1126                                  fill='black',
1127                                  tags=('barsep',))
1128                ocolor = color
1129                y += dy
1130            elif graphtype == 'Lines':
1131                if dy > ymax:
1132                    ymax = dy
1133                elif dy < ymin:
1134                    ymin = dy
1135                y0 = lastkindval.get(k)
1136                if y0 is None:
1137                    y0 = dy
1138                    x00 = x0
1139                else:
1140                    x00 = x0 - 0.4 * xscale
1141                    C.create_line(x00,  - y0 * yscale,
1142                                  x1 - 0.6 * xscale,  - dy * yscale,
1143                                  fill=color,
1144                                  tags=('a',))
1145
1146                C.create_line(x1 - 0.6 * xscale,  - dy * yscale,
1147                              x1 - 0.4 * xscale,  - dy * yscale,
1148                              fill=color,
1149                              width=4,
1150                              tags=('a',))
1151
1152        if graphtype == 'Bars':
1153            if y > ymax:
1154                ymax = y
1155            elif y < ymin:
1156                ymin = y
1157
1158        assert idx == len(self.ymaxs) == len(self.ymins)
1159        self.ymaxs.append(ymax)
1160        self.ymins.append(ymin)
1161
1162        if idx > self.tmax:
1163            self.tmax = idx
1164
1165    def drawingarea_xview(self, cmd, what, unit=None):
1166        if cmd == 'scroll' and unit == 'units':
1167            what = int(max(2, self.xscale)*int(what))
1168
1169        self.xview(cmd, what, unit)
1170
1171    def setcursor(self, cursor):
1172        if cursor != self.cursor:
1173            self.drawingarea['cursor'] = cursor
1174            self.master['cursor'] = cursor
1175            self.cursor = cursor
1176
1177    def xmarkers_set(self):
1178        for m in self.marks:
1179            m.set()
1180
1181    def xview(self, *args):
1182        if not args:
1183            return self.drawingarea.xview()
1184        self.drawingarea.xview(*args)
1185        self.xaxis.xview(*args)
1186        self.xmarks.xview(*args)
1187
1188    def xview_moveto(self, fraction):
1189        self.xview(MOVETO, fraction)
1190
1191    def xview_pos(self, pos, fraction=None, leftmargin=5, rightmargin=5):
1192        # Scroll canvas view, if necessary, so that something
1193        # (eg an x marker) at canvas position pos will be visible
1194        # with minimum specified margin at left and right.
1195        # Scroll relative to fraction; default is current xview position.
1196
1197        if fraction is None:
1198            fraction = self.xview()[0]
1199
1200        x1, y1, x2, y2 = self.scrollregion
1201
1202        cc = x1 + fraction * (x2 - x1)
1203
1204        xm = pos - cc
1205
1206        lo = leftmargin
1207        hi = self.botx - rightmargin
1208
1209        if xm < lo:
1210            dx = xm - lo
1211            xm = lo
1212        elif xm >= hi:
1213            dx = (xm - hi)
1214            xm = hi
1215        else:
1216            dx = 0
1217
1218        r = fraction + dx / float(x2 - x1)
1219        self.xview_moveto(r)
1220
1221    def drawxaxis(self):
1222        scale_table = self.scale_table
1223        self.xaxis.delete('all')
1224        self.drawingarea.delete('xgrid')
1225        x1, y1, x2, y2 = self.scrollregion
1226
1227        chdx, chdy = self.chdim
1228
1229        i = 0
1230        while (scale_table[i] * self.xscale <
1231               min(5, len(str(scale_table[i] * self.tmax))) * chdx):
1232            i += 1
1233        self.xstep = scale_table[i]
1234
1235        divisuf = (
1236            (1000000000000, '%dT'),
1237            (1000000000, '%dG'),
1238            (1000000,  '%dM'),
1239            (1000,  '%dK'),
1240            (1, '%d')
1241        )
1242
1243        for divi, form in divisuf:
1244            if self.xstep >= divi:
1245                break
1246
1247        self.xdivi = divi
1248        self.xform = form
1249
1250        self.xi0 = 0
1251        self.updatexaxis()
1252
1253    def updatexaxis(self):
1254
1255        chdx, chdy = self.chdim
1256        step = self.xstep
1257
1258        gridon = self.xgrid
1259
1260        for i in range(self.xi0, self.tmax+step, step):
1261            x = self.canx0 + i*self.xscale
1262            self.xaxis.create_line(x, 0, x, 4)
1263            if gridon:
1264                self.drawingarea.create_line(x, 0, x, -self.boty,
1265                                             tags=('xgrid',), width=2, stipple="gray25")
1266            text = self.xform % (i / self.xdivi)
1267            self.xaxis.create_text(x, chdy,  text=text)
1268
1269        self.xaxis.create_line(self.canx0 + self.xi0 *
1270                               self.xscale, 1, x+self.xscale, 1)
1271
1272        self.xi0 = i
1273        self.xmarkers_set()
1274
1275    def drawyaxis(self):
1276
1277        gridon = self.ygrid
1278
1279        self.yaxis.delete('all')
1280        self.drawingarea.delete('ygrid')
1281
1282        chdx, chdy = self.getchdim()
1283
1284        width = int(self.yaxis['width'])
1285        i = 0
1286        maxval = self.yrange
1287
1288        while (self.scale_table[i] * self.yscale < 1.5 * chdy):
1289            i += 1
1290        step = self.scale_table[i]
1291
1292        divisuf = (
1293            (1000000000000, '%4dT'),
1294            (1000000000, '%4dG'),
1295            (1000000,  '%4dM'),
1296            (1000,  '%4dK'),
1297            (1, '%5d')
1298        )
1299
1300        for divi, form in divisuf:
1301            if step >= divi:
1302                break
1303
1304        for i in range(0, maxval+step, step):
1305            y = - i*self.yscale
1306            self.yaxis.create_line(width-3, y, width-1, y)
1307            if gridon:
1308                self.drawingarea.create_line(self.scrollregion[0], y,
1309                                             self.scrollregion[2], y,
1310                                             stipple="gray25",
1311                                             tags=('ygrid',))
1312            text = form % (i / divi)
1313            self.yaxis.create_text(chdx*2.5, y-0.5*chdy,  text=text)
1314
1315        #self.yaxis.create_text(chdx*2.5, 0.5*chdy, text='bytes')
1316
1317        self.yaxis.create_line(width-1, 0, width-1, -self.boty)
1318        self.xmarkers_set()
1319
1320    def getchdim(self):
1321        ch = self.xaxis.create_text(0, 0, text='0')
1322        x1, y1, x2, y2 = self.xaxis.bbox(ch)
1323        self.xaxis.delete(ch)
1324        chdx = abs(x2 - x1)
1325        chdy = abs(y2 - y1)
1326
1327        return chdx, chdy
1328
1329    def load_stats(self, stats):
1330
1331        ocursor = self.frame.winfo_toplevel()['cursor']
1332        try:
1333            self.frame.winfo_toplevel()['cursor'] = 'watch'
1334            self.frame.update()
1335
1336            self.numstats = len(stats)
1337
1338            self.lastkindval = {}
1339            self.tmax = 0
1340            self.ymax = None
1341            self.ymaxs = []
1342            self.ymins = []
1343
1344            C = self.drawingarea
1345
1346            C.delete('barsep')
1347            C.delete('a')
1348
1349            for (i, st) in enumerate(stats):
1350                self.draw_stat(i, st)
1351
1352            try:
1353                self.drawingarea.tag_raise('barsep', 'a')
1354            except TclError:
1355                pass  # May be 'tagOrId "a" doesn't match any items' if empty!
1356
1357            self.drawxaxis()
1358            self.drawyaxis()
1359            self.xmarkers_set()
1360            self.yrange_auto()
1361        finally:
1362            self.frame.winfo_toplevel()['cursor'] = ocursor
1363
1364    def add_stats(self, stats):
1365        for (i, st) in enumerate(stats):
1366            self.draw_stat(i+self.numstats, st)
1367        self.numstats += len(stats)
1368        self.updatexaxis()
1369        self.setscrollregion()
1370
1371    def setxgrid(self, grid):
1372        self.xgrid = grid
1373        self.drawxaxis()
1374
1375    def setygrid(self, grid):
1376        self.ygrid = grid
1377        self.drawyaxis()
1378
1379    def setgraphtype(self, gmode, stats):
1380        graphtype, statype = gmode.split(' ')
1381        if graphtype != self.graphtype or statype != self.statype:
1382            self.graphtype = graphtype
1383            self.statype = statype
1384            if statype == 'Size':
1385                self.yunit['text'] = 'Bytes'
1386            elif statype == 'Count':
1387                self.yunit['text'] = 'Objects'
1388            else:
1389                raise ValueError
1390            self.load_stats(stats)
1391
1392    def setscrollregion(self):
1393        C = self.drawingarea
1394        botx = self.botx
1395        x1 = self.canx0
1396        x2 = self.tmax * self.xscale + self.canx0
1397        x1extra = botx / 2 + 2  # max(5, self.xscale*0.5)
1398        x2extra = botx / 2 + 2  # max(5, self.xscale*0.5)
1399
1400        x1 -= x1extra
1401        x2 += x2extra
1402
1403        y1 = 1-self.boty
1404        y2 = 1
1405
1406        self.scrollregion = (x1, y1, x2, y2)
1407        C.configure(scrollregion=self.scrollregion)
1408
1409        self.xaxis.configure(scrollregion=(x1, 0, x2, 10))
1410        self.xmarks.configure(scrollregion=(x1, 0, x2, 20))
1411        self.yaxis.configure(scrollregion=(0, y1, 20, y2))
1412
1413        self.drawingarea.yview(MOVETO, 0.0)
1414
1415    def setxrange(self, xrange):
1416        dxrange = self.xrange / float(xrange)
1417        self.xrange = xrange
1418        xscaleorg = self.drawingarea.canvasx(self.botx/2)
1419        self.drawingarea.scale("a", xscaleorg, 0, dxrange, 1.0)
1420        self.drawingarea.scale("barsep", xscaleorg, 0, dxrange, 1.0)
1421        self.canx0 = xscaleorg + (self.canx0 - xscaleorg) * dxrange
1422        self.xscale = self.botx / float(self.xrange)
1423        self.setxscrollincrement(max(2, self.xscale))
1424        self.drawxaxis()
1425        self.setscrollregion()
1426
1427    def setxscrollincrement(self, dx):
1428        return
1429        self.drawingarea.configure(xscrollincrement=dx)
1430        self.xaxis.configure(xscrollincrement=dx)
1431        self.xmarks.configure(xscrollincrement=dx)
1432
1433    def setyrange(self, yrange):
1434
1435        dyrange = float(self.yrange) / yrange
1436        self.yrange = yrange
1437        self.var_yrange.set(yrange)
1438
1439        self.drawingarea.scale("a", 0, 0, 1.0, dyrange)
1440        self.drawingarea.scale("barsep", 0, 0, 1.0, dyrange)
1441        self.yscale = float(self.boty) / self.yrange
1442        self.drawingarea.yview(MOVETO, 0.0)
1443        self.drawyaxis()
1444
1445    def xscrollbar_set(self, first, last):
1446        self.xscrollbar.set(first, last)
1447        self.yrange_auto()
1448
1449    def yrange_auto(self, force=0):
1450        if force or self.ycontrol.autovar.get():
1451            lo = max(0,
1452                     int(0.5+(self.drawingarea.canvasx(0) - self.canx0) / self.xscale))
1453            hi = min(len(self.ymaxs),
1454                     int(1.5+(self.drawingarea.canvasx(self.botx) - self.canx0) / self.xscale))
1455            if lo == hi:
1456                ymax = 1
1457            else:
1458                ymax = max(self.ymaxs[lo:hi])
1459            if ymax != self.ymax:
1460                self.ymax = ymax
1461                self.ycontrol.fit(ymax)
1462
1463
1464class MarkerControl:
1465    def __init__(self, master,
1466                 marker,
1467                 setcommand=lambda: 0
1468                 ):
1469        self.sample = 0
1470        self.numsamples = 0
1471        self.setcommand = setcommand
1472        self.marker = marker
1473        self.name = marker.name
1474        sf = self.frame = Frame(master, borderwidth=2, relief=GROOVE)
1475        self.samplevar = SizeVar()
1476        Label(sf, text='%s sample' % marker.name).grid(row=0, column=0)
1477        Label(sf,
1478              textvariable=self.samplevar,
1479              font=('terminal', '16', 'bold'),
1480              bg='black', fg='yellow'
1481              ).grid(row=1, column=0, padx=3, pady=3)
1482        ClickButton(sf, text='-',
1483                    pady=0, padx=5,
1484                    command=lambda: self.changesample(-1)).grid(row=0, column=1, sticky=E)
1485        ClickButton(sf, text='+',
1486                    pady=0, padx=5,
1487                    command=lambda: self.changesample(1)).grid(row=0, column=2, sticky=W)
1488        self.trackingvar = BooleanVar()
1489        self.trackbutton = Checkbutton(
1490            sf, text='Track',
1491            padx=5,
1492
1493            variable=self.trackingvar,
1494            relief=RAISED,
1495            command=self.settracking,
1496            indicatoron=1,
1497        )
1498        self.trackbutton.grid(row=1, column=1, columnspan=2)
1499
1500    def changesample(self, d):
1501        sample = self.sample + d
1502        if 0 <= sample < self.numsamples:
1503            self.setmarker(sample)
1504
1505    def setmarker(self, sample):
1506        self.marker.move(sample)
1507        self.setsample(sample)
1508
1509    def setnumsamples(self, num):
1510        self.numsamples = num
1511        if self.trackingvar.get() or self.sample >= self.numsamples:
1512            self.setmarker(max(0, self.numsamples-1))
1513
1514    def setsample(self, sample):
1515        self.sample = sample
1516        self.samplevar.set(sample)
1517        self.setcommand()
1518
1519    def settracking(self, tracking=None):
1520        if tracking is not None:
1521            self.trackingvar.set(tracking)
1522        else:
1523            tracking = self.trackingvar.get()
1524        if tracking:
1525            self.setmarker(max(0, self.numsamples-1))
1526
1527
1528class Window:
1529    def __init__(self, app, frame, windowmenu=None):
1530        self.app = app
1531        self.frame = frame
1532        self.windowmenu = windowmenu
1533        self.wtitle = frame.title()
1534        self._is_destroyed = 0
1535        # Binding to <destroy> didnt work well:
1536        #   frame.bind('<Destroy>', self.event_destroy, add='+')
1537        # I give up. I modify .destroy of frame argument instead.
1538        self.old_destroy = frame.destroy
1539        frame.destroy = self.new_destroy
1540
1541    def new_destroy(self):
1542        if self._is_destroyed:
1543            return
1544        self._is_destroyed = 1
1545        self.app.del_window(self)
1546        try:
1547            self.old_destroy()
1548        except TclError:
1549            # This may happen at closing last window
1550            # because exit destroys the root when it sees all windows were closed.
1551            # So I ignore it.
1552            pass
1553
1554    def title(self, title):
1555        self.frame.title(title)
1556        self.frame.iconname(title)
1557        self.wtitle = title
1558        self.app.chg_window(self)
1559
1560    def wakeup(self):
1561        frame = self.frame
1562        try:
1563            if frame.wm_state() == "iconic":
1564                frame.wm_deiconify()
1565            frame.tkraise()
1566            # I don't think I want .focus_set: it behaved strange in X at least.
1567            # frame.focus_set()
1568        except TclError:
1569            # This can happen when the window menu was torn off.
1570            # Simply ignore it.
1571            pass
1572
1573
1574class WindowMenu:
1575    def __init__(self, frame, variable):
1576        self.button = Menubutton(frame, text='Window')
1577        self.menu = Menu(self.button)
1578        self.button['menu'] = self.menu
1579        self.variable = variable
1580        self.wmap = {}
1581
1582    def add_window(self, window):
1583        self.menu.add_radiobutton(
1584            command=window.wakeup,
1585            label='%d %s' % (window.wid, window.wtitle),
1586            value=window.wid,
1587            variable=self.variable)
1588        self.wmap[window.wid] = self.menu.index(END)
1589
1590    def chg_window(self, window):
1591        self.menu.delete(self.wmap[window.wid])
1592        self.menu.insert_radiobutton(
1593            self.wmap[window.wid],
1594            command=window.wakeup,
1595            label='%d %s' % (window.wid, window.wtitle),
1596            value=window.wid,
1597            variable=self.variable)
1598
1599    def del_window(self, window):
1600        idx = self.wmap[window.wid]
1601        del self.wmap[window.wid]
1602        try:
1603            self.menu.delete(idx)
1604        except TclError:
1605            # This can happen if the menu was destroyed before its contents.
1606            # Simply ignore it.
1607            pass
1608        for wid in list(self.wmap.keys()):
1609            if self.wmap[wid] > idx:
1610                self.wmap[wid] -= 1
1611
1612
1613class ProfileApp:
1614    def __init__(self, mod):
1615        self.mod = mod
1616        root = Tk()
1617        self.root = root
1618        root.withdraw()
1619        self.windows = {}
1620        self.windowmenus = {}
1621        self.var_window = IntVar(root)
1622
1623    def add_window(self, window):
1624        window.wid = max([0]+list(self.windows.keys()))+1
1625        self.windows[window.wid] = window
1626        wm = getattr(window, 'windowmenu', None)
1627        if wm:
1628            self.windowmenus[window.wid] = wm
1629            for w in list(self.windows.values()):
1630                if w is not window:
1631                    wm.add_window(w)
1632        for wm in list(self.windowmenus.values()):
1633            wm.add_window(window)
1634        self.var_window.set(window.wid)
1635        window.frame.bind('<FocusIn>',
1636                          lambda event: self.var_window.set(window.wid), add='+')
1637        window.frame.bind('<Deactivate>',
1638                          lambda event: self.var_window.set(0), add='+')
1639
1640    def add_window_frame(self, frame, windowmenu=None):
1641        w = Window(self, frame, windowmenu)
1642        self.add_window(w)
1643        return w
1644
1645    def chg_window(self, window):
1646        for wm in list(self.windowmenus.values()):
1647            wm.chg_window(window)
1648
1649    def del_window(self, window):
1650        wid = window.wid
1651        if getattr(window, 'windowmenu', None):
1652            del self.windowmenus[wid]
1653        del self.windows[wid]
1654        for wm in list(self.windowmenus.values()):
1655            wm.del_window(window)
1656        if not self.windows:
1657            self.exit()
1658
1659    def exit(self):
1660        try:
1661            self.root.destroy()
1662        except TclError:
1663            pass
1664        self.root.quit()
1665
1666    def mainloop(self):
1667        return self.root.mainloop()
1668
1669    def new_profile_browser(self, filename):
1670        return ProfileBrowser(self, filename)
1671
1672
1673class PaneDiv:
1674    def __init__(self, master, movecommand):
1675        self.frame = frame = Frame(master)
1676        self.movecommand = movecommand
1677
1678        self.butsize = bs = 6
1679        bc = self.butcent = bs / 2 + 3
1680        h = 10
1681        self.top = Canvas(
1682            frame,
1683            width=10,
1684            height=h,
1685        )
1686
1687        self.top.create_line(
1688            bc, 0, bc, h, fill='#808080', width=1)
1689        self.top.create_line(
1690            bc+1, 0, bc+1, h, fill='white', width=1)
1691
1692        self.rsbut = Canvas(
1693            frame,
1694            cursor='crosshair',
1695            width=self.butsize,
1696            height=self.butsize,
1697            relief=RAISED,
1698            bd=2
1699        )
1700
1701        self.bot = Canvas(
1702            frame,
1703            width=10,
1704            height=300,
1705            bd=0
1706        )
1707
1708        self.top.grid(row=0, column=0, sticky=N)
1709        self.rsbut.grid(row=1, column=0, sticky=N)
1710        self.bot.grid(row=2, column=0, sticky=N)
1711
1712        self.rsbut.bind('<Button-1>', self.but_down)
1713        self.rsbut.bind('<ButtonRelease-1>', self.but_up)
1714
1715    def but_down(self, event):
1716        self.down_event = event
1717        self.rsbut.configure(relief=SUNKEN)
1718
1719    def but_up(self, event):
1720        self.rsbut.configure(relief=RAISED)
1721        dx = event.x - self.down_event.x
1722        self.movecommand(dx)
1723
1724    def setheight(self, height):
1725        h = height - 18
1726        self.bot['height'] = h
1727
1728        bc = self.butcent
1729
1730        self.bot.create_line(
1731            bc, 0, bc, h, fill='#808080', width=1)
1732        self.bot.create_line(
1733            bc+1, 0, bc+1, h, fill='white', width=1)
1734
1735
1736class TableFrame:
1737    def __init__(self, graph, master, numkindrows, samplevar):
1738        self.graph = graph
1739        self.mod = graph.mod
1740        frame = self.frame = Frame(master, borderwidth=2, relief=GROOVE)
1741        row = 0
1742        self.marktime = StringVar()
1743        self.totsizevar = SizeVar()
1744        self.sampler = StringVar()
1745        self.sampler.set('R')
1746
1747        fr = Frame(frame)  # For header
1748        om = OptionMenu(fr, self.sampler, 'R', 'L', 'R-L')
1749        om.grid(row=0, column=0, sticky=W)
1750        Label(fr, text='Sample').grid(row=0, column=1, sticky=W)
1751        Label(fr, textvariable=samplevar, background='black', foreground='yellow',
1752              ).grid(row=0, column=2, sticky=W, pady=3)
1753        Label(fr, text='at').grid(row=0, column=3, sticky=W)
1754        Label(fr, textvariable=self.marktime).grid(
1755            row=0, column=4, sticky=W)
1756        Label(fr, text='Total size = ').grid(
1757            row=1, column=0, columnspan=3, sticky=W)
1758        Label(fr, textvar=self.totsizevar).grid(
1759            row=1, column=3, columnspan=2, sticky=W)
1760        fr.grid(row=row, column=0, sticky=W)
1761        row += 1
1762
1763        orow = row
1764
1765        tb = Frame(frame)
1766        row = 0
1767
1768        Label(tb, text="").grid(row=row, column=0)
1769        Label(tb, text="R", ).grid(row=row, column=1, sticky=E)
1770        Label(tb, text="%R").grid(row=row, column=2, sticky=E)
1771        Label(tb, text="R-L", ).grid(row=row, column=3, sticky=E)
1772        Label(tb, text="%L").grid(row=row, column=4, sticky=E)
1773        Label(tb, text="Kind").grid(row=row, column=5, sticky=W)
1774
1775        row += 1
1776        self.profrows = []
1777        self.totrow = ProfileRow(tb, row)
1778        self.profrows.append(self.totrow)
1779        row += 1
1780
1781        for i in range(numkindrows+1):
1782            profrow = ProfileRow(tb, row)
1783            self.profrows.append(profrow)
1784            row += 1
1785
1786        row = orow
1787        tb.grid(row=row, column=0, sticky=W)
1788
1789        # for next..
1790        row += 1
1791
1792        self.totresize = 0
1793        self.kindwidth = ProfileRow.kindwidth
1794
1795    def resize(self, dx, dy):
1796        dx = int(dx)
1797        self.totresize += dx
1798        charresize, extra = divmod(self.totresize, 7)
1799        newwidth = ProfileRow.kindwidth + charresize
1800        oldwidth = self.profrows[0].kind['width']
1801        if newwidth < 10:
1802            newwidth = 10
1803            dx = (newwidth - oldwidth) * 7 + extra
1804        for pr in self.profrows:
1805            pr.kind['width'] = newwidth
1806            pr.kindwidth = newwidth
1807            pr.kind['padx'] = extra / 2
1808            import textwrap
1809            kindtext = textwrap.fill(pr.kindtext, width=pr.kindwidth)
1810            pr.set_kind(pr.kindtext)
1811
1812        return dx, dy
1813
1814    def update(self, lsamp, rsamp):
1815
1816        self.marktime.set(self.mod.time.asctime(
1817            self.mod.time.localtime(rsamp.stat.timemade)))
1818
1819        return
1820
1821        for pr in self.profrows:
1822            pr.clear()
1823
1824        rdiv = float(rsamp.stat.size)
1825        ldiv = float(lsamp.stat.size)
1826
1827        self.totrow.set_color_size_percent_kind(
1828            None,
1829            rsamp.stat.size,
1830            100.0,
1831            rsamp.stat.size - lsamp.stat.size,
1832            (rsamp.stat.size - lsamp.stat.size) * 100.0 / ldiv,
1833            '<Total>'
1834        )
1835
1836        for i, r in enumerate(rsamp.rows):
1837            l = lsamp.kindrows[r.name]
1838            self.profrows[i+1].set_color_size_percent_kind(
1839                self.graph.getkindcolor(r.name),
1840                r.size,
1841                r.size * 100.0 / rdiv,
1842                r.size - l.size,
1843                (r.size - l.size) * 100.0 / ldiv,
1844                r.name)
1845
1846
1847class ColSpec:
1848    def __init__(self, tf, header, width, pos, render, idx=()):
1849        self.tf = tf
1850        self.header = header
1851        self.name = header
1852        self.width = width
1853        self.pos = pos
1854        self.render = render
1855        self.idx = idx
1856
1857    def align(self, text):
1858        sp = ' '*(self.width - len(text))
1859        if self.pos == LEFT:
1860            text = text + sp
1861        elif self.pos == RIGHT:
1862            text = sp[:-1] + text + ' '
1863        else:
1864            assert 0
1865        assert len(text) == self.width
1866        return text
1867
1868
1869class TableFrame:
1870    def __init__(self, graph, master):
1871        self.graph = graph
1872        self.mod = graph.mod
1873
1874        frame = self.frame = Frame(
1875            master,
1876            borderwidth=3,
1877            relief=SUNKEN
1878        )
1879
1880        self.colspecs = {}
1881        self.colwidths = []
1882
1883        def defcol(names, width, pos, put, idxfunc=lambda x: ()):
1884            if callable(put):
1885                put = [put]*len(names)
1886            self.colwidths.append(width)
1887            for name, put in zip(names, put):
1888                spec = ColSpec(self, name, width, pos, put, idxfunc(name))
1889                self.colspecs[name] = spec
1890
1891        defcol(('A', 'B'), 2, LEFT, self.putcolor, lambda x: x)
1892        defcol(('Size', 'Count'), 7, RIGHT, [self.putsize, self.putcount])
1893        defcol(('%A:Tot', '%B:Tot'), 7, RIGHT,
1894               self.putpercent, lambda name: name[1])
1895        defcol(('B-A', 'A-B', 'Cumul'), 7, RIGHT, [self.putdiff, self.putdiff, self.putcumul],
1896               lambda name: [(), name.split('-')]['-' in name])
1897        defcol(('%A:Tot', '%B:Tot'), 7, RIGHT,
1898               self.putpercent, lambda name: name[1])
1899        defcol(('Kind',), 20, LEFT, self.putkind)
1900
1901        width = 0
1902        for w in self.colwidths:
1903            width += w
1904        self.totxresize = 0
1905        self.totyresize = 0
1906        self.kindcol = self.colspecs['Kind']
1907        self.orgkindwidth = self.kindcol.width
1908        self.widthbeforekind = width - self.orgkindwidth
1909        self.minkindwidth = 10
1910        self.mintextheight = 2
1911        width += 1
1912        self.width = self.orgwidth = width
1913
1914        wrap = NONE
1915        cursor = master['cursor']
1916        relief = FLAT
1917        self.minpadx = 3
1918        self.tothead = Text(
1919            frame,
1920            width=width,
1921            wrap=wrap,
1922            background='#ccc',
1923            height=2,
1924            padx=self.minpadx,
1925            relief=relief,
1926            cursor=cursor,
1927        )
1928
1929        self.rowhead = Text(
1930            frame,
1931            width=width,
1932            wrap=wrap,
1933            background='#ccc',
1934            height=1,
1935            padx=self.minpadx,
1936            relief=relief,
1937            cursor=cursor,
1938        )
1939
1940        self.tsframe = Frame(frame)
1941
1942        self.textminpady = 2
1943        self.text = Text(
1944            self.tsframe,
1945            width=width,
1946            wrap=wrap,
1947            height=21,
1948            background='#e0e0e0',
1949            relief=relief,
1950            takefocus=0,
1951            cursor=cursor,
1952            padx=self.minpadx,
1953            pady=self.textminpady,
1954        )
1955
1956        self.scrollbar = Scrollbar(
1957            self.tsframe,
1958            width=10,
1959            orient=VERTICAL,
1960            command=self.text.yview
1961        )
1962        self.scrollbar_totwidth = int(
1963            self.scrollbar['width']) + 6  # width + padding
1964
1965        self.uses_scrollbar = 0
1966        self.auto_scrollbar = 1
1967
1968        self.orgtextheight = int(self.text['height'])
1969
1970        padx = 0
1971        pady = 0
1972        self.tothead.pack(anchor=N+W, padx=padx, pady=pady)
1973        self.rowhead.pack(anchor=N+W, padx=padx, pady=pady)
1974        self.text.pack(side=LEFT, anchor=N+W, padx=padx, pady=pady)
1975        self.tsframe.pack(anchor=N+W, padx=padx, pady=pady)
1976
1977    def setchdim(self):
1978        self.text.update()
1979        self.chdx = float(self.text.winfo_width()) / self.width
1980        self.chdy = float(self.text.winfo_height()) / self.orgtextheight
1981        self.chdx = int(round(self.chdx))
1982        self.chdy = int(round(self.chdy))
1983        self.pixwidth = self.width * self.chdx
1984        self.pixheight = self.width * self.chdy
1985
1986    def putcolor(self, col):
1987        if self.colorow.name == '<Total>':
1988            text = col.align(' ')
1989            color = '#e0e0e0'
1990        else:
1991            color = self.graph.getkindcolor(self.colorow.name),
1992            text = col.align('@')
1993        self.text.insert('end', text, (color,))
1994        self.text.tag_config(color, foreground=color, background='#e0e0e0',
1995                             font=('terminal', '12', 'bold'),)
1996
1997    def putcount(self, col):
1998        self.valmode = 'Count'
1999        count = self.colorow.count
2000        self.cumulval += count
2001        self.putval(col, count)
2002
2003    def putsize(self, col):
2004        self.valmode = 'Size'
2005        size = self.colorow.size
2006        self.cumulval += size
2007        self.putval(col, size)
2008
2009    def putval(self, col, val):
2010        self.curval = val
2011        self.ap(col.align(sizestring(val)))
2012
2013    def putpercent(self, col):
2014        a = self.statbyname[col.idx]
2015
2016        if self.valmode == 'Count':
2017            ref = a.count
2018        elif self.valmode == 'Size':
2019            ref = a.size
2020
2021        if ref:
2022            ps = percentstring(self.curval * 100.0 / ref)
2023        else:
2024            ps = '---'
2025        self.ap(col.align(ps))
2026
2027    def putdiff(self, col):
2028
2029        a, b = self.rowbyname[col.idx[0]], self.rowbyname[col.idx[1]]
2030        if self.valmode == 'Count':
2031            a, b = a.count, b.count
2032        elif self.valmode == 'Size':
2033            a, b = a.size, b.size
2034
2035        self.putval(col, a - b)
2036
2037    def putcumul(self, col):
2038        self.putval(col, self.cumulval)
2039
2040    def putkind(self, col):
2041        # Must be last!
2042        import textwrap
2043        wraplines = textwrap.wrap(self.colorow.name, width=col.width)
2044        self.ap(col.align(wraplines[0]))
2045        if len(wraplines) > 1:
2046            initial = '\n'+' '*(self.widthbeforekind)
2047            for line in wraplines[1:]:
2048                self.ap(initial+col.align(line))
2049
2050    def setmode(self, mode, numkindrows):
2051        self.mode = mode
2052        self.numkindrows = numkindrows
2053        self.mcontrols = self.graph.mcontrolbyname
2054        self.stats = self.graph.stats
2055        self.cols = [self.colspecs[x.strip()]
2056                     for x in mode.split(' ') if x.strip()]
2057        self.controlnames = {}
2058        name = self.cols[0].idx
2059        self.colorcontrol = self.mcontrols[name]
2060        self.controlnames[name] = 1
2061        self.controls = [self.colorcontrol]
2062        self.lastidxs = [None]
2063        for i, co in enumerate(self.cols):
2064            idx = co.idx
2065            if not isinstance(idx, (tuple, list)):
2066                idx = (idx,)
2067            for idx in idx:
2068                if idx not in self.controlnames:
2069                    self.controls.append(self.mcontrols[idx])
2070                    self.controlnames[idx] = 1
2071                    self.lastidxs.append(None)
2072
2073    def setscrollbar(self, sb):
2074        if sb == self.uses_scrollbar:
2075            return
2076        self.uses_scrollbar = sb
2077        w = self.scrollbar_totwidth
2078        if sb:
2079            self.resize(-w, 0, setscrollbar=0)
2080            self.scrollbar.pack(side=LEFT, fill=Y)
2081            self.text['yscrollcommand'] = self.scrollbar.set
2082        else:
2083            self.resize(w, 0, setscrollbar=0)
2084            self.scrollbar.pack_forget()
2085            self.text['yscrollcommand'] = None
2086
2087    def update_simple(self, lsamp, rsamp):
2088        t = self.text
2089        t.delete('1.0', '100.0')
2090        t.insert('1.0', str(rsamp.stat))
2091
2092    def update(self, force=0, setscrollbar=1):
2093
2094        stats = self.stats
2095
2096        idxs = [max(0, min(control.sample, len(stats)-1))
2097                for control in self.controls]
2098        if (idxs == self.lastidxs) and not force:
2099            return
2100
2101        self.lastidxs = idxs
2102
2103        self.text['state'] = self.tothead['state'] = self.rowhead['state'] = NORMAL
2104
2105        self.text.delete('1.0', END)
2106        self.tothead.delete('1.0', END)
2107        self.rowhead.delete('1.0', END)
2108
2109        if not stats:
2110            self.tothead.insert('end', '-- No Sample --')
2111            self.text['state'] = self.tothead['state'] = self.rowhead['state'] = DISABLED
2112            return
2113
2114        self.statbyname = {}
2115        statbyidx = []
2116        for i, control in enumerate(self.controls):
2117            stat = stats[idxs[i]]
2118            statbyidx.append(stat)
2119            self.statbyname[control.name] = stat
2120
2121        samps = self.samps = [
2122            Sample(self.mod, statbyidx[0], self.controls[0].marker.name, idxs[0],
2123                   numkindrows=self.numkindrows,
2124                   statype=self.graph.display.statype
2125                   )]
2126
2127        self.colorsamp = samps[0]
2128        if len(self.controls) > 1:
2129            samps.append(Sample(self.mod, statbyidx[1], self.controls[1].marker.name, idxs[1],
2130                                relative=samps[0]))
2131
2132            self.relsamp = samps[1]
2133
2134        t = self.tothead
2135
2136        n = max([len(str(samp.index)) for samp in samps])
2137        for samp in samps:
2138            t.insert('end', 'Sample %s: ' % samp.name)
2139            t.insert('end', ('%%%dd' % n) % samp.index, ('index',))
2140            t.insert('end', ' at %s\n' % (samp.datetime))
2141
2142        t.tag_configure('index', background='#e0e0e0')
2143
2144        t = self.rowhead
2145
2146        self.sizes = [float(samp.stat.size) for samp in samps]
2147
2148        for col in self.cols:
2149            t.insert('end', col.align(col.header), ('header',))
2150        t.insert('end', '\n')
2151
2152        t = self.text
2153
2154        self.ap = lambda text: t.insert('end', text)
2155
2156        self.colorow = Row(samps[0].count, samps[0].size, '<Total>')
2157        self.rowbyname = self.statbyname
2158        self.cumulval = 0
2159        for col in self.cols:
2160            col.render(col)
2161        self.ap('\n\n')
2162
2163        self.cumulval = 0
2164        for i, a in enumerate(samps[0].rows):
2165            self.colorow = a
2166            if len(samps) > 1:
2167                self.rowbyname = {
2168                    samps[0].name: a,
2169                    samps[1].name: samps[1].kindrows[a.name]
2170                }
2171            for col in self.cols:
2172                col.render(col)
2173            self.ap('\n')
2174
2175        if setscrollbar and self.auto_scrollbar:
2176            numrows = int(self.text.index('end').split('.')[0])-2
2177            h = int(self.text['height'])
2178            needs_scrollbar = numrows > h
2179            if needs_scrollbar != self.uses_scrollbar:
2180                self.setscrollbar(needs_scrollbar)
2181
2182        self.text['state'] = self.tothead['state'] = self.rowhead['state'] = DISABLED
2183
2184    def resize(self, dx, dy, setscrollbar=1):
2185        dx = int(dx)
2186        oldwidth = self.pixwidth
2187        newwidth = self.pixwidth + dx
2188        if newwidth < self.chdx * 2:
2189            newwidth = self.chdx * 2
2190
2191        self.pixwidth = newwidth
2192        dx = newwidth - oldwidth
2193
2194        charwidth, extra = divmod(newwidth, self.chdx)
2195
2196        self.kindcol.width = max(
2197            charwidth - self.widthbeforekind - 1, self.minkindwidth)
2198
2199        self.totxresize += dx
2200        for t in (self.tothead, self.rowhead, self.text):
2201            t['width'] = charwidth
2202            t['padx'] = self.minpadx + extra / 2
2203
2204        dy = int(dy)
2205
2206        rowresize, extra = divmod(self.totyresize + dy, self.chdy)
2207        newheight = self.orgtextheight + rowresize
2208        oldheight = int(self.text['height'])
2209        if newheight < self.mintextheight:
2210            newheight = self.mintextheight
2211            dy = (newheight - oldheight) * self.chdy + extra
2212        self.totyresize += dy
2213
2214        self.text['height'] = newheight
2215        self.text['pady'] = self.textminpady + extra / 2
2216
2217        self.update(force=1, setscrollbar=1)
2218
2219        return dx, dy
2220
2221
2222class Filler:
2223    def __init__(self, master):
2224        self.frame = self.can = Canvas(
2225            master,
2226            # background='blue',
2227            width=0,
2228            height=0)
2229
2230    def getsize(self):
2231        return int(self.can['width']), int(self.can['height']),
2232
2233    def setsize(self, w, h):
2234        self.can.configure(
2235            width=w,
2236            height=h
2237        )
2238
2239    def resize(self, dw, dh):
2240        w, h = self.getsize()
2241        self.setsize(max(0, w + dw), max(0, h + dh))
2242
2243
2244class Row:
2245    def __init__(self, count, size, name):
2246        self.count = count
2247        self.size = size
2248        self.name = name
2249
2250
2251class Sample:
2252    def __init__(self, mod, stat, name, index, numkindrows=None, statype='Size', relative=None):
2253        self.stat = stat
2254        self.size = stat.size
2255        self.count = stat.count
2256        self.name = name
2257        self.index = index
2258
2259        self.datetime = mod.time.asctime(mod.time.localtime(stat.timemade))
2260
2261        self.kindrows = {}
2262
2263        if numkindrows is not None:
2264            rows = stat.get_rows_n_and_other(numkindrows, statype)
2265            for r in rows:
2266                self.kindrows[r.name] = r
2267        else:
2268            kinds = []
2269            oidx = None
2270            for row in relative.rows:
2271                if row.name == '<Other>':
2272                    oidx = len(kinds)
2273                    continue
2274                else:
2275                    kinds.append(row.name)
2276
2277            rows = stat.get_rows_of_kinds(kinds)
2278            size = 0
2279            count = 0
2280            for i, row in enumerate(rows):
2281                kind = kinds[i]
2282                if row is None:
2283                    row = Row(0, 0, kind)
2284                self.kindrows[kind] = row
2285                size += row.size
2286                count += row.count
2287
2288            if oidx is not None:
2289                other = Row(stat.count - count, stat.size - size, '<Other>')
2290                rows[oidx:oidx] = [other]
2291                self.kindrows['<Other>'] = other
2292
2293        self.rows = rows
2294
2295
2296class ProfileBrowser:
2297    colors = ("red", "green", "blue", "yellow", "magenta", "cyan", 'white')
2298    numkindrows = 10
2299
2300    def __init__(self, app, filename):
2301        self.inited = 0
2302        self.app = app
2303        self.mod = mod = app.mod
2304        self.master = master = app.root
2305        if filename:
2306            filename = mod.path.abspath(filename)
2307            self.initialdir = mod.path.dirname(filename)
2308        else:
2309            self.initialdir = mod.os.getcwd()
2310
2311        self.frame = frame = Toplevel(
2312            master,
2313            # background='#bbb'
2314        )
2315        #frame['cursor'] = 'umbrella'
2316        # frame.resizable(True,True)
2317
2318        self.menubar = Frame(self.frame, relief=RAISED, bd=2)
2319
2320        self.filebutton = Menubutton(self.menubar, text='File')
2321        self.filemenu = Menu(self.filebutton)
2322        self.filebutton['menu'] = self.filemenu
2323        self.filemenu.add_command(
2324            label='New Profile Browser', command=self.cmd_new)
2325        self.filemenu.add_command(label='Open Profile', command=self.cmd_open)
2326        self.filemenu.add_command(label='Close Window', command=self.cmd_close)
2327        self.filemenu.add_command(
2328            label='Clear Cache', command=self.cmd_clear_cache)
2329        self.filemenu.add_command(label='Exit', command=self.cmd_exit)
2330        self.panebutton = Menubutton(self.menubar, text='Pane')
2331        self.panemenu = Menu(self.panebutton)
2332        self.panebutton['menu'] = self.panemenu
2333
2334        choices = [
2335            ('Bars', 'Lines'),
2336            ('Size', 'Count'),
2337        ]
2338
2339        self.graphtypevar = StringVar()
2340        self.graphbutton = self.modechooser(
2341            self.menubar, 'Graph', choices,
2342            self.graphtypevar, self.cmd_graphtype)
2343
2344        choices = [
2345            ('A', 'B'),
2346            ('Size', 'Count'),
2347            ('%A:Tot', '%B:Tot'),
2348            ('Cumul', 'A-B', 'B-A'),
2349            ('%A:Tot', '%B:Tot'),
2350            ('Kind',),
2351        ]
2352
2353        self.var_tablemode = StringVar()
2354        self.tablebutton = Menubutton(self.menubar, text='Table')
2355        self.tablemenu = Menu(self.tablebutton)
2356        self.tablebutton['menu'] = self.tablemenu
2357        self.headermenu = Menu(self.tablebutton, title='Table header')
2358        self.addmodechooser(
2359            self.headermenu,
2360            choices,
2361            self.var_tablemode,
2362            self.cmd_tablemode
2363        )
2364        self.tablemenu.add_cascade(label='Header', menu=self.headermenu)
2365        self.var_tablescrollbar = StringVar()
2366        self.tablescrollbarmenu = Menu(
2367            self.tablebutton, title='Table scrollbar')
2368
2369        self.addmodechooser(
2370            self.tablescrollbarmenu,
2371            [('Auto', 'On', 'Off')],
2372            self.var_tablescrollbar,
2373            self.cmd_tablescrollbar
2374        )
2375
2376        self.tablemenu.add_cascade(
2377            label='Scrollbar',
2378            menu=self.tablescrollbarmenu)
2379
2380        self.windowmenu = WindowMenu(self.menubar, self.app.var_window)
2381        self.window = app.add_window_frame(self.frame, self.windowmenu)
2382
2383        self.helpbutton = Menubutton(self.menubar, text='Help')
2384        self.helpmenu = Menu(self.helpbutton)
2385        self.helpbutton['menu'] = self.helpmenu
2386        self.helpmenu.add_command(label='About', command=self.cmd_about)
2387        self.helpmenu.add_command(label='Help', command=self.cmd_help)
2388
2389        self.ctrlframe = Frame(
2390            self.frame,
2391            bd=2,
2392            relief=GROOVE,
2393            # background='#999',
2394
2395
2396        )
2397
2398        self.exitbutton = Button(self.ctrlframe, text='Exit', command=self.cmd_exit,
2399                                 background='red')
2400
2401        self.set_filename(filename)
2402
2403        self.id_collect = None
2404        self.collecting = IntVar()
2405        self.collecting.set(0)
2406        self.collectbutton = Checkbutton(self.ctrlframe, text='Collect',
2407                                         variable=self.collecting,
2408                                         command=self.cmd_collect,
2409                                         relief=RAISED)
2410
2411        self.stats = Stats(self.mod)
2412
2413        self.disptab = Frame(self.frame,
2414                             # relief=SUNKEN,
2415                             # bd=3
2416                             )
2417
2418        self.display = Display(self.disptab,
2419                               scale_table=AxisControl.scale_table,
2420                               numkindrows=self.numkindrows,
2421                               getkindcolor=self.getkindcolor,
2422                               )
2423
2424        self.xcontrol = AxisControl(self.ctrlframe,
2425                                    name='X',
2426                                    range=self.display.xrange,
2427                                    grid=self.display.xgrid,
2428                                    unit='samples',
2429                                    rangecommand=self.display.setxrange,
2430                                    gridcommand=self.display.setxgrid
2431                                    )
2432
2433        self.ycontrol = AxisControl(self.ctrlframe,
2434                                    name='Y',
2435                                    range=self.display.yrange,
2436                                    grid=self.display.ygrid,
2437                                    unit='bytes',
2438                                    rangecommand=self.display.setyrange,
2439                                    gridcommand=self.display.setygrid,
2440                                    autocommand=self.display.cmd_yrange_auto
2441                                    )
2442
2443        self.display.xcontrol = self.xcontrol
2444        self.display.ycontrol = self.ycontrol
2445
2446        self.mcontrols = []
2447        self.mcontrolbyname = {}
2448        for name in ('A', 'B'):
2449            marker = self.display.new_xmarker(name)
2450            control = MarkerControl(
2451                self.ctrlframe, marker, self.update_tableframe)
2452            marker.set_poscommand(control.setsample)
2453            self.mcontrols.append(control)
2454            self.mcontrolbyname[name] = control
2455
2456        self.var_showcontrol = BooleanVar()
2457        self.var_showcontrol.set(1)
2458        self.panemenu.add_checkbutton(
2459            label='Show Control Panel',
2460            variable=self.var_showcontrol,
2461            command=self.cmd_showcontrol)
2462
2463        self.var_showgraph = BooleanVar()
2464        self.var_showgraph.set(1)
2465        self.panemenu.add_checkbutton(
2466            label='Show Graph',
2467            variable=self.var_showgraph,
2468            command=self.cmd_showgraph)
2469
2470        self.var_showtable = BooleanVar()
2471        self.var_showtable.set(1)
2472        self.panemenu.add_checkbutton(
2473            label='Show Table',
2474            variable=self.var_showtable,
2475            command=self.cmd_showtable)
2476
2477        tf = self.tf = TableFrame(self, self.disptab)
2478        d_t = self.d_t = PaneDiv(self.disptab, movecommand=self.cmd_dt_moved)
2479
2480        self.xcontrol.frame.grid(row=0, column=0, padx=3, pady=3, sticky=W)
2481        self.ycontrol.frame.grid(row=1, column=0, padx=3, pady=3)
2482        self.mcontrols[0].frame.grid(
2483            row=0, column=1, columnspan=1, sticky=W, padx=3, pady=3)
2484        self.mcontrols[1].frame.grid(
2485            row=1, column=1, columnspan=1, sticky=W, padx=3, pady=3)
2486        self.exitbutton.grid(row=0, column=2, padx=3, pady=3)
2487        self.collectbutton.grid(row=0, column=3, padx=3, pady=3)
2488
2489        self.filler = Filler(self.frame)
2490
2491        self.filebutton.pack(side=LEFT)
2492        self.panebutton.pack(side=LEFT)
2493        self.graphbutton.pack(side=LEFT)
2494        self.tablebutton.pack(side=LEFT)
2495        self.windowmenu.button.pack(side=LEFT)
2496        self.helpbutton.pack(side=LEFT)
2497
2498        self.menubar.grid(column=0, columnspan=4, sticky=N+W+E)
2499        self.gridmain()
2500
2501        frame.bind('<Map>', self.event_map)
2502
2503        self.tf.setmode(self.var_tablemode.get(), self.numkindrows)
2504
2505        self.load_filename(filename)
2506
2507        d_t.frame.update_idletasks()
2508        d_t.setheight(max(self.display.frame.winfo_height(),
2509                          tf.frame.winfo_height()))
2510
2511        d_t.frame.update_idletasks()
2512        self.minsize = (500, 400)
2513        self.maxsize = (self.frame.winfo_screenwidth(),
2514                        self.frame.winfo_screenheight())
2515        minsizes = {
2516            # (ctrl, disp, tab) : (width, height)
2517            (0, 0, 0): (270, 25),
2518            (1, 0, 0): (363, 61),
2519            (0, 1, 0): (270, 131),
2520            (1, 1, 0): (270, 131),
2521        }
2522
2523        self.setusergeometry()
2524
2525        def initfinal():
2526            self.tf.setchdim()
2527
2528            rx = self.frame.winfo_rootx() + self.frame.winfo_width()
2529            self.tf_wanted_margin = rx - \
2530                (self.tf.frame.winfo_rootx() + self.tf.frame.winfo_width())
2531
2532            self.lastw = self.frame.winfo_width()
2533            self.lasth = self.frame.winfo_height()
2534            self.in_configure = 0
2535            frame.bind('<Configure>', self.event_configure)
2536            self.inited = 1
2537
2538        initfinal()
2539        # self.frame.after_idle(initfinal)
2540
2541    def cmd_about(self):
2542        self.cmd_help('about')
2543
2544    def cmd_help(self, pickname='help'):
2545        os = self.mod.os
2546        ocursor = self.frame.winfo_toplevel()['cursor']
2547        try:
2548            self.frame.winfo_toplevel()['cursor'] = 'watch'
2549            self.frame.update()
2550            m = self.mod.Text.gsltextviewer(
2551                self.frame,
2552                inpickle=getattr(self.mod.pbhelp, pickname)
2553                # htmloutfile='/tmp/x.html',
2554            )
2555
2556            self.app.add_window_frame(m)
2557        finally:
2558            self.frame.winfo_toplevel()['cursor'] = ocursor
2559
2560    def cmd_clear_cache(self):
2561        self.stats.clear_cache()
2562
2563    def cmd_close(self):
2564        self.frame.destroy()
2565
2566    def cmd_collect(self, *args):
2567        # self.afterfunc()
2568        # self.frame.after(1, self.afterfunc) # Turn on button first.??
2569
2570        if self.collecting.get():
2571            self.event_collect()
2572        else:
2573            if self.id_collect is not None:
2574                self.frame.after_cancel(self.id_collect)
2575                self.id_collect = None
2576
2577    def event_collect(self):
2578        o, n = self.stats.collect()
2579        if n:
2580            if o != self.display.numstats:
2581                self.display.load_stats(self.stats)
2582            else:
2583                st = self.stats[-n:]
2584                self.display.add_stats(st)
2585            for c in self.mcontrols:
2586                c.setnumsamples(len(self.stats))
2587
2588        self.id_collect = self.frame.after(1000, self.event_collect)
2589
2590    def cmd_dt_moved(self, dx):
2591        # The division between display and table panes moved.
2592
2593        # Disable configure event handling while we are resizing.
2594        self.in_configure += 1
2595
2596        # Right x position of enclosing frame
2597
2598        rx = self.frame.winfo_rootx() + self.frame.winfo_width()
2599
2600        # Right margin between pane divider and enclosing window
2601
2602        mx = rx - (self.d_t.frame.winfo_rootx() + self.d_t.frame.winfo_width())
2603
2604        # Don't move pane divider outside window
2605        dx = min(dx, mx)
2606
2607        # Right margin between table and enclosing window
2608        # before resizing
2609        mx = rx - (self.tf.frame.winfo_rootx() + self.tf.frame.winfo_width())
2610
2611        dx, _ = self.display.resize(dx, 0)
2612
2613        wanted_margin = self.tf_wanted_margin
2614
2615        # After move
2616        mx -= dx
2617        self.tf.resize(mx - wanted_margin, 0)
2618
2619        self.display.moveback()
2620
2621        self.in_configure -= 1
2622
2623    def cmd_exit(self):
2624        self.app.exit()
2625
2626    def cmd_graphtype(self):
2627        self.display.setgraphtype(self.graphtypevar.get(), self.stats)
2628        self.cmd_tablemode()
2629
2630    def cmd_new(self):
2631        self.app.new_profile_browser(self.filename)
2632
2633    def cmd_open(self):
2634        op = tkinter.filedialog.Open(self.frame,
2635                                     # ? Should we have default extension or not??
2636                                     # defaultextension='.hpy',
2637                                     initialdir=self.initialdir,
2638                                     filetypes=[('Heapy data files', '.hpy'),
2639                                                ('All files', '*')
2640                                                ]
2641                                     )
2642        filename = op.show()
2643        if filename:
2644            self.load_filename(filename)
2645
2646    def cmd_showcontrol(self):
2647        self.grid_things()
2648
2649    def cmd_showgraph(self):
2650        if self.var_showgraph.get() and self.var_showtable.get():
2651            self.tf.resize(-self.tf.totxresize, 0)
2652            self.display.resize(self.display.orgwidth - self.display.botx, 0)
2653            self.display.moveback()
2654        self.grid_things()
2655
2656    cmd_showtable = cmd_showgraph
2657
2658    def cmd_tablemode(self):
2659        self.tf.setmode(self.var_tablemode.get(), self.numkindrows)
2660        self.tf.update()
2661
2662    def cmd_tablescrollbar(self):
2663        tf = self.tf
2664        s = self.var_tablescrollbar.get()
2665        if s == 'Auto':
2666            tf.auto_scrollbar = 1
2667            tf.update(force=1, setscrollbar=1)
2668        elif s == 'On':
2669            tf.auto_scrollbar = 0
2670            tf.setscrollbar(1)
2671        elif s == 'Off':
2672            tf.auto_scrollbar = 0
2673            tf.setscrollbar(0)
2674        else:
2675            assert 0
2676
2677    def setusergeometry(self):
2678        # Make the geometry of the window be user-specified
2679        # This is called after Tk has determined the size
2680        # of the window needed for the initial widget configuration.
2681        # The size is not to be changed after that, other than
2682        # on user request.
2683        # I couldn't just do frame.geometry(frame.geometry()) because,
2684        # presumably, of a bug in the Tk and/or wm I am using. I hope
2685        # this works for all systems .. Notes  26 Oct 2005.
2686
2687        self.frame.update()
2688        g = '%dx%d+%d+%d' % (
2689            self.frame.winfo_width(),
2690            self.frame.winfo_height(),
2691            self.frame.winfo_rootx(),
2692            self.frame.winfo_rooty())
2693        self.frame.geometry(g)
2694
2695    def modechooser(self, frame, name, choices, cmdvar, command):
2696
2697        button = Menubutton(frame, text=name)
2698        menu = Menu(button)
2699        button['menu'] = menu
2700
2701        self.addmodechooser(menu, choices, cmdvar, command)
2702        return button
2703
2704    def addmodechooser(self, menu, choices, cmdvar, command):
2705
2706        def setcmdvar():
2707            cmdvar.set(' '.join([v.get() for v in vars]))
2708
2709        def cmd():
2710            setcmdvar()
2711            command()
2712
2713        vars = []
2714        for ch in choices:
2715            var = StringVar()
2716            vars.append(var)
2717            var.set(ch[0])
2718            for a in ch:
2719                menu.add_radiobutton(
2720                    command=cmd,
2721                    label=a,
2722                    value=a,
2723                    variable=var,
2724                    #font=('Courier','12', 'bold'),
2725                    #font=('Helvetica','12', 'bold'),
2726                    columnbreak=(a == ch[0])
2727                )
2728
2729        setcmdvar()
2730
2731    def grid_things(self):
2732        ow = self.frame.winfo_width()
2733        oh = self.frame.winfo_height()
2734
2735        self.ctrlframe.grid_forget()
2736        self.display.frame.grid_forget()
2737        self.d_t.frame.grid_forget()
2738        self.tf.frame.grid_forget()
2739        self.disptab.grid_forget()
2740        self.filler.frame.grid_forget()
2741
2742        self.gridmain()
2743
2744        self.frame.update_idletasks()
2745        self.sizewidgets()
2746
2747    def gridmain(self):
2748
2749        row = 1
2750
2751        c = self.var_showcontrol.get()
2752        if c:
2753            self.ctrlframe.grid(row=row, column=0,
2754                                columnspan=3, padx=3, pady=3, sticky=W)
2755            row += 1
2756
2757        column = 0
2758
2759        g = self.var_showgraph.get()
2760        t = self.var_showtable.get()
2761        gt = (g, t)
2762        if g:
2763            self.display.frame.grid(row=0, column=column, sticky=N+W,
2764                                    padx=3, pady=3
2765                                    )
2766            column += 1
2767
2768        if g and t:
2769            self.d_t.frame.grid(row=0, column=column, sticky=N+W)
2770            column += 1
2771        if t:
2772            self.tf.frame.grid(row=0, column=column, sticky=N+W, padx=3, pady=3
2773                               )
2774        if g or t:
2775            self.disptab.grid(row=row, column=0,
2776                              sticky=N+W,
2777                              # padx=3,pady=3,
2778                              )
2779            row += 1
2780        self.filler.setsize(0, 0)
2781        self.filler.frame.grid(row=row, column=3, sticky=N+W)
2782
2783        self.frame.resizable(1, 1)
2784
2785    def event_configure(self, event):
2786        if event.widget is not self.frame:
2787            return
2788
2789        if not self.inited:
2790            return
2791
2792        if self.in_configure:
2793            return
2794
2795        curw = self.frame.winfo_width()
2796        curh = self.frame.winfo_height()
2797        if curw == self.lastw and curh == self.lasth:
2798            return
2799
2800        self.in_configure += 1
2801
2802        self.lastw = curw
2803        self.lasth = curh
2804
2805        self.sizewidgets()
2806
2807        self.in_configure -= 1
2808
2809    def sizewidgets(self):
2810        self.frame.update()
2811        curw = self.frame.winfo_width()
2812        curh = self.frame.winfo_height()
2813
2814        mbx = self.menubar.winfo_rootx()
2815        mby = self.menubar.winfo_rooty()
2816
2817        sfs = []
2818        if self.var_showgraph.get():
2819            sfs.append(self.display)
2820        if self.var_showtable.get():
2821            sfs.append(self.tf)
2822
2823        if not sfs:
2824            sfs.append(self.filler)
2825
2826        dys = {}
2827        didh = 0
2828        for sf in sfs:
2829            f = sf.frame
2830            diy = f.winfo_rooty()
2831            dih = f.winfo_height()
2832
2833            ch = diy - mby + dih
2834            dy = curh - ch - 7
2835            didh = didh or dy
2836            dys[sf] = dy
2837
2838        if self.var_showtable.get():
2839            f = self.tf.frame
2840        elif self.var_showgraph.get():
2841            f = self.display.frame
2842        else:
2843            f = self.filler.frame
2844
2845        fx = f.winfo_rootx()
2846        fw = f.winfo_width()
2847
2848        cw = fx - mbx + fw
2849
2850        fdw = curw - cw - 6
2851
2852        if f is self.filler.frame and not self.var_showcontrol.get():
2853            fdw = curw - self.filler.getsize()[0] - 3
2854
2855        if didh or fdw:
2856            if self.var_showgraph.get() and self.var_showtable.get():
2857                dprop = float(self.display.frame.winfo_width())
2858                dprop = dprop / (dprop + self.tf.frame.winfo_width())
2859                dx, dy = self.display.resize(fdw * dprop, dys[self.display])
2860                self.tf.resize(fdw - dx, dys[self.tf])
2861                self.frame.update_idletasks()
2862                self.d_t.setheight(max(self.display.frame.winfo_height(),
2863                                       self.tf.frame.winfo_height()))
2864
2865            elif self.var_showgraph.get():
2866                self.display.resize(fdw, dys[self.display])
2867            elif self.var_showtable.get():
2868                self.tf.resize(fdw, dys[self.tf])
2869            else:
2870                self.filler.resize(fdw, dys[self.filler])
2871                self.filler.setsize(self.filler.getsize()[0], 1000)
2872            if self.var_showgraph.get():
2873                self.display.moveback()
2874
2875        #self.resize(dw, dh)
2876
2877    def resize(self, dw, dh):
2878        self.display.resize(dw, dh)
2879        # self.frame.wm_geometry('')
2880
2881    def event_map(self, event):
2882        self.frame.unbind('<Map>')
2883        self.frame.bind('<Unmap>', self.event_unmap)
2884        self.frame.lift()
2885
2886    def event_unmap(self, event):
2887        self.frame.unbind('<Unmap>')
2888        self.frame.bind('<Map>', self.event_map)
2889
2890    def load_filename(self, filename):
2891        ocursor = self.frame.winfo_toplevel()['cursor']
2892        try:
2893            self.frame.winfo_toplevel()['cursor'] = 'watch'
2894            self.frame.update()
2895            if filename:
2896                filename = self.mod.path.abspath(filename)
2897            try:
2898                self.stats.open(filename)
2899            except Exception:
2900                __import__('traceback').print_exc()
2901                etype, value, tb = self.mod._root.sys.exc_info()
2902                tkinter.messagebox.showerror(
2903                    master=self.frame,
2904                    message=(
2905                        "Error when loading\n%r:\n" % filename +
2906                        "%s" % ''.join(self.mod._root.traceback.format_exception_only(
2907                            etype, value)))
2908                )
2909            else:
2910                self.display.load_stats(self.stats)
2911
2912                for c in self.mcontrols:
2913                    c.setnumsamples(len(self.stats))
2914                # self.scontrol.trackcommand(1)
2915                self.set_filename(filename)
2916
2917                self.xrange_fit()
2918                self.display.xview_moveto(0)
2919                self.mcontrols[1].settracking(0)
2920                self.mcontrols[0].settracking(1)
2921                self.yrange_fit()
2922                self.tf.update(force=1)
2923                if filename:
2924                    self.initialdir = self.mod.path.dirname(filename)
2925
2926        finally:
2927            self.frame.winfo_toplevel()['cursor'] = ocursor
2928
2929    def update_tableframe(self):
2930        self.tf.update()
2931
2932    def getkindcolor(self, kind):
2933        if kind == '<Other>':
2934            return 'black'
2935        else:
2936            return self.colors[abs(hash(kind)) % len(self.colors)]
2937
2938    def set_filename(self, filename):
2939        self.filename = filename
2940        if not filename:
2941            filename = '<No File>'
2942        title = 'Heapy Profile Browser: %s' % filename
2943        self.window.title(title)
2944
2945    def setnormpos(self):
2946        self.setscrollregion()
2947        if self.ymax >= self.yrange:
2948            self.yrange_fit()
2949        if self.xi0 is None:
2950            self.drawxaxis()
2951        else:
2952            self.updatexaxis()
2953
2954        self.track()
2955
2956    def redraw_all(self):
2957        pass
2958
2959    def trackoff(self):
2960        self.rcontrol.settracking(0)
2961
2962    def xrange_fit(self):
2963        self.xcontrol.fit(len(self.stats))
2964
2965    def yrange_fit(self):
2966        self.display.yrange_auto(force=1)
2967
2968
2969class _GLUECLAMP_:
2970    _imports_ = (
2971        '_parent:Use',
2972        '_parent:pbhelp',
2973        '_root.guppy.etc:textView',
2974        '_root:hashlib',
2975        '_root:os',
2976        '_root.os:path',
2977        '_root:time',
2978        '_root.guppy.gsl:Text',
2979    )
2980
2981    def pb(self, filename=None):
2982        """pb( [filename: profilefilename+])
2983
2984Create a Profile Browser window.
2985
2986Argument
2987    filename: profilefilename+
2988        The name of a file containing profile data.
2989See also
2990    Heapy Profile Browser[1]
2991    Screenshot[2]
2992References
2993    [0] heapy_Use.html#heapykinds.Use.pb
2994    [1] ProfileBrowser.html
2995    [2] pbscreen.jpg"""
2996
2997        pa = ProfileApp(self)
2998        pa.new_profile_browser(filename)
2999        pa.mainloop()
3000
3001    def tpg(self):
3002        self('/tmp/x.hpy')
3003