Displaying line numbers

Let's work toward showing line numbers to the left of the Text widget. This will require us to tweak the code in various places. So, before we start coding, let's look at what we are trying to achieve.

The View menu has a menu item that allows users to choose whether to Show Line Number. We only want to show line numbers if the option is selected, as shown in the following screenshot:

If the option is selected, we need to display line numbers in the left frame that we created earlier.

The line number should update every time a user enters a new line, deletes a line, cuts or pastes line text, performs an undo or a redo operation, opens an existing file, or clicks on the New menu item. In short, the line number should be updated after every activity results in a change of content. Therefore, we need to define a function called on_content_changed(). This function should be called after the definitions of every press, cut, paste, undo, redo, new, and open key, to check whether lines have been added or removed from the text area and accordingly update the line numbers.

We achieve this by using the following two strategies (refer to 2.10.py in the code bundle):

def on_content_changed(event=None):
update_line_numbers()

Bind a key press event to the update_line_number() function, as follows:

content_text.bind('<Any-KeyPress>', on_content_changed) 

Next, add a call to the on_content_changed() function in each of the definitions of cut, paste, undo, redo, new, and open.

Then define a get_line_numbers() function that returns a string containing all the numbers until the last row, separated by line breaks.

So for instance, if the last non-empty row in the content widget is 5, this function returns us a string of the 1 /n 2 /n 3 /n 4/n 5 /n form.

The following is the function definition:

def get_line_numbers():
output = ''
if show_line_number.get():
row, col = content_text.index("end").split('.')
for i in range(1, int(row)):
output += str(i)+ ' '
return output

Now, let's define the update_line_numbers() function, which simply updates the text widget that displays the line using the string output from the previous function:

def update_line_numbers(event = None):
line_numbers = get_line_numbers()
line_number_bar.config(state='normal')
line_number_bar.delete('1.0', 'end')
line_number_bar.insert('1.0', line_numbers)
line_number_bar.config(state='disabled')

The following is a description of the preceding code:

  • You may recall that we assigned a show_line_number variable to the menu item earlier:
    show_line_number = IntVar()
    show_line_number.set(1)
    view_menu.add_checkbutton(label="Show Line Number", variable=show_line_number)
  • If the show_line_number option is set to 1 (that is to say, it has been checked off in the menu item), we calculate the last line and last column in the text.
  • We then create a text string consisting of numbers from 1 to the number of the last line, with each number separated by a line break ( ). This string is then added to the left label by using the line_number_bar.config() method.
  • If Show Line Number is unchecked in the menu, the variable text remains blank, thereby displaying no line numbers.
  • Finally, we update each of the previously defined cut, paste, undo, redonew, and open functions to invoke the on_content_changed() function at its end.
We have finished adding the line number functionality to the text editor. However, I would like to add that this implementation, though simple, has some limitations in that it does not handle word wrapping and font size variability very well. A foolproof line numbering solution would require the use of the Canvas widget – something that we discuss Chapter 4 Game of Chess and onward. Meanwhile, if you are curious, take a look at a sample Canvas-based implementation at https://stackoverflow.com/a/16375233/2348704.

Lastly, in this iteration, we will implement a feature where a user can choose to highlight the current line (2.10.py).

The idea is simple. We need to locate the line of the cursor and add a tag to the line. We also need to configure the tag so that it appears with a differently colored background to highlight it.

You may recall that we have already provided a menu choice to users to decide whether to highlight the current line. We will now add a callback command from this menu item to a function that we will define as toggle_highlight:

to_highlight_line = BooleanVar()
view_menu.add_checkbutton(label='Highlight Current Line', onvalue=1, offvalue=0, variable=to_highlight_line, command=toggle_highlight)

Now, we define three functions to handle this for us:

def highlight_line(interval=100):
content_text.tag_remove("active_line", 1.0, "end")
content_text.tag_add("active_line",
"insert linestart", "insert lineend+1c")
content_text.after(interval, toggle_highlight)

def undo_highlight():
content_text.tag_remove("active_line", 1.0, "end")

def toggle_highlight(event=None):
if to_highlight_line.get():
highlight_line()
else:
undo_highlight()

The following is a description of the preceding code:

  • Every time a user checks/unchecks View | Highlight Current Line, this invokes the toggle_highlight function. This function checks whether the menu item is checked. If it is checked, it invokes the highlight_line function. Otherwise, if the menu item is unchecked, it invokes the undo_highlight function.
  • The highlight_line function simply adds a tag called active_line to the current line, and after every 100 milliseconds it calls the toggle_highlight function to check whether the current line should still be highlighted.
  • The undo_highlight function is invoked when the user unchecks highlighting in the View menu. Once invoked, it simply removes the active_line tag from the entire text area. 

Finally, we can configure the tag named active_line so that it is displayed with a different background color, as follows:

content_text.tag_configure('active_line', background='ivory2')
We used the .widget.after(ms, callback) handler in the code.
Methods that let us perform some periodic actions are called alarm handlers. The following are some commonly used Tkinter alarm handlers:
after(delay_ms, callback, args...): This registers a callback alarm, which can be called after a given number of milliseconds.
after_cancel(id): This cancels the given callback alarm.
after_idle(callback, args...): This calls back only when there are no more events to process in mainloop; that is, after the system becomes idle.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.72.15