Tkinter Text widget getting too slow

Michael Peuser mpeuser at web.de
Fri Sep 5 15:50:16 EDT 2003


"Christos TZOTZIOY Georgiou" <tzot at sil-tec.gr> schrieb im Newsbeitrag
news:eoselvguf3hvie3s95kjndhtuno0jiu09s at 4ax.com...
> On Thu, 4 Sep 2003 03:15:16 +0900, rumours say that "Changjune Kim"
> <juneaftn at REMOVETHIShanmail.net> might have written:
>
> >(I'm interested to see his "extremely fast" text widget, too)
>
> Notice that Michael Peuser described an "extremely fast" "binary
> editor"; I assume a dual view (hex / chars) one.  This is not a text
> widget --you don't have to account for line feeds and variable width
> characters :)

Well I have somthing a little bit over 100 lines; I removed the editor part
but that is straight forward.
No scaling to size up to 1 GB.
-----------------------------------
# wckhwex by Michael Peuser May-2003
# Using some ideas from F. Lundh (WCK)
# This is Open Source. I will be happy if it could help somebody.

versionString="wckhex 0.3 by mpeuser.de"
from __future__ import division

from Tkinter import *
from WCK import Widget, EventMixin
from tkFileDialog import askopenfile
import mmap

class HexView(EventMixin, Widget):
    """Displays HUGE binary file using memory mapped files
    """

    aCols=10     # address field
    bCols=2.5*16+1 # binary  field
    cCols=16+1   # ascii field

    ui_takefocus=1
    ui_doublebuffer=0

    def __init__(self,root, buffer=None):
        self.buffer=buffer
        self.defLines=(len(buffer)+15)//16
        self.firstLine=0            # numer of first line in window
        self.oldFirst=-1            # .. compared to last time
        self.visLines=1             # visible lines in window
        self.pix=None               # pixmap will be created on resize

        self.ui_init(root)
        self.vscroll=Scrolls(self,orient=VERTICAL)
        self.vscroll.pack(side=RIGHT,fill=Y)

        self.pack(side=TOP,fill=BOTH,expand=1)
        self.font=self.ui_font(0,"Courier 10")
        (self.cWidth, self.lHeight) = self.font.measure("X")

    def ui_handle_resize(self, width, height):

        self.height=height
        self.width= max(width,
                    (self.aCols+1+self.bCols+1+self.cCols)*self.cWidth) #
pixel
        self.visLines=self.height//self.lHeight
        self.pix=self.ui_pixmap(self.width, self.height)
        self.vscroll.doScroll() # compute new thumb
        self.drawPix()

    def ui_handle_clear(*whatever):
        pass                    # no need for clear as is pixmap long enough

    def ui_handle_repair(self, draw, x0, y0, x1, y1):
        #if self.pix:
            draw.paste(self.pix)

    def onclick(self, ev):
        self.focus_set()
        if ev.num==1:
            pass        #XXX implement editor here
        if ev.num==3:
            pop=Menu(self,tearoff=0);
            pop.add_command(label=versionString,state=DISABLED)
            pop.post(ev.x+self.winfo_rootx(),ev.y+self.winfo_rooty())

    def onkey(self, event):
        todo ={'Down': ("scroll",1,"lines"),  'Up':   ('scroll',-1,'lines'),
               'Next': ("scroll",1,"pages"),  'Prior':('scroll',-1,'pages')}
        x=todo.get(event.keysym)
        print x
        if x: self.vscroll.doScroll(*x)

    def bgText(self,x,y,w,h,text,theBrush):
        "Displays centered text with background color"
        self.pix.rectangle((x+1,y+1,x+w-2,y+h-2),theBrush)
        (tw,th)=self.pix.textsize(text,self.font)
        self.pix.text((x+(w-tw)/2,y+(h-th)/2),text,self.font)

    def drawPix(self,unchangedSize=0):
        "Central dawing routine"
        c=self.pix
        repair=None
        whiteBrush=self.ui_brush(0xffffff)
        yellowBrush=self.ui_brush(0xffff99)
        grayPen=self.ui_pen(0x999999,2)
        ww,wh = self.ui_size()
        h=self.lHeight
        posX = 0

