17db96d56Sopenharmony_ci"""Search dialog for Find, Find Again, and Find Selection
27db96d56Sopenharmony_ci   functionality.
37db96d56Sopenharmony_ci
47db96d56Sopenharmony_ci   Inherits from SearchDialogBase for GUI and uses searchengine
57db96d56Sopenharmony_ci   to prepare search pattern.
67db96d56Sopenharmony_ci"""
77db96d56Sopenharmony_cifrom tkinter import TclError
87db96d56Sopenharmony_ci
97db96d56Sopenharmony_cifrom idlelib import searchengine
107db96d56Sopenharmony_cifrom idlelib.searchbase import SearchDialogBase
117db96d56Sopenharmony_ci
127db96d56Sopenharmony_cidef _setup(text):
137db96d56Sopenharmony_ci    """Return the new or existing singleton SearchDialog instance.
147db96d56Sopenharmony_ci
157db96d56Sopenharmony_ci    The singleton dialog saves user entries and preferences
167db96d56Sopenharmony_ci    across instances.
177db96d56Sopenharmony_ci
187db96d56Sopenharmony_ci    Args:
197db96d56Sopenharmony_ci        text: Text widget containing the text to be searched.
207db96d56Sopenharmony_ci    """
217db96d56Sopenharmony_ci    root = text._root()
227db96d56Sopenharmony_ci    engine = searchengine.get(root)
237db96d56Sopenharmony_ci    if not hasattr(engine, "_searchdialog"):
247db96d56Sopenharmony_ci        engine._searchdialog = SearchDialog(root, engine)
257db96d56Sopenharmony_ci    return engine._searchdialog
267db96d56Sopenharmony_ci
277db96d56Sopenharmony_cidef find(text):
287db96d56Sopenharmony_ci    """Open the search dialog.
297db96d56Sopenharmony_ci
307db96d56Sopenharmony_ci    Module-level function to access the singleton SearchDialog
317db96d56Sopenharmony_ci    instance and open the dialog.  If text is selected, it is
327db96d56Sopenharmony_ci    used as the search phrase; otherwise, the previous entry
337db96d56Sopenharmony_ci    is used.  No search is done with this command.
347db96d56Sopenharmony_ci    """
357db96d56Sopenharmony_ci    pat = text.get("sel.first", "sel.last")
367db96d56Sopenharmony_ci    return _setup(text).open(text, pat)  # Open is inherited from SDBase.
377db96d56Sopenharmony_ci
387db96d56Sopenharmony_cidef find_again(text):
397db96d56Sopenharmony_ci    """Repeat the search for the last pattern and preferences.
407db96d56Sopenharmony_ci
417db96d56Sopenharmony_ci    Module-level function to access the singleton SearchDialog
427db96d56Sopenharmony_ci    instance to search again using the user entries and preferences
437db96d56Sopenharmony_ci    from the last dialog.  If there was no prior search, open the
447db96d56Sopenharmony_ci    search dialog; otherwise, perform the search without showing the
457db96d56Sopenharmony_ci    dialog.
467db96d56Sopenharmony_ci    """
477db96d56Sopenharmony_ci    return _setup(text).find_again(text)
487db96d56Sopenharmony_ci
497db96d56Sopenharmony_cidef find_selection(text):
507db96d56Sopenharmony_ci    """Search for the selected pattern in the text.
517db96d56Sopenharmony_ci
527db96d56Sopenharmony_ci    Module-level function to access the singleton SearchDialog
537db96d56Sopenharmony_ci    instance to search using the selected text.  With a text
547db96d56Sopenharmony_ci    selection, perform the search without displaying the dialog.
557db96d56Sopenharmony_ci    Without a selection, use the prior entry as the search phrase
567db96d56Sopenharmony_ci    and don't display the dialog.  If there has been no prior
577db96d56Sopenharmony_ci    search, open the search dialog.
587db96d56Sopenharmony_ci    """
597db96d56Sopenharmony_ci    return _setup(text).find_selection(text)
607db96d56Sopenharmony_ci
617db96d56Sopenharmony_ci
627db96d56Sopenharmony_ciclass SearchDialog(SearchDialogBase):
637db96d56Sopenharmony_ci    "Dialog for finding a pattern in text."
647db96d56Sopenharmony_ci
657db96d56Sopenharmony_ci    def create_widgets(self):
667db96d56Sopenharmony_ci        "Create the base search dialog and add a button for Find Next."
677db96d56Sopenharmony_ci        SearchDialogBase.create_widgets(self)
687db96d56Sopenharmony_ci        # TODO - why is this here and not in a create_command_buttons?
697db96d56Sopenharmony_ci        self.make_button("Find Next", self.default_command, isdef=True)
707db96d56Sopenharmony_ci
717db96d56Sopenharmony_ci    def default_command(self, event=None):
727db96d56Sopenharmony_ci        "Handle the Find Next button as the default command."
737db96d56Sopenharmony_ci        if not self.engine.getprog():
747db96d56Sopenharmony_ci            return
757db96d56Sopenharmony_ci        self.find_again(self.text)
767db96d56Sopenharmony_ci
777db96d56Sopenharmony_ci    def find_again(self, text):
787db96d56Sopenharmony_ci        """Repeat the last search.
797db96d56Sopenharmony_ci
807db96d56Sopenharmony_ci        If no search was previously run, open a new search dialog.  In
817db96d56Sopenharmony_ci        this case, no search is done.
827db96d56Sopenharmony_ci
837db96d56Sopenharmony_ci        If a search was previously run, the search dialog won't be
847db96d56Sopenharmony_ci        shown and the options from the previous search (including the
857db96d56Sopenharmony_ci        search pattern) will be used to find the next occurrence
867db96d56Sopenharmony_ci        of the pattern.  Next is relative based on direction.
877db96d56Sopenharmony_ci
887db96d56Sopenharmony_ci        Position the window to display the located occurrence in the
897db96d56Sopenharmony_ci        text.
907db96d56Sopenharmony_ci
917db96d56Sopenharmony_ci        Return True if the search was successful and False otherwise.
927db96d56Sopenharmony_ci        """
937db96d56Sopenharmony_ci        if not self.engine.getpat():
947db96d56Sopenharmony_ci            self.open(text)
957db96d56Sopenharmony_ci            return False
967db96d56Sopenharmony_ci        if not self.engine.getprog():
977db96d56Sopenharmony_ci            return False
987db96d56Sopenharmony_ci        res = self.engine.search_text(text)
997db96d56Sopenharmony_ci        if res:
1007db96d56Sopenharmony_ci            line, m = res
1017db96d56Sopenharmony_ci            i, j = m.span()
1027db96d56Sopenharmony_ci            first = "%d.%d" % (line, i)
1037db96d56Sopenharmony_ci            last = "%d.%d" % (line, j)
1047db96d56Sopenharmony_ci            try:
1057db96d56Sopenharmony_ci                selfirst = text.index("sel.first")
1067db96d56Sopenharmony_ci                sellast = text.index("sel.last")
1077db96d56Sopenharmony_ci                if selfirst == first and sellast == last:
1087db96d56Sopenharmony_ci                    self.bell()
1097db96d56Sopenharmony_ci                    return False
1107db96d56Sopenharmony_ci            except TclError:
1117db96d56Sopenharmony_ci                pass
1127db96d56Sopenharmony_ci            text.tag_remove("sel", "1.0", "end")
1137db96d56Sopenharmony_ci            text.tag_add("sel", first, last)
1147db96d56Sopenharmony_ci            text.mark_set("insert", self.engine.isback() and first or last)
1157db96d56Sopenharmony_ci            text.see("insert")
1167db96d56Sopenharmony_ci            return True
1177db96d56Sopenharmony_ci        else:
1187db96d56Sopenharmony_ci            self.bell()
1197db96d56Sopenharmony_ci            return False
1207db96d56Sopenharmony_ci
1217db96d56Sopenharmony_ci    def find_selection(self, text):
1227db96d56Sopenharmony_ci        """Search for selected text with previous dialog preferences.
1237db96d56Sopenharmony_ci
1247db96d56Sopenharmony_ci        Instead of using the same pattern for searching (as Find
1257db96d56Sopenharmony_ci        Again does), this first resets the pattern to the currently
1267db96d56Sopenharmony_ci        selected text.  If the selected text isn't changed, then use
1277db96d56Sopenharmony_ci        the prior search phrase.
1287db96d56Sopenharmony_ci        """
1297db96d56Sopenharmony_ci        pat = text.get("sel.first", "sel.last")
1307db96d56Sopenharmony_ci        if pat:
1317db96d56Sopenharmony_ci            self.engine.setcookedpat(pat)
1327db96d56Sopenharmony_ci        return self.find_again(text)
1337db96d56Sopenharmony_ci
1347db96d56Sopenharmony_ci
1357db96d56Sopenharmony_cidef _search_dialog(parent):  # htest #
1367db96d56Sopenharmony_ci    "Display search test box."
1377db96d56Sopenharmony_ci    from tkinter import Toplevel, Text
1387db96d56Sopenharmony_ci    from tkinter.ttk import Frame, Button
1397db96d56Sopenharmony_ci
1407db96d56Sopenharmony_ci    top = Toplevel(parent)
1417db96d56Sopenharmony_ci    top.title("Test SearchDialog")
1427db96d56Sopenharmony_ci    x, y = map(int, parent.geometry().split('+')[1:])
1437db96d56Sopenharmony_ci    top.geometry("+%d+%d" % (x, y + 175))
1447db96d56Sopenharmony_ci
1457db96d56Sopenharmony_ci    frame = Frame(top)
1467db96d56Sopenharmony_ci    frame.pack()
1477db96d56Sopenharmony_ci    text = Text(frame, inactiveselectbackground='gray')
1487db96d56Sopenharmony_ci    text.pack()
1497db96d56Sopenharmony_ci    text.insert("insert","This is a sample string.\n"*5)
1507db96d56Sopenharmony_ci
1517db96d56Sopenharmony_ci    def show_find():
1527db96d56Sopenharmony_ci        text.tag_add('sel', '1.0', 'end')
1537db96d56Sopenharmony_ci        _setup(text).open(text)
1547db96d56Sopenharmony_ci        text.tag_remove('sel', '1.0', 'end')
1557db96d56Sopenharmony_ci
1567db96d56Sopenharmony_ci    button = Button(frame, text="Search (selection ignored)", command=show_find)
1577db96d56Sopenharmony_ci    button.pack()
1587db96d56Sopenharmony_ci
1597db96d56Sopenharmony_ciif __name__ == '__main__':
1607db96d56Sopenharmony_ci    from unittest import main
1617db96d56Sopenharmony_ci    main('idlelib.idle_test.test_search', verbosity=2, exit=False)
1627db96d56Sopenharmony_ci
1637db96d56Sopenharmony_ci    from idlelib.idle_test.htest import run
1647db96d56Sopenharmony_ci    run(_search_dialog)
165