Implementing the Find Text feature

Next, let's code the Find Text feature (2.05.py). The following screenshot shows an example of the Find Text feature:

Here's a quick summary of the desired functionality. When a user clicks on the Find menu item, a new Toplevel window opens up. The user enters a search keyword and specifies whether the search needs to be case-sensitive. When the user clicks on the Find All button, all matches are highlighted.

To search through the document, we rely on the text_widget.search() method. The search method takes in the following arguments:

search(pattern, startindex, stopindex=None, forwards=None, backwards=None, exact=None, regexp=None, nocase=None, count=None)

For the editor, define a function called find_text and attach it as a callback to the Find menu (2.05.py):

edit_menu.add_command(label='Find',underline= 0, accelerator='Ctrl+F', command=find_text)

Also, bind it to the Ctrl + F shortcut, as follows:

content_text.bind('<Control-f>', find_text)
content_text.bind('<Control-F>', find_text)

Then, define the find_text function, as follows (2.05.py):

def find_text(event=None):
search_toplevel = Toplevel(root)
search_toplevel.title('Find Text')
search_toplevel.transient(root)
Label(search_toplevel, text="Find All:").grid(row=0,
column=0,sticky='e')
search_entry_widget = Entry(search_toplevel, width=25)
search_entry_widget.grid(row=0, column=1, padx=2, pady=2,
sticky='we')
search_entry_widget.focus_set()
ignore_case_value = IntVar()
.... more code here to crate checkbox and button
def close_search_window():
content_text.tag_remove('match', '1.0', END)
search_toplevel.destroy()
search_toplevel.protocol('WM_DELETE_WINDOW',
close_search_window)
return "break"

The following is a description of the preceding code (2.05.py):

  • When a user clicks on the Find menu item, it invokes a find_text callback.
  •  The first four lines of the find_text() function create a new Toplevel window, add a window title, specify its geometry (size, shape, and location), and set it as a transient window. Setting it as a transient window means that it is always drawn on top of its parent or root window. If you comment out this line and click on the root editor window, the Find window will go behind the root window.
  • The next eight lines of code are pretty self-explanatory; they set the widgets of the Find window. They add the Label, Entry, Button, and Checkbutton widgets, and set up the search_string and ignore_case_value variables to track the value a user enters into the Entry widget and whether the user has checked off Checkbutton. The widgets are arranged by using the grid geometry manager to fit into the Find window.
  • The Find All button has a command option that calls a search_output function, passing the search string as the first argument and whether the search needs to be case-sensitive as its second argument. The third, fourth, and fifth arguments pass the Toplevel window, the Text widget, and the Entry widget as parameters.
  • We override the Close button of the Find window and redirect it to a callback named close_search(). The close_search function is defined within the find_text function. This function takes care of removing the match tag that was added during the search. If we do not override the Close button and remove these tags, the matched string will continue to be marked in red and yellow even after the search has ended.

Next, we define the search_output function, which does the actual searching and adds the match tag to the matching text. The code for this is as follows:

def search_output(needle, if_ignore_case, content_text,
search_toplevel, search_box):
content_text.tag_remove('match', '1.0', END)
matches_found = 0
if needle:
start_pos = '1.0'
while True:
start_pos = content_text.search(needle, start_pos,
nocase=if_ignore_case, stopindex=END)
if not start_pos:
break
end_pos = '{}+{}c'.format(start_pos, len(needle))
content_text.tag_add('match', start_pos, end_pos)
matches_found += 1
start_pos = end_pos
content_text.tag_config('match', foreground='red', background='yellow')
search_box.focus_set()
search_toplevel.title('{} matches found'.format(matches_found))

The following is a description of the preceding code:

  • This part of the code is the heart of the search function. It searches through the entire document by using the while True loop, breaking out of the loop only if no more text items remain to be searched.
  • The code first removes the previous search-related match tags if there are any, as we do not want to append the results of the new search to the previous search results. The function uses the search() method, which is provided in Tkinter in the Text widget. The search() method takes the following arguments:
      search(pattern, index, stopindex=None, forwards=None,
backwards=None, exact=None, regexp=None, nocase=None, count=None)
  • The search() method returns the starting position of the first match. We store it in a variable named start_pos, calculate the position of the last character in the matched word, and store it in the end_pos variable.
  • For every search match that it finds, it adds the match tag to the text ranging from the first position to the last position. After every match, we set the value of start_pos to be equal to end_pos. This ensures that the next search starts after end_pos.
  • The loop also keeps track of the number of matches by using the count variable.
  • Outside the loop, the tag match is configured to have a red font and yellow background. The last line of this function updates the title of the Find window with the number of matches that were found.
In case of event bindings, interaction occurs between input devices (keyboard/mouse) and your application. In addition to event binding, Tkinter also supports protocol handling.
The term protocol refers to the interaction between your application and the window manager. An example of a protocol is WM_DELETE_WINDOW, which handles the close window event for your window manager.
Tkinter lets you override these protocol handlers by mentioning your own handler for the root or Toplevel widget. To override the window exit protocol, we use the following command:
root.protocol(WM_DELETE_WINDOW, callback)
Once you add this command, Tkinter reroutes protocol handling to the specified callback/handler.
..................Content has been hidden....................

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