3

I am adding line number feature to the text widget by following Bryan Oakley's code here. I am inserting my own XML file in that text box. That file is listed below:

myFile.xml

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>

<p1:sample1 xmlns:p1="http://www.example.org/eHorizon">

   <p1:time nTimestamp="5">
      <p1:location hours = "1" path = '1'>
         <p1:feature color="6" type="a">560</p1:feature>
         <p1:feature color="2" type="a">564</p1:feature>
         <p1:feature color="3" type="b">570</p1:feature>
         <p1:feature color="4" type="c">570</p1:feature>
      </p1:location>
   </p1:time>

   <p1:time nTimestamp="6">
      <p1:location hours = "1" path = '1'>
         <p1:feature color="2" type="a">564</p1:feature>
         <p1:feature color="3" type="b">570</p1:feature>
         <p1:feature color="4" type="c">570</p1:feature>
      </p1:location>
   </p1:time>

</p1:sample1>

myCode.py

import Tkinter as tk

class TextLineNumbers(tk.Canvas):
    def __init__(self, *args, **kwargs):
        tk.Canvas.__init__(self, *args, **kwargs)
        self.textwidget = None

    def attach(self, text_widget):
        self.textwidget = text_widget

    def redraw(self, *args):
        '''redraw line numbers'''
        self.delete("all")

        i = self.textwidget.index("@0,0")
        while True :
            dline= self.textwidget.dlineinfo(i)
            if dline is None: break
            y = dline[1]
            linenum = str(i).split(".")[0]
            self.create_text(2,y,anchor="nw", text=linenum)
            i = self.textwidget.index("%s+1line" % i)

class CustomText(tk.Text):
    def __init__(self, *args, **kwargs):
        tk.Text.__init__(self, *args, **kwargs)

        self.tk.eval('''
            proc widget_proxy {widget widget_command args} {

                # call the real tk widget command with the real args
                set result [uplevel [linsert $args 0 $widget_command]]

                # generate the event for certain types of commands
                if {([lindex $args 0] in {insert replace delete}) ||
                    ([lrange $args 0 2] == {mark set insert}) || 
                    ([lrange $args 0 1] == {xview moveto}) ||
                    ([lrange $args 0 1] == {xview scroll}) ||
                    ([lrange $args 0 1] == {yview moveto}) ||
                    ([lrange $args 0 1] == {yview scroll})} {

                    event generate  $widget <<Change>> -when tail
                }

                # return the result from the real widget command
                return $result
            }
            ''')
        self.tk.eval('''
            rename {widget} _{widget}
            interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget}
        '''.format(widget=str(self)))

class Example(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)
        self.text = CustomText(self)
        self.vsb = tk.Scrollbar(orient="vertical", command=self.text.yview)
        self.text.configure(yscrollcommand=self.vsb.set)
        self.text.tag_configure("bigfont", font=("Helvetica", "24", "bold"))
        self.linenumbers = TextLineNumbers(self, width=30)
        self.linenumbers.attach(self.text)

        self.vsb.pack(side="right", fill="y")
        self.linenumbers.pack(side="left", fill="y")
        self.text.pack(side="right", fill="both", expand=True)

        self.text.bind("<<Change>>", self._on_change)
        self.text.bind("<Configure>", self._on_change)
        file = open('C:/your/file/location/myFile.xml')
        self.text.insert("end", file.read())


    def _on_change(self, event):
        self.linenumbers.redraw()

if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(side="top", fill="both", expand=True)
    root.mainloop()

What I want:

The result generated on running code looks like this:

enter image description here

I have edited this pic using ms-paint to show red markings. Numbering of lines in red is what I want. As it can be seen that numbering on canvas also includes space, I want to modify code in such a manner that there are no line numbers on empty or white spaces.

So there are two objectives I want to achieve:

1). Follow line numbering in a way numbers marked in red are given.

2). After this changes are made if possible I also want to extract the line number from canvas for any given string for that text widget.

What I tried

I am trying to compare two files and introduce empty lines on places where there are missing points. Before I proceed I need to implement an algorithm which can exclude empty white space from allotting line numbers (which happens with most of the editors). So far I am playing around but cannot find how can I implement this in code above.

Community
  • 1
  • 1
Radheya
  • 779
  • 1
  • 11
  • 41

1 Answers1

5

Hopefully, Bryan Oakley will see your question and post an eloquent solution. But in the mean time, this somewhat hacky approach works. :)

On each redraw, we grab the current text contents of the widget, and build a dictionary that uses the index of each non-blank line as the key in a dictionary that holds the actual line numbers we want.

def redraw(self, *args):
    '''redraw line numbers'''
    self.delete("all")

    # Build dict to convert line index to line number
    linenums = {}
    num = 1
    contents = self.textwidget.get("1.0", tk.END)
    for i, line in enumerate(contents.splitlines(), 1):
        i = str(i) + '.0'
        # Only increment line number if the line's not blank
        linetext = self.textwidget.get(i, "%s+1line" % i)
        if linetext.strip():
            linenums[i] = str(num)
            #print num, linetext,
            num += 1

    i = self.textwidget.index("@0,0")
    while True :
        dline = self.textwidget.dlineinfo(i)
        if dline is None: 
            break

        linenum = linenums.get(i)
        if linenum is not None:
            y = dline[1]
            self.create_text(2,y,anchor="nw", text=linenum)

        i = self.textwidget.index("%s+1line" % i)

If you un-comment the #print num, linetext, line it will print each non-blank line with its number on every redraw. You probably don't want that, but it should help you to figure out how to extract the line numbers for a given line.

PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
  • You don't need me. This is pretty much how I would have done it. Creating a dictionary to map actual to virtual line numbers seems like as good of a solution as anything else. – Bryan Oakley Nov 06 '15 at 15:22
  • Thanks, @BryanOakley! I appreciate your vote of confidence. – PM 2Ring Nov 06 '15 at 15:42
  • @PM2Ring This works just like i expected! Thanks a ton. – Radheya Nov 06 '15 at 15:46
  • regarding `print num, linetext`, i was willing to extract line numbers after the code is printed in text box. This step will be done when I will be doing comparison. But thanks for the hint. I am able to understand whats going on. – Radheya Nov 06 '15 at 15:50
  • @DhruvJ: My pleasure! FWIW, [Peter Varo](http://stackoverflow.com/users/2188562/peter-varo) just pointed out that `if linetext.strip() != '':` can be better written as `if linetext.strip():`, so I've modified the code slightly. – PM 2Ring Nov 06 '15 at 15:57
  • @PM2Ring okay got it! no problemo – Radheya Nov 07 '15 at 17:46