Coverage for src/tkinterquickhelper/funcwin/frame_params.py: 18%
163 statements
« prev ^ index » next coverage.py v7.1.0, created at 2024-04-25 05:39 +0200
« prev ^ index » next coverage.py v7.1.0, created at 2024-04-25 05:39 +0200
1# -*- coding: utf-8 -*-
2"""
3@file
5@brief Defines @see cl FrameParams.
6"""
7import sys
8import os
9import tkinter
10from .tk_window import create_tk
11from .function_helper import private_adjust_parameters
12from .storing_functions import _private_restore, _private_store, interpret_parameter
15class FrameParams(tkinter.Frame):
16 """
17 Creates a Frame window for a list of parameters.
18 """
20 def __init__(self, parent, restore=True, width=100, raise_exception=False,
21 params=None, help="", key_save="", command_leave=None): # pylint: disable=W0622
22 """
23 @param parent window parent
24 @param restore if True, check if existing saved parameters are present
25 @param width number of characters in every Entry field
26 @param raise_exception raise an exception instead of catching it
27 @param params parameters to overwrite
28 @param help help to display
29 @param key_save to make unique the file storing and restoring the parameters
30 @param command_leave if not None, this function will be called when clicking on Cancel or Leave
31 """
32 if params is None:
33 params = {}
35 tkinter.Frame.__init__(self, parent)
36 self.fdoc = tkinter.Frame(self)
37 self.fpar = tkinter.Frame(self)
38 self.fbut = tkinter.Frame(self)
39 self.fpar.pack()
40 self.fbut.pack()
41 self.fdoc.pack()
42 self.restore = restore
43 self.parent = parent
44 self.input = {}
45 self.types = {}
46 self.raise_exception = raise_exception
47 self._added = {}
48 self.key_save = key_save
49 self.command_leave = command_leave
51 # retrieve previous answers
52 self._history = []
53 self._hpos = -1
55 self.info = {"name": "FrameParams", "param": params,
56 "help": help, "key_save": key_save}
58 objs = []
60 if restore:
61 self._history = _private_restore(
62 ".".join([self.info["name"], self.info["key_save"]]))
63 if len(self._history) > 0:
64 self.info["param"].update(self._history[-1])
65 self._hpos = len(self._history) - 1
67 for k in self.info["param"]:
68 self.types[k] = self.info["param"][k].__class__
69 if self.types[k] in [None, None.__class__]:
70 self.types[k] = str
72 # documentation
73 tlab = tkinter.Label(self.fdoc, text="Help")
74 tlab.pack(side=tkinter.LEFT)
75 lab = tkinter.Text(self.fdoc, width=width, height=7)
76 lab.pack(side=tkinter.LEFT)
77 lab.insert("0.0", self.info["help"])
78 objs.append(lab)
79 objs.append(tlab)
81 scroll = tkinter.Scrollbar(self.fdoc)
82 scroll.pack(side=tkinter.RIGHT, fill=tkinter.Y)
83 scroll.config(command=lab.yview, width=5)
84 lab.config(yscrollcommand=scroll.set)
86 # next
87 line = 0
88 for k in sorted(self.info["param"]):
89 if k in self._added:
90 continue
91 lab = tkinter.Label(self.fpar, text=k)
92 lab.grid(row=line, column=0)
94 if k in ["password", "password1", "password2", "password3"]:
95 lab = tkinter.Entry(self.fpar, width=width, show="*")
96 else:
97 lab = tkinter.Entry(self.fpar, width=width)
99 lab.grid(row=line, column=1)
100 if self.info["param"][k] is not None:
101 lab.insert("0", str(self.info["param"][k]))
102 self.input[k] = lab
103 objs.append(lab)
104 line += 1
106 # optional
107 for k in sorted(self.info["param"]):
108 if k not in self._added:
109 continue
110 lab = tkinter.Label(self.fpar, text=k)
111 lab.grid(row=line, column=0)
113 if k in ["password", "password1", "password2", "password3"]:
114 lab = tkinter.Entry(self.fpar, width=width, show="*")
115 else:
116 lab = tkinter.Entry(self.fpar, width=width)
118 lab.grid(row=line, column=1)
119 if self.info["param"][k] is not None:
120 lab.insert("0", str(self.info["param"][k]))
121 self.input[k] = lab
122 objs.append(lab)
123 line += 1
125 # next: button
126 self.cancel = tkinter.Button(self.fbut, text="cancel or leave")
127 self.run = tkinter.Button(self.fbut, text=" ok ")
128 self.cancel.pack(side=tkinter.LEFT)
129 self.run.pack(side=tkinter.LEFT)
130 self.run.bind('<Return>', self.run_function)
131 self.run.bind('<Escape>', self.run_cancel)
133 self.cancel.config(command=self.run_cancel)
134 self.run.config(command=self.run_function)
135 private_adjust_parameters(self.info["param"])
136 self._already = False
138 # up, down
139 self.bup = tkinter.Button(self.fbut, text="up")
140 self.bdown = tkinter.Button(self.fbut, text="down")
141 self.bup.pack(side=tkinter.LEFT)
142 self.bdown.pack(side=tkinter.LEFT)
143 self.bup.config(command=self.history_up)
144 self.bdown.config(command=self.history_down)
146 # keys
147 for obj in objs + \
148 [parent, self, self.bup, self.bdown, self.run, self.cancel, self.fdoc]:
149 obj.bind("<Up>", self.history_up)
150 obj.bind("<Down>", self.history_down)
151 obj.bind("<Return>", self.run_function)
152 obj.bind("<Escape>", self.run_cancel)
154 def update(self):
155 """
156 update the parameters (ie ``self.info``)
157 """
158 for k in self.input: # pylint: disable=C0206
159 self.input[k].delete(0, tkinter.END)
160 self.input[k].insert("0", str(self.info["param"].get(k, "")))
162 def history_up(self, *args):
163 """
164 look back in the history (log of used parameters)
165 and update the parameters
166 """
167 if len(self._history) > 0:
168 self._hpos = (self._hpos + 1) % len(self._history)
169 self.info["param"].update(self._history[self._hpos])
170 self.update()
172 def history_down(self, *args):
173 """
174 look forward in the history (log of used parameters)
175 and update the parameters
176 """
177 if len(self._history) > 0:
178 self._hpos = (
179 self._hpos + len(self._history) - 1) % len(self._history)
180 self.info["param"].update(self._history[self._hpos])
181 self.update()
183 def run_cancel(self, *args):
184 """
185 what to do when Cancel is pressed
186 """
187 self.info["param"]["__cancel__"] = True
188 if self.command_leave is not None:
189 self.command_leave()
190 else:
191 self.parent.destroy()
193 def get_parameters(self):
194 """
195 returns the parameters
197 @return dictionary
198 """
199 res = {}
200 for k, v in self.input.items():
201 s = v.get()
202 s = s.strip()
203 if len(s) == 0:
204 s = None
205 ty = self.types[k]
206 res[k] = interpret_parameter(ty, s)
207 return res
209 def get_title(self):
210 """
211 Returns the title.
213 @return ``self.info ["name"]``
214 """
215 return self.info["name"]
217 def refresh(self):
218 """
219 Refreshes the screen.
220 """
221 if self._already:
222 self.after(1000, self.refresh)
223 else:
224 self.run.config(state=tkinter.NORMAL)
225 self.parent.destroy()
227 def run_function(self, *args):
228 """
229 Runs the function.
230 """
231 self.parent.withdraw()
232 # self.run.config(state=tkinter.DISABLED)
233 self._already = True
235 res = self.get_parameters()
236 if self.restore:
237 _private_store(
238 ".".join([self.info["name"], self.info["key_save"]]), res)
240 self.info["param"].update(res)
241 self.parent.destroy()
243 @staticmethod
244 def open_window(params,
245 help_string="",
246 title="",
247 top_level_window=None,
248 key_save="",
249 do_not_open=False):
250 """
251 Opens a :epkg:`tkinter` window to set up parameters.
252 It adds entries for the parameters,
253 it displays the help given to this function.
254 It also memorizes the latest values used (stored in ``<user>/TEMP folder``).
256 @param help_string help to de displayed
257 @param top_level_window if you want this window to depend on a top level window from tkinter
258 @param params if not None, overwrite values for some parameters,
259 it will be updated by the function (= returned value)
260 @param key_save parameters are saved and restore from a file, key_save will make this file unique
261 @param title title of the window
262 @param do_not_open do not open the window, let you do it
263 @return new parameters (or a the Windows object if *do_not_open* is True)
265 @warning If the string "__cancel__" is present in the results, it means the users clicked on cancel.
267 The window looks like:
268 @image images/open_window_params.png
270 Example::
272 params = {"velib_key": "", "contract":"Paris"}
273 newparams = FrameParams.open_window (params, "fetch data from Velib website")
275 .. versionchanged:: 1.0
276 Parameter *do_not_open* was added.
277 """
278 param = params if params is not None else {}
280 root = top_level_window if top_level_window is not None else create_tk()
281 ico = os.path.realpath(
282 os.path.join(os.path.split(__file__)[0], "project_ico.ico"))
283 fr = FrameParams(
284 root, params=param, help=help_string, key_save=key_save)
285 fr.pack()
286 root.title(title)
287 if ico is not None and top_level_window is None and sys.platform.startswith(
288 "win"):
289 root.wm_iconbitmap(ico)
290 if top_level_window is None:
291 fr.focus_set()
292 root.focus_set()
294 if do_not_open:
295 return fr
296 else:
297 fr.mainloop()
298 return param
301def open_window_params(params, help_string="", title="", top_level_window=None,
302 key_save="", do_not_open=False):
303 """
304 Open a :epkg:`tkinter` window to set up parameters.
305 It adds entries for the parameters,
306 it displays the help given to this function.
307 It also memorizes the latest values used
308 (stored in `<user>/TEMP` folder).
310 @param help_string help to de displayed
311 @param top_level_window if you want this window to depend on a top level window from tkinter
312 @param params if not None, overwrite values for some parameters,
313 it will be updated by the function (= returned value)
314 @param key_save parameters are saved and restore from a file, key_save will make this file unique
315 @param title title of the window
316 @param do_not_open do not open the window, let you do it
317 @return new parameters (or a the Windows object if *do_not_open* is True)
319 @warning If the string "__cancel__" is present in the results, it means the users clicked on cancel.
321 The window looks like:
322 @image images/open_window_params.png
324 .. exref::
325 :title: Open a tkinter window to ask parameters to a user
327 ::
329 params = { "user": os.environ.get("USERNAME", os.environ["USER"]),
330 "password":"" }
331 newparams = open_window_params(params, title="try the password *",
332 help_string="unit test", key_save="my_key")
334 The program opens a window like the following one:
336 @image images/open_params.png
338 The parameters ``key_save`` can be ignored but if you use this function
339 with different parameters, they should all appear after a couple of runs.
340 That is because the function uses ``key_save`` ot unique the file uses
341 to store the values for the parameters used in previous execution.
343 Password are not stored in a text file. You must type them again next time.
344 """
345 return FrameParams.open_window(params=params, help_string=help_string, title=title,
346 top_level_window=top_level_window, key_save=key_save,
347 do_not_open=do_not_open)