• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Search dialog for Find, Find Again, and Find Selection
2   functionality.
3
4   Inherits from SearchDialogBase for GUI and uses searchengine
5   to prepare search pattern.
6"""
7from tkinter import TclError
8
9from idlelib import searchengine
10from idlelib.searchbase import SearchDialogBase
11
12def _setup(text):
13    """Return the new or existing singleton SearchDialog instance.
14
15    The singleton dialog saves user entries and preferences
16    across instances.
17
18    Args:
19        text: Text widget containing the text to be searched.
20    """
21    root = text._root()
22    engine = searchengine.get(root)
23    if not hasattr(engine, "_searchdialog"):
24        engine._searchdialog = SearchDialog(root, engine)
25    return engine._searchdialog
26
27def find(text):
28    """Open the search dialog.
29
30    Module-level function to access the singleton SearchDialog
31    instance and open the dialog.  If text is selected, it is
32    used as the search phrase; otherwise, the previous entry
33    is used.  No search is done with this command.
34    """
35    pat = text.get("sel.first", "sel.last")
36    return _setup(text).open(text, pat)  # Open is inherited from SDBase.
37
38def find_again(text):
39    """Repeat the search for the last pattern and preferences.
40
41    Module-level function to access the singleton SearchDialog
42    instance to search again using the user entries and preferences
43    from the last dialog.  If there was no prior search, open the
44    search dialog; otherwise, perform the search without showing the
45    dialog.
46    """
47    return _setup(text).find_again(text)
48
49def find_selection(text):
50    """Search for the selected pattern in the text.
51
52    Module-level function to access the singleton SearchDialog
53    instance to search using the selected text.  With a text
54    selection, perform the search without displaying the dialog.
55    Without a selection, use the prior entry as the search phrase
56    and don't display the dialog.  If there has been no prior
57    search, open the search dialog.
58    """
59    return _setup(text).find_selection(text)
60
61
62class SearchDialog(SearchDialogBase):
63    "Dialog for finding a pattern in text."
64
65    def create_widgets(self):
66        "Create the base search dialog and add a button for Find Next."
67        SearchDialogBase.create_widgets(self)
68        # TODO - why is this here and not in a create_command_buttons?
69        self.make_button("Find Next", self.default_command, isdef=True)
70
71    def default_command(self, event=None):
72        "Handle the Find Next button as the default command."
73        if not self.engine.getprog():
74            return
75        self.find_again(self.text)
76
77    def find_again(self, text):
78        """Repeat the last search.
79
80        If no search was previously run, open a new search dialog.  In
81        this case, no search is done.
82
83        If a search was previously run, the search dialog won't be
84        shown and the options from the previous search (including the
85        search pattern) will be used to find the next occurrence
86        of the pattern.  Next is relative based on direction.
87
88        Position the window to display the located occurrence in the
89        text.
90
91        Return True if the search was successful and False otherwise.
92        """
93        if not self.engine.getpat():
94            self.open(text)
95            return False
96        if not self.engine.getprog():
97            return False
98        res = self.engine.search_text(text)
99        if res:
100            line, m = res
101            i, j = m.span()
102            first = "%d.%d" % (line, i)
103            last = "%d.%d" % (line, j)
104            try:
105                selfirst = text.index("sel.first")
106                sellast = text.index("sel.last")
107                if selfirst == first and sellast == last:
108                    self.bell()
109                    return False
110            except TclError:
111                pass
112            text.tag_remove("sel", "1.0", "end")
113            text.tag_add("sel", first, last)
114            text.mark_set("insert", self.engine.isback() and first or last)
115            text.see("insert")
116            return True
117        else:
118            self.bell()
119            return False
120
121    def find_selection(self, text):
122        """Search for selected text with previous dialog preferences.
123
124        Instead of using the same pattern for searching (as Find
125        Again does), this first resets the pattern to the currently
126        selected text.  If the selected text isn't changed, then use
127        the prior search phrase.
128        """
129        pat = text.get("sel.first", "sel.last")
130        if pat:
131            self.engine.setcookedpat(pat)
132        return self.find_again(text)
133
134
135def _search_dialog(parent):  # htest #
136    "Display search test box."
137    from tkinter import Toplevel, Text
138    from tkinter.ttk import Frame, Button
139
140    top = Toplevel(parent)
141    top.title("Test SearchDialog")
142    x, y = map(int, parent.geometry().split('+')[1:])
143    top.geometry("+%d+%d" % (x, y + 175))
144
145    frame = Frame(top)
146    frame.pack()
147    text = Text(frame, inactiveselectbackground='gray')
148    text.pack()
149    text.insert("insert","This is a sample string.\n"*5)
150
151    def show_find():
152        text.tag_add('sel', '1.0', 'end')
153        _setup(text).open(text)
154        text.tag_remove('sel', '1.0', 'end')
155
156    button = Button(frame, text="Search (selection ignored)", command=show_find)
157    button.pack()
158
159if __name__ == '__main__':
160    from unittest import main
161    main('idlelib.idle_test.test_search', verbosity=2, exit=False)
162
163    from idlelib.idle_test.htest import run
164    run(_search_dialog)
165