Cheryl Sabella | 0bb5e75 | 2019-03-16 19:29:33 -0400 | [diff] [blame] | 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 | """ |
Terry Jan Reedy | 6f7b0f5 | 2016-07-10 20:21:31 -0400 | [diff] [blame] | 7 | from tkinter import TclError |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 8 | |
Terry Jan Reedy | 6fa5bdc | 2016-05-28 13:22:31 -0400 | [diff] [blame] | 9 | from idlelib import searchengine |
| 10 | from idlelib.searchbase import SearchDialogBase |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 11 | |
| 12 | def _setup(text): |
Cheryl Sabella | 0bb5e75 | 2019-03-16 19:29:33 -0400 | [diff] [blame] | 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 | """ |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 21 | root = text._root() |
Terry Jan Reedy | 6fa5bdc | 2016-05-28 13:22:31 -0400 | [diff] [blame] | 22 | engine = searchengine.get(root) |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 23 | if not hasattr(engine, "_searchdialog"): |
| 24 | engine._searchdialog = SearchDialog(root, engine) |
| 25 | return engine._searchdialog |
| 26 | |
| 27 | def find(text): |
Cheryl Sabella | 0bb5e75 | 2019-03-16 19:29:33 -0400 | [diff] [blame] | 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 | """ |
Chui Tey | 5a231c8f | 2002-11-06 02:18:45 +0000 | [diff] [blame] | 35 | pat = text.get("sel.first", "sel.last") |
Terry Jan Reedy | b236fe4 | 2016-05-17 18:18:37 -0400 | [diff] [blame] | 36 | return _setup(text).open(text, pat) # Open is inherited from SDBase. |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 37 | |
| 38 | def find_again(text): |
Cheryl Sabella | 0bb5e75 | 2019-03-16 19:29:33 -0400 | [diff] [blame] | 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 | """ |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 47 | return _setup(text).find_again(text) |
| 48 | |
| 49 | def find_selection(text): |
Cheryl Sabella | 0bb5e75 | 2019-03-16 19:29:33 -0400 | [diff] [blame] | 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 | """ |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 59 | return _setup(text).find_selection(text) |
| 60 | |
Terry Jan Reedy | bfbaa6b | 2016-08-31 00:50:55 -0400 | [diff] [blame] | 61 | |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 62 | class SearchDialog(SearchDialogBase): |
Cheryl Sabella | 0bb5e75 | 2019-03-16 19:29:33 -0400 | [diff] [blame] | 63 | "Dialog for finding a pattern in text." |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 64 | |
| 65 | def create_widgets(self): |
Cheryl Sabella | 0bb5e75 | 2019-03-16 19:29:33 -0400 | [diff] [blame] | 66 | "Create the base search dialog and add a button for Find Next." |
Terry Jan Reedy | 2733618 | 2015-05-14 18:10:50 -0400 | [diff] [blame] | 67 | SearchDialogBase.create_widgets(self) |
Cheryl Sabella | 0bb5e75 | 2019-03-16 19:29:33 -0400 | [diff] [blame] | 68 | # TODO - why is this here and not in a create_command_buttons? |
| 69 | self.make_button("Find Next", self.default_command, isdef=True) |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 70 | |
| 71 | def default_command(self, event=None): |
Cheryl Sabella | 0bb5e75 | 2019-03-16 19:29:33 -0400 | [diff] [blame] | 72 | "Handle the Find Next button as the default command." |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 73 | if not self.engine.getprog(): |
| 74 | return |
Roger Serwy | 391f469 | 2013-06-10 23:01:20 -0500 | [diff] [blame] | 75 | self.find_again(self.text) |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 76 | |
| 77 | def find_again(self, text): |
Cheryl Sabella | 0bb5e75 | 2019-03-16 19:29:33 -0400 | [diff] [blame] | 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 seach 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 | """ |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 93 | if not self.engine.getpat(): |
| 94 | self.open(text) |
Kurt B. Kaiser | 0f4402d | 2002-09-18 03:10:10 +0000 | [diff] [blame] | 95 | return False |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 96 | if not self.engine.getprog(): |
Kurt B. Kaiser | 0f4402d | 2002-09-18 03:10:10 +0000 | [diff] [blame] | 97 | return False |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 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: |
Terry Jan Reedy | 3ff55a8 | 2016-08-10 23:44:54 -0400 | [diff] [blame] | 108 | self.bell() |
Kurt B. Kaiser | 0f4402d | 2002-09-18 03:10:10 +0000 | [diff] [blame] | 109 | return False |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 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") |
Kurt B. Kaiser | 0f4402d | 2002-09-18 03:10:10 +0000 | [diff] [blame] | 116 | return True |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 117 | else: |
Terry Jan Reedy | 3ff55a8 | 2016-08-10 23:44:54 -0400 | [diff] [blame] | 118 | self.bell() |
Kurt B. Kaiser | 0f4402d | 2002-09-18 03:10:10 +0000 | [diff] [blame] | 119 | return False |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 120 | |
| 121 | def find_selection(self, text): |
Cheryl Sabella | 0bb5e75 | 2019-03-16 19:29:33 -0400 | [diff] [blame] | 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 | """ |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 129 | pat = text.get("sel.first", "sel.last") |
| 130 | if pat: |
| 131 | self.engine.setcookedpat(pat) |
| 132 | return self.find_again(text) |
Terry Jan Reedy | 0a4d13e | 2014-05-27 03:30:54 -0400 | [diff] [blame] | 133 | |
Terry Jan Reedy | b236fe4 | 2016-05-17 18:18:37 -0400 | [diff] [blame] | 134 | |
| 135 | def _search_dialog(parent): # htest # |
Terry Jan Reedy | 6f7b0f5 | 2016-07-10 20:21:31 -0400 | [diff] [blame] | 136 | "Display search test box." |
| 137 | from tkinter import Toplevel, Text |
Terry Jan Reedy | aff0ada | 2019-01-02 22:04:06 -0500 | [diff] [blame] | 138 | from tkinter.ttk import Frame, Button |
Terry Jan Reedy | 6f7b0f5 | 2016-07-10 20:21:31 -0400 | [diff] [blame] | 139 | |
Terry Jan Reedy | aff0ada | 2019-01-02 22:04:06 -0500 | [diff] [blame] | 140 | top = Toplevel(parent) |
| 141 | top.title("Test SearchDialog") |
Terry Jan Reedy | a748032 | 2016-07-10 17:28:10 -0400 | [diff] [blame] | 142 | x, y = map(int, parent.geometry().split('+')[1:]) |
Terry Jan Reedy | aff0ada | 2019-01-02 22:04:06 -0500 | [diff] [blame] | 143 | top.geometry("+%d+%d" % (x, y + 175)) |
| 144 | |
| 145 | frame = Frame(top) |
| 146 | frame.pack() |
| 147 | text = Text(frame, inactiveselectbackground='gray') |
Terry Jan Reedy | 0a4d13e | 2014-05-27 03:30:54 -0400 | [diff] [blame] | 148 | text.pack() |
Terry Jan Reedy | b236fe4 | 2016-05-17 18:18:37 -0400 | [diff] [blame] | 149 | text.insert("insert","This is a sample string.\n"*5) |
Terry Jan Reedy | 0a4d13e | 2014-05-27 03:30:54 -0400 | [diff] [blame] | 150 | |
| 151 | def show_find(): |
Terry Jan Reedy | 6f7b0f5 | 2016-07-10 20:21:31 -0400 | [diff] [blame] | 152 | text.tag_add('sel', '1.0', 'end') |
Terry Jan Reedy | b236fe4 | 2016-05-17 18:18:37 -0400 | [diff] [blame] | 153 | _setup(text).open(text) |
Terry Jan Reedy | 6f7b0f5 | 2016-07-10 20:21:31 -0400 | [diff] [blame] | 154 | text.tag_remove('sel', '1.0', 'end') |
Terry Jan Reedy | 0a4d13e | 2014-05-27 03:30:54 -0400 | [diff] [blame] | 155 | |
Terry Jan Reedy | aff0ada | 2019-01-02 22:04:06 -0500 | [diff] [blame] | 156 | button = Button(frame, text="Search (selection ignored)", command=show_find) |
Terry Jan Reedy | 0a4d13e | 2014-05-27 03:30:54 -0400 | [diff] [blame] | 157 | button.pack() |
| 158 | |
| 159 | if __name__ == '__main__': |
Terry Jan Reedy | 4d92158 | 2018-06-19 19:12:52 -0400 | [diff] [blame] | 160 | from unittest import main |
| 161 | main('idlelib.idle_test.test_search', verbosity=2, exit=False) |
Terry Jan Reedy | a748032 | 2016-07-10 17:28:10 -0400 | [diff] [blame] | 162 | |
Terry Jan Reedy | 0a4d13e | 2014-05-27 03:30:54 -0400 | [diff] [blame] | 163 | from idlelib.idle_test.htest import run |
| 164 | run(_search_dialog) |