blob: fbef87aa2d3d04c1865b28252324b9e39d478947 [file] [log] [blame]
Terry Jan Reedyc5507c02013-08-18 18:22:43 -04001'''Define SearchDialogBase used by Search, Replace, and Grep dialogs.'''
Terry Jan Reedyede05732014-06-26 01:40:51 -04002
Terry Jan Reedyaff0ada2019-01-02 22:04:06 -05003from tkinter import Toplevel
4from tkinter.ttk import Frame, Entry, Label, Button, Checkbutton, Radiobutton
David Scherer7aced172000-08-15 01:13:23 +00005
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04006
David Scherer7aced172000-08-15 01:13:23 +00007class SearchDialogBase:
Terry Jan Reedyede05732014-06-26 01:40:51 -04008 '''Create most of a 3 or 4 row, 3 column search dialog.
Terry Jan Reedyc5507c02013-08-18 18:22:43 -04009
Terry Jan Reedyede05732014-06-26 01:40:51 -040010 The left and wide middle column contain:
11 1 or 2 labeled text entry lines (make_entry, create_entries);
12 a row of standard Checkbuttons (make_frame, create_option_buttons),
13 each of which corresponds to a search engine Variable;
14 a row of dialog-specific Check/Radiobuttons (create_other_buttons).
Terry Jan Reedyba5d8f32013-08-18 18:27:02 -040015
Terry Jan Reedyc5507c02013-08-18 18:22:43 -040016 The narrow right column contains command buttons
Terry Jan Reedyede05732014-06-26 01:40:51 -040017 (make_button, create_command_buttons).
Terry Jan Reedyc5507c02013-08-18 18:22:43 -040018 These are bound to functions that execute the command.
19
Terry Jan Reedy525168b2014-06-30 20:00:03 -040020 Except for command buttons, this base class is not limited to items
21 common to all three subclasses. Rather, it is the Find dialog minus
22 the "Find Next" command, its execution function, and the
23 default_command attribute needed in create_widgets. The other
24 dialogs override attributes and methods, the latter to replace and
25 add widgets.
Terry Jan Reedyc5507c02013-08-18 18:22:43 -040026 '''
David Scherer7aced172000-08-15 01:13:23 +000027
Terry Jan Reedyede05732014-06-26 01:40:51 -040028 title = "Search Dialog" # replace in subclasses
David Scherer7aced172000-08-15 01:13:23 +000029 icon = "Search"
Terry Jan Reedyede05732014-06-26 01:40:51 -040030 needwrapbutton = 1 # not in Find in Files
David Scherer7aced172000-08-15 01:13:23 +000031
32 def __init__(self, root, engine):
Terry Jan Reedyede05732014-06-26 01:40:51 -040033 '''Initialize root, engine, and top attributes.
34
35 top (level widget): set in create_widgets() called from open().
Mark Roseman5df6c992020-10-24 20:14:02 -070036 frame: container for all widgets in dialog.
Terry Jan Reedy525168b2014-06-30 20:00:03 -040037 text (Text searched): set in open(), only used in subclasses().
Terry Jan Reedyede05732014-06-26 01:40:51 -040038 ent (ry): created in make_entry() called from create_entry().
39 row (of grid): 0 in create_widgets(), +1 in make_entry/frame().
Xtreakd9677f32019-06-03 09:51:15 +053040 default_command: set in subclasses, used in create_widgets().
Terry Jan Reedyede05732014-06-26 01:40:51 -040041
42 title (of dialog): class attribute, override in subclasses.
43 icon (of dialog): ditto, use unclear if cannot minimize dialog.
44 '''
David Scherer7aced172000-08-15 01:13:23 +000045 self.root = root
Terry Jan Reedyc4656822018-12-28 02:41:35 -050046 self.bell = root.bell
David Scherer7aced172000-08-15 01:13:23 +000047 self.engine = engine
48 self.top = None
49
Chui Tey5a231c8f2002-11-06 02:18:45 +000050 def open(self, text, searchphrase=None):
Terry Jan Reedyede05732014-06-26 01:40:51 -040051 "Make dialog visible on top of others and ready to use."
David Scherer7aced172000-08-15 01:13:23 +000052 self.text = text
53 if not self.top:
54 self.create_widgets()
55 else:
56 self.top.deiconify()
57 self.top.tkraise()
Tal Einat554450f2019-06-07 08:54:40 +030058 self.top.transient(text.winfo_toplevel())
Chui Tey5a231c8f2002-11-06 02:18:45 +000059 if searchphrase:
60 self.ent.delete(0,"end")
61 self.ent.insert("end",searchphrase)
David Scherer7aced172000-08-15 01:13:23 +000062 self.ent.focus_set()
63 self.ent.selection_range(0, "end")
64 self.ent.icursor(0)
65 self.top.grab_set()
66
67 def close(self, event=None):
Terry Jan Reedyede05732014-06-26 01:40:51 -040068 "Put dialog away for later use."
David Scherer7aced172000-08-15 01:13:23 +000069 if self.top:
70 self.top.grab_release()
Tal Einat554450f2019-06-07 08:54:40 +030071 self.top.transient('')
David Scherer7aced172000-08-15 01:13:23 +000072 self.top.withdraw()
73
74 def create_widgets(self):
Terry Jan Reedyede05732014-06-26 01:40:51 -040075 '''Create basic 3 row x 3 col search (find) dialog.
76
77 Other dialogs override subsidiary create_x methods as needed.
78 Replace and Find-in-Files add another entry row.
79 '''
David Scherer7aced172000-08-15 01:13:23 +000080 top = Toplevel(self.root)
81 top.bind("<Return>", self.default_command)
82 top.bind("<Escape>", self.close)
83 top.protocol("WM_DELETE_WINDOW", self.close)
84 top.wm_title(self.title)
85 top.wm_iconname(self.icon)
86 self.top = top
Mark Roseman5df6c992020-10-24 20:14:02 -070087 self.frame = Frame(top, padding="5px")
88 self.frame.grid(sticky="nwes")
89 top.grid_columnconfigure(0, weight=100)
90 top.grid_rowconfigure(0, weight=100)
David Scherer7aced172000-08-15 01:13:23 +000091
92 self.row = 0
Mark Roseman5df6c992020-10-24 20:14:02 -070093 self.frame.grid_columnconfigure(0, pad=2, weight=0)
94 self.frame.grid_columnconfigure(1, pad=2, minsize=100, weight=100)
David Scherer7aced172000-08-15 01:13:23 +000095
Terry Jan Reedyede05732014-06-26 01:40:51 -040096 self.create_entries() # row 0 (and maybe 1), cols 0, 1
97 self.create_option_buttons() # next row, cols 0, 1
98 self.create_other_buttons() # next row, cols 0, 1
99 self.create_command_buttons() # col 2, all rows
David Scherer7aced172000-08-15 01:13:23 +0000100
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400101 def make_entry(self, label_text, var):
102 '''Return (entry, label), .
103
104 entry - gridded labeled Entry for text entry.
105 label - Label widget, returned for testing.
106 '''
Mark Roseman5df6c992020-10-24 20:14:02 -0700107 label = Label(self.frame, text=label_text)
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400108 label.grid(row=self.row, column=0, sticky="nw")
Mark Roseman5df6c992020-10-24 20:14:02 -0700109 entry = Entry(self.frame, textvariable=var, exportselection=0)
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400110 entry.grid(row=self.row, column=1, sticky="nwe")
David Scherer7aced172000-08-15 01:13:23 +0000111 self.row = self.row + 1
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400112 return entry, label
David Scherer7aced172000-08-15 01:13:23 +0000113
Terry Jan Reedyede05732014-06-26 01:40:51 -0400114 def create_entries(self):
115 "Create one or more entry lines with make_entry."
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400116 self.ent = self.make_entry("Find:", self.engine.patvar)[0]
Terry Jan Reedyede05732014-06-26 01:40:51 -0400117
Chui Tey72a8a3b2002-11-04 23:07:51 +0000118 def make_frame(self,labeltext=None):
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400119 '''Return (frame, label).
120
121 frame - gridded labeled Frame for option or other buttons.
122 label - Label widget, returned for testing.
123 '''
Chui Tey72a8a3b2002-11-04 23:07:51 +0000124 if labeltext:
Mark Roseman5df6c992020-10-24 20:14:02 -0700125 label = Label(self.frame, text=labeltext)
Terry Jan Reedy8cefd082014-06-30 23:52:20 -0400126 label.grid(row=self.row, column=0, sticky="nw")
Terry Jan Reedy525168b2014-06-30 20:00:03 -0400127 else:
Terry Jan Reedy8cefd082014-06-30 23:52:20 -0400128 label = ''
Mark Roseman5df6c992020-10-24 20:14:02 -0700129 frame = Frame(self.frame)
Terry Jan Reedy8cefd082014-06-30 23:52:20 -0400130 frame.grid(row=self.row, column=1, columnspan=1, sticky="nwe")
David Scherer7aced172000-08-15 01:13:23 +0000131 self.row = self.row + 1
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400132 return frame, label
David Scherer7aced172000-08-15 01:13:23 +0000133
David Scherer7aced172000-08-15 01:13:23 +0000134 def create_option_buttons(self):
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400135 '''Return (filled frame, options) for testing.
136
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400137 Options is a list of searchengine booleanvar, label pairs.
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400138 A gridded frame from make_frame is filled with a Checkbutton
139 for each pair, bound to the var, with the corresponding label.
140 '''
Terry Jan Reedy8cefd082014-06-30 23:52:20 -0400141 frame = self.make_frame("Options")[0]
142 engine = self.engine
143 options = [(engine.revar, "Regular expression"),
144 (engine.casevar, "Match case"),
145 (engine.wordvar, "Whole word")]
David Scherer7aced172000-08-15 01:13:23 +0000146 if self.needwrapbutton:
Terry Jan Reedy8cefd082014-06-30 23:52:20 -0400147 options.append((engine.wrapvar, "Wrap around"))
148 for var, label in options:
Terry Jan Reedy6f7b0f52016-07-10 20:21:31 -0400149 btn = Checkbutton(frame, variable=var, text=label)
David Scherer7aced172000-08-15 01:13:23 +0000150 btn.pack(side="left", fill="both")
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400151 return frame, options
David Scherer7aced172000-08-15 01:13:23 +0000152
153 def create_other_buttons(self):
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400154 '''Return (frame, others) for testing.
155
156 Others is a list of value, label pairs.
157 A gridded frame from make_frame is filled with radio buttons.
158 '''
Terry Jan Reedy8cefd082014-06-30 23:52:20 -0400159 frame = self.make_frame("Direction")[0]
160 var = self.engine.backvar
161 others = [(1, 'Up'), (0, 'Down')]
162 for val, label in others:
Terry Jan Reedy6f7b0f52016-07-10 20:21:31 -0400163 btn = Radiobutton(frame, variable=var, value=val, text=label)
Terry Jan Reedy8cefd082014-06-30 23:52:20 -0400164 btn.pack(side="left", fill="both")
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400165 return frame, others
David Scherer7aced172000-08-15 01:13:23 +0000166
Terry Jan Reedyede05732014-06-26 01:40:51 -0400167 def make_button(self, label, command, isdef=0):
168 "Return command button gridded in command frame."
169 b = Button(self.buttonframe,
170 text=label, command=command,
171 default=isdef and "active" or "normal")
172 cols,rows=self.buttonframe.grid_size()
173 b.grid(pady=1,row=rows,column=0,sticky="ew")
174 self.buttonframe.grid(rowspan=rows+1)
175 return b
176
David Scherer7aced172000-08-15 01:13:23 +0000177 def create_command_buttons(self):
Terry Jan Reedyede05732014-06-26 01:40:51 -0400178 "Place buttons in vertical command frame gridded on right."
Mark Roseman5df6c992020-10-24 20:14:02 -0700179 f = self.buttonframe = Frame(self.frame)
Kurt B. Kaiser4fc90472002-11-21 03:02:17 +0000180 f.grid(row=0,column=2,padx=2,pady=2,ipadx=2,ipady=2)
Chui Tey72a8a3b2002-11-04 23:07:51 +0000181
Terry Jan Reedyba043022019-05-31 04:26:35 -0400182 b = self.make_button("Close", self.close)
David Scherer7aced172000-08-15 01:13:23 +0000183 b.lower()
Terry Jan Reedyede05732014-06-26 01:40:51 -0400184
Terry Jan Reedy6f7b0f52016-07-10 20:21:31 -0400185
186class _searchbase(SearchDialogBase): # htest #
187 "Create auto-opening dialog with no text connection."
188
189 def __init__(self, parent):
190 import re
191 from idlelib import searchengine
192
193 self.root = parent
194 self.engine = searchengine.get(parent)
195 self.create_widgets()
196 print(parent.geometry())
197 width,height, x,y = list(map(int, re.split('[x+]', parent.geometry())))
198 self.top.geometry("+%d+%d" % (x + 40, y + 175))
199
Terry Jan Reedy3ff55a82016-08-10 23:44:54 -0400200 def default_command(self, dummy): pass
Terry Jan Reedy6f7b0f52016-07-10 20:21:31 -0400201
Terry Jan Reedy4d921582018-06-19 19:12:52 -0400202
Terry Jan Reedyede05732014-06-26 01:40:51 -0400203if __name__ == '__main__':
Terry Jan Reedy4d921582018-06-19 19:12:52 -0400204 from unittest import main
205 main('idlelib.idle_test.test_searchbase', verbosity=2, exit=False)
Terry Jan Reedy6f7b0f52016-07-10 20:21:31 -0400206
207 from idlelib.idle_test.htest import run
208 run(_searchbase)