# speed up scrolling by image blitting, but unclean parametrisation of paste
        if unchangedSize:                         ## check if blitting
useful
            linesTBS = self.oldFirst-self.firstLine  ## TBS = to be scrolled
            if 0 < linesTBS < self.visLines/2:    ## down, repair top
                c.paste(c,(0, h*linesTBS))
                theRange=range(1,linesTBS+1)
                c.rectangle((0,h,ww,(linesTBS+1)*h),whiteBrush)
                c.rectangle((0,h,self.aCols*self.cWidth,(linesTBS+1)*h),
yellowBrush)
            elif 0 < -linesTBS < self.visLines/2: ## up, repair bottom
                c.paste(c,(0, h*linesTBS))
                delta=self.visLines+linesTBS
                theRange=range(delta-1,self.visLines)
                c.rectangle((0, delta*h, ww, wh),whiteBrush)
                c.rectangle((0,delta*h, self.aCols*self.cWidth, wh),
yellowBrush)
            else:
                unchangedSize=0 # blitting would not make much
improvement...
        self.oldFirst=self.firstLine              # remember first line for
next time
# end of blitting optimization

        if not unchangedSize:                         # all new
            c.rectangle((0,0,ww,wh),whiteBrush)
            theRange=range(1, self.visLines)
            c.rectangle((0,h,self.aCols*self.cWidth,wh), yellowBrush)

        for w, text in \

((self.aCols,"address"),(self.bCols,"hex"),(self.cCols,"ascii")):
            w *= self.cWidth
            if repair!='T':
                self.bgText(posX, 0, w, h, text, yellowBrush)
            c.line((posX - 1, 0, posX - 1, wh -1), grayPen)
            posX+=w

        # addresses and data
        posY = self.lHeight
        for y in theRange:
            posX = 0
            heightY = self.lHeight
            address=(y-1+self.firstLine)*16
            vals = self.buffer[address:address+16]
            c.text((posX,y*self.lHeight),
                   "%3x.%05x " % divmod(address, 1024*1024), self.font)
            posX += self.aCols*self.cWidth

            for vala in range(address,address+16):
                if vala %8==0: posX+=self.cWidth//2   # some in between
space
                if vala<len(buffer):
                    c.text((posX,
y*heightY),"%02x"%ord(buffer[vala]),self.font)
                posX += 2.5*self.cWidth

            c.text((posX+10,y*heightY),buffer[address:address+16],self.font)
        self.ui_damage()
        self.focus_set()

class Scrolls(Scrollbar):
    "wrapper around Tk scrolbars"
    def __init__(self,frame,orient=None,**kw):
        Scrollbar.__init__(self,frame,orient=orient,command=self.doScroll)
        self.visible=1 # start packed
        self.theMaster=frame

    def doScroll(self,a=None,b=None,c=None):
        m=self.theMaster
        oldfirst=m.firstLine
        if a=="scroll":
            if c=='pages': b=int(b)*m.visLines-1
            m.firstLine = oldfirst+int(b)
        elif a=="moveto":
            m.firstLine=int(float(b)*(m.defLines+1))
        if m.firstLine+m.visLines>m.defLines: # the end
            m.firstLine=m.defLines-m.visLines+1
        if m.firstLine<0:
            m.firstLine=0
        size = m.visLines/m.defLines  # 0...1
        start= m.firstLine/m.defLines # 0...1
        if self.visible: self.set(start,start+size)
        if oldfirst != m.firstLine: m.drawPix(unchangedSize=1)

if __name__ == "__main__":
    root=Tk()
    root.iconify()
    fbb=askopenfile("r+");      assert fbb, "No file"
    buffer=mmap.mmap(fbb.fileno(),0)

    s=HexView(root, buffer=buffer)

    root.title("%s (%6.3f MB)" %(fbb.name, len(buffer)/1024/1024))
    root.geometry("720x550+10+10")
    root.deiconify()
    root.mainloop()
    fbb.close()
# the End








More information about the Python-list mailing list