blob: 64ed50c7364be394c08df04c94c737f738f2f8b6 [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
Serhiy Storchaka3bb3fb32021-04-25 13:07:58 +03005from tkinter.simpledialog import _setup_dialog
David Scherer7aced172000-08-15 01:13:23 +00006
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -04007
David Scherer7aced172000-08-15 01:13:23 +00008class SearchDialogBase:
Terry Jan Reedyede05732014-06-26 01:40:51 -04009 '''Create most of a 3 or 4 row, 3 column search dialog.
Terry Jan Reedyc5507c02013-08-18 18:22:43 -040010
Terry Jan Reedyede05732014-06-26 01:40:51 -040011 The left and wide middle column contain:
12 1 or 2 labeled text entry lines (make_entry, create_entries);
13 a row of standard Checkbuttons (make_frame, create_option_buttons),
14 each of which corresponds to a search engine Variable;
15 a row of dialog-specific Check/Radiobuttons (create_other_buttons).
Terry Jan Reedyba5d8f32013-08-18 18:27:02 -040016
Terry Jan Reedyc5507c02013-08-18 18:22:43 -040017 The narrow right column contains command buttons
Terry Jan Reedyede05732014-06-26 01:40:51 -040018 (make_button, create_command_buttons).
Terry Jan Reedyc5507c02013-08-18 18:22:43 -040019 These are bound to functions that execute the command.
20
Terry Jan Reedy525168b2014-06-30 20:00:03 -040021 Except for command buttons, this base class is not limited to items
22 common to all three subclasses. Rather, it is the Find dialog minus
23 the "Find Next" command, its execution function, and the
24 default_command attribute needed in create_widgets. The other
25 dialogs override attributes and methods, the latter to replace and
26 add widgets.
Terry Jan Reedyc5507c02013-08-18 18:22:43 -040027 '''
David Scherer7aced172000-08-15 01:13:23 +000028
Terry Jan Reedyede05732014-06-26 01:40:51 -040029 title = "Search Dialog" # replace in subclasses
David Scherer7aced172000-08-15 01:13:23 +000030 icon = "Search"
Terry Jan Reedyede05732014-06-26 01:40:51 -040031 needwrapbutton = 1 # not in Find in Files
David Scherer7aced172000-08-15 01:13:23 +000032
33 def __init__(self, root, engine):
Terry Jan Reedyede05732014-06-26 01:40:51 -040034 '''Initialize root, engine, and top attributes.
35
36 top (level widget): set in create_widgets() called from open().
Mark Roseman5df6c992020-10-24 20:14:02 -070037 frame: container for all widgets in dialog.
Terry Jan Reedy525168b2014-06-30 20:00:03 -040038 text (Text searched): set in open(), only used in subclasses().
Terry Jan Reedyede05732014-06-26 01:40:51 -040039 ent (ry): created in make_entry() called from create_entry().
40 row (of grid): 0 in create_widgets(), +1 in make_entry/frame().
Xtreakd9677f32019-06-03 09:51:15 +053041 default_command: set in subclasses, used in create_widgets().
Terry Jan Reedyede05732014-06-26 01:40:51 -040042
43 title (of dialog): class attribute, override in subclasses.
44 icon (of dialog): ditto, use unclear if cannot minimize dialog.
45 '''
David Scherer7aced172000-08-15 01:13:23 +000046 self.root = root
Terry Jan Reedyc4656822018-12-28 02:41:35 -050047 self.bell = root.bell
David Scherer7aced172000-08-15 01:13:23 +000048 self.engine = engine
49 self.top = None
50
Chui Tey5a231c8f2002-11-06 02:18:45 +000051 def open(self, text, searchphrase=None):
Terry Jan Reedyede05732014-06-26 01:40:51 -040052 "Make dialog visible on top of others and ready to use."
David Scherer7aced172000-08-15 01:13:23 +000053 self.text = text
54 if not self.top:
55 self.create_widgets()
56 else:
57 self.top.deiconify()
58 self.top.tkraise()
Tal Einat554450f2019-06-07 08:54:40 +030059 self.top.transient(text.winfo_toplevel())
Chui Tey5a231c8f2002-11-06 02:18:45 +000060 if searchphrase:
61 self.ent.delete(0,"end")
62 self.ent.insert("end",searchphrase)
David Scherer7aced172000-08-15 01:13:23 +000063 self.ent.focus_set()
64 self.ent.selection_range(0, "end")
65 self.ent.icursor(0)
66 self.top.grab_set()
67
68 def close(self, event=None):
Terry Jan Reedyede05732014-06-26 01:40:51 -040069 "Put dialog away for later use."
David Scherer7aced172000-08-15 01:13:23 +000070 if self.top:
71 self.top.grab_release()
Tal Einat554450f2019-06-07 08:54:40 +030072 self.top.transient('')
David Scherer7aced172000-08-15 01:13:23 +000073 self.top.withdraw()
74
75 def create_widgets(self):
Terry Jan Reedyede05732014-06-26 01:40:51 -040076 '''Create basic 3 row x 3 col search (find) dialog.
77
78 Other dialogs override subsidiary create_x methods as needed.
79 Replace and Find-in-Files add another entry row.
80 '''
David Scherer7aced172000-08-15 01:13:23 +000081 top = Toplevel(self.root)
82 top.bind("<Return>", self.default_command)
83 top.bind("<Escape>", self.close)
84 top.protocol("WM_DELETE_WINDOW", self.close)
85 top.wm_title(self.title)
86 top.wm_iconname(self.icon)
Serhiy Storchaka3bb3fb32021-04-25 13:07:58 +030087 _setup_dialog(top)
David Scherer7aced172000-08-15 01:13:23 +000088 self.top = top
Mark Roseman5df6c992020-10-24 20:14:02 -070089 self.frame = Frame(top, padding="5px")
90 self.frame.grid(sticky="nwes")
91 top.grid_columnconfigure(0, weight=100)
92 top.grid_rowconfigure(0, weight=100)
David Scherer7aced172000-08-15 01:13:23 +000093
94 self.row = 0
Mark Roseman5df6c992020-10-24 20:14:02 -070095 self.frame.grid_columnconfigure(0, pad=2, weight=0)
96 self.frame.grid_columnconfigure(1, pad=2, minsize=100, weight=100)
David Scherer7aced172000-08-15 01:13:23 +000097
Terry Jan Reedyede05732014-06-26 01:40:51 -040098 self.create_entries() # row 0 (and maybe 1), cols 0, 1
99 self.create_option_buttons() # next row, cols 0, 1
100 self.create_other_buttons() # next row, cols 0, 1
101 self.create_command_buttons() # col 2, all rows
David Scherer7aced172000-08-15 01:13:23 +0000102
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400103 def make_entry(self, label_text, var):
104 '''Return (entry, label), .
105
106 entry - gridded labeled Entry for text entry.
107 label - Label widget, returned for testing.
108 '''
Mark Roseman5df6c992020-10-24 20:14:02 -0700109 label = Label(self.frame, text=label_text)
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400110 label.grid(row=self.row, column=0, sticky="nw")
Mark Roseman5df6c992020-10-24 20:14:02 -0700111 entry = Entry(self.frame, textvariable=var, exportselection=0)
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400112 entry.grid(row=self.row, column=1, sticky="nwe")
David Scherer7aced172000-08-15 01:13:23 +0000113 self.row = self.row + 1
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400114 return entry, label
David Scherer7aced172000-08-15 01:13:23 +0000115
Terry Jan Reedyede05732014-06-26 01:40:51 -0400116 def create_entries(self):
117 "Create one or more entry lines with make_entry."
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400118 self.ent = self.make_entry("Find:", self.engine.patvar)[0]
Terry Jan Reedyede05732014-06-26 01:40:51 -0400119
Chui Tey72a8a3b2002-11-04 23:07:51 +0000120 def make_frame(self,labeltext=None):
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400121 '''Return (frame, label).
122
123 frame - gridded labeled Frame for option or other buttons.
124 label - Label widget, returned for testing.
125 '''
Chui Tey72a8a3b2002-11-04 23:07:51 +0000126 if labeltext:
Mark Roseman5df6c992020-10-24 20:14:02 -0700127 label = Label(self.frame, text=labeltext)
Terry Jan Reedy8cefd082014-06-30 23:52:20 -0400128 label.grid(row=self.row, column=0, sticky="nw")
Terry Jan Reedy525168b2014-06-30 20:00:03 -0400129 else:
Terry Jan Reedy8cefd082014-06-30 23:52:20 -0400130 label = ''
Mark Roseman5df6c992020-10-24 20:14:02 -0700131 frame = Frame(self.frame)
Terry Jan Reedy8cefd082014-06-30 23:52:20 -0400132 frame.grid(row=self.row, column=1, columnspan=1, sticky="nwe")
David Scherer7aced172000-08-15 01:13:23 +0000133 self.row = self.row + 1
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400134 return frame, label
David Scherer7aced172000-08-15 01:13:23 +0000135
David Scherer7aced172000-08-15 01:13:23 +0000136 def create_option_buttons(self):
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400137 '''Return (filled frame, options) for testing.
138
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400139 Options is a list of searchengine booleanvar, label pairs.
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400140 A gridded frame from make_frame is filled with a Checkbutton
141 for each pair, bound to the var, with the corresponding label.
142 '''
Terry Jan Reedy8cefd082014-06-30 23:52:20 -0400143 frame = self.make_frame("Options")[0]
144 engine = self.engine
145 options = [(engine.revar, "Regular expression"),
146 (engine.casevar, "Match case"),
147 (engine.wordvar, "Whole word")]
David Scherer7aced172000-08-15 01:13:23 +0000148 if self.needwrapbutton:
Terry Jan Reedy8cefd082014-06-30 23:52:20 -0400149 options.append((engine.wrapvar, "Wrap around"))
150 for var, label in options:
Terry Jan Reedy6f7b0f52016-07-10 20:21:31 -0400151 btn = Checkbutton(frame, variable=var, text=label)
David Scherer7aced172000-08-15 01:13:23 +0000152 btn.pack(side="left", fill="both")
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400153 return frame, options
David Scherer7aced172000-08-15 01:13:23 +0000154
155 def create_other_buttons(self):
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400156 '''Return (frame, others) for testing.
157
158 Others is a list of value, label pairs.
159 A gridded frame from make_frame is filled with radio buttons.
160 '''
Terry Jan Reedy8cefd082014-06-30 23:52:20 -0400161 frame = self.make_frame("Direction")[0]
162 var = self.engine.backvar
163 others = [(1, 'Up'), (0, 'Down')]
164 for val, label in others:
Terry Jan Reedy6f7b0f52016-07-10 20:21:31 -0400165 btn = Radiobutton(frame, variable=var, value=val, text=label)
Terry Jan Reedy8cefd082014-06-30 23:52:20 -0400166 btn.pack(side="left", fill="both")
Terry Jan Reedy5283c4e2014-07-13 17:27:26 -0400167 return frame, others
David Scherer7aced172000-08-15 01:13:23 +0000168
Terry Jan Reedyede05732014-06-26 01:40:51 -0400169 def make_button(self, label, command, isdef=0):
170 "Return command button gridded in command frame."
171 b = Button(self.buttonframe,
172 text=label, command=command,
173 default=isdef and "active" or "normal")
174 cols,rows=self.buttonframe.grid_size()
175 b.grid(pady=1,row=rows,column=0,sticky="ew")
176 self.buttonframe.grid(rowspan=rows+1)
177 return b
178
David Scherer7aced172000-08-15 01:13:23 +0000179 def create_command_buttons(self):
Terry Jan Reedyede05732014-06-26 01:40:51 -0400180 "Place buttons in vertical command frame gridded on right."
Mark Roseman5df6c992020-10-24 20:14:02 -0700181 f = self.buttonframe = Frame(self.frame)
Kurt B. Kaiser4fc90472002-11-21 03:02:17 +0000182 f.grid(row=0,column=2,padx=2,pady=2,ipadx=2,ipady=2)
Chui Tey72a8a3b2002-11-04 23:07:51 +0000183
Terry Jan Reedyba043022019-05-31 04:26:35 -0400184 b = self.make_button("Close", self.close)
David Scherer7aced172000-08-15 01:13:23 +0000185 b.lower()
Terry Jan Reedyede05732014-06-26 01:40:51 -0400186
Terry Jan Reedy6f7b0f52016-07-10 20:21:31 -0400187
188class _searchbase(SearchDialogBase): # htest #
189 "Create auto-opening dialog with no text connection."
190
191 def __init__(self, parent):
192 import re
193 from idlelib import searchengine
194
195 self.root = parent
196 self.engine = searchengine.get(parent)
197 self.create_widgets()
198 print(parent.geometry())
199 width,height, x,y = list(map(int, re.split('[x+]', parent.geometry())))
200 self.top.geometry("+%d+%d" % (x + 40, y + 175))
201
Terry Jan Reedy3ff55a82016-08-10 23:44:54 -0400202 def default_command(self, dummy): pass
Terry Jan Reedy6f7b0f52016-07-10 20:21:31 -0400203
Terry Jan Reedy4d921582018-06-19 19:12:52 -0400204
Terry Jan Reedyede05732014-06-26 01:40:51 -0400205if __name__ == '__main__':
Terry Jan Reedy4d921582018-06-19 19:12:52 -0400206 from unittest import main
207 main('idlelib.idle_test.test_searchbase', verbosity=2, exit=False)
Terry Jan Reedy6f7b0f52016-07-10 20:21:31 -0400208
209 from idlelib.idle_test.htest import run
210 run(_searchbase)