# -*- coding: utf-8 -*-
"""
Defines :class:`FrameParams <tkinterquickhelper.funcwin.frame_params.FrameParams>`.
:githublink:`%|py|7`
"""
import sys
import os
import tkinter
from .tk_window import create_tk
from .function_helper import private_adjust_parameters
from .storing_functions import _private_restore, _private_store, interpret_parameter
[docs]class FrameParams(tkinter.Frame):
"""
Creates a Frame window for a list of parameters.
:githublink:`%|py|18`
"""
[docs] def __init__(self, parent, restore=True, width=100, raise_exception=False,
params=None, help="", key_save="", command_leave=None): # pylint: disable=W0622
"""
:param parent: window parent
:param restore: if True, check if existing saved parameters are present
:param width: number of characters in every Entry field
:param raise_exception: raise an exception instead of catching it
:param params: parameters to overwrite
:param help: help to display
:param key_save: to make unique the file storing and restoring the parameters
:param command_leave: if not None, this function will be called when clicking on Cancel or Leave
:githublink:`%|py|31`
"""
if params is None:
params = {}
tkinter.Frame.__init__(self, parent)
self.fdoc = tkinter.Frame(self)
self.fpar = tkinter.Frame(self)
self.fbut = tkinter.Frame(self)
self.fpar.pack()
self.fbut.pack()
self.fdoc.pack()
self.restore = restore
self.parent = parent
self.input = {}
self.types = {}
self.raise_exception = raise_exception
self._added = {}
self.key_save = key_save
self.command_leave = command_leave
# retrieve previous answers
self._history = []
self._hpos = -1
self.info = {"name": "FrameParams", "param": params,
"help": help, "key_save": key_save}
objs = []
typstr = str # unicode#
if restore:
self._history = _private_restore(
".".join([self.info["name"], self.info["key_save"]]))
if len(self._history) > 0:
self.info["param"].update(self._history[-1])
self._hpos = len(self._history) - 1
for k in self.info["param"]:
self.types[k] = self.info["param"][k].__class__
if self.types[k] in [None, None.__class__]:
self.types[k] = typstr
# documentation
tlab = tkinter.Label(self.fdoc, text="Help")
tlab.pack(side=tkinter.LEFT)
lab = tkinter.Text(self.fdoc, width=width, height=7)
lab.pack(side=tkinter.LEFT)
lab.insert("0.0", self.info["help"])
objs.append(lab)
objs.append(tlab)
scroll = tkinter.Scrollbar(self.fdoc)
scroll.pack(side=tkinter.RIGHT, fill=tkinter.Y)
scroll.config(command=lab.yview, width=5)
lab.config(yscrollcommand=scroll.set)
# next
line = 0
for k in sorted(self.info["param"]):
if k in self._added:
continue
lab = tkinter.Label(self.fpar, text=k)
lab.grid(row=line, column=0)
if k in ["password", "password1", "password2", "password3"]:
lab = tkinter.Entry(self.fpar, width=width, show="*")
else:
lab = tkinter.Entry(self.fpar, width=width)
lab.grid(row=line, column=1)
if self.info["param"][k] is not None:
lab.insert("0", typstr(self.info["param"][k]))
self.input[k] = lab
objs.append(lab)
line += 1
# optional
for k in sorted(self.info["param"]):
if k not in self._added:
continue
lab = tkinter.Label(self.fpar, text=k)
lab.grid(row=line, column=0)
if k in ["password", "password1", "password2", "password3"]:
lab = tkinter.Entry(self.fpar, width=width, show="*")
else:
lab = tkinter.Entry(self.fpar, width=width)
lab.grid(row=line, column=1)
if self.info["param"][k] is not None:
lab.insert("0", typstr(self.info["param"][k]))
self.input[k] = lab
objs.append(lab)
line += 1
# next: button
self.cancel = tkinter.Button(self.fbut, text="cancel or leave")
self.run = tkinter.Button(self.fbut, text=" ok ")
self.cancel.pack(side=tkinter.LEFT)
self.run.pack(side=tkinter.LEFT)
self.run.bind('<Return>', self.run_function)
self.run.bind('<Escape>', self.run_cancel)
self.cancel.config(command=self.run_cancel)
self.run.config(command=self.run_function)
private_adjust_parameters(self.info["param"])
self._already = False
# up, down
self.bup = tkinter.Button(self.fbut, text="up")
self.bdown = tkinter.Button(self.fbut, text="down")
self.bup.pack(side=tkinter.LEFT)
self.bdown.pack(side=tkinter.LEFT)
self.bup.config(command=self.history_up)
self.bdown.config(command=self.history_down)
# keys
for obj in objs + \
[parent, self, self.bup, self.bdown, self.run, self.cancel, self.fdoc]:
obj.bind("<Up>", self.history_up)
obj.bind("<Down>", self.history_down)
obj.bind("<Return>", self.run_function)
obj.bind("<Escape>", self.run_cancel)
[docs] def update(self):
"""
update the parameters (ie ``self.info``)
:githublink:`%|py|158`
"""
typstr = str # unicode#
for k in self.input:
self.input[k].delete(0, tkinter.END)
self.input[k].insert("0", typstr(self.info["param"].get(k, "")))
[docs] def history_up(self, *args):
"""
look back in the history (log of used parameters)
and update the parameters
:githublink:`%|py|168`
"""
if len(self._history) > 0:
self._hpos = (self._hpos + 1) % len(self._history)
self.info["param"].update(self._history[self._hpos])
self.update()
[docs] def history_down(self, *args):
"""
look forward in the history (log of used parameters)
and update the parameters
:githublink:`%|py|178`
"""
if len(self._history) > 0:
self._hpos = (
self._hpos + len(self._history) - 1) % len(self._history)
self.info["param"].update(self._history[self._hpos])
self.update()
[docs] def run_cancel(self, *args):
"""
what to do when Cancel is pressed
:githublink:`%|py|188`
"""
self.info["param"]["__cancel__"] = True
if self.command_leave is not None:
self.command_leave()
else:
self.parent.destroy()
[docs] def get_parameters(self):
"""
returns the parameters
:return: dictionary
:githublink:`%|py|200`
"""
res = {}
for k, v in self.input.items():
s = v.get()
s = s.strip()
if len(s) == 0:
s = None
ty = self.types[k]
res[k] = interpret_parameter(ty, s)
return res
[docs] def get_title(self):
"""
Returns the title.
:return: ``self.info ["name"]``
:githublink:`%|py|216`
"""
return self.info["name"]
[docs] def refresh(self):
"""
Refreshes the screen.
:githublink:`%|py|222`
"""
if self._already:
self.after(1000, self.refresh)
else:
self.run.config(state=tkinter.NORMAL)
self.parent.destroy()
[docs] def run_function(self, *args):
"""
Runs the function.
:githublink:`%|py|232`
"""
self.parent.withdraw()
# self.run.config(state=tkinter.DISABLED)
self._already = True
res = self.get_parameters()
if self.restore:
_private_store(
".".join([self.info["name"], self.info["key_save"]]), res)
self.info["param"].update(res)
self.parent.destroy()
[docs] @staticmethod
def open_window(params,
help_string="",
title="",
top_level_window=None,
key_save="",
do_not_open=False):
"""
Opens a :epkg:`tkinter` window to set up parameters.
It adds entries for the parameters,
it displays the help given to this function.
It also memorizes the latest values used (stored in ``<user>/TEMP folder``).
:param help_string: help to de displayed
:param top_level_window: if you want this window to depend on a top level window from tkinter
:param params: if not None, overwrite values for some parameters,
it will be updated by the function (= returned value)
:param key_save: parameters are saved and restore from a file, key_save will make this file unique
:param title: title of the window
:param do_not_open: do not open the window, let you do it
:return: new parameters (or a the Windows object if *do_not_open* is True)
.. warning:: If the string "__cancel__" is present in the results, it means the users clicked on cancel.
The window looks like:
.. image:: ../../images/open_window_params.png
:align: center
Example::
params = {"velib_key": "", "contract":"Paris"}
newparams = FrameParams.open_window (params, "fetch data from Velib website")
.. versionchanged:: 1.0
Parameter *do_not_open* was added.
:githublink:`%|py|279`
"""
param = params if params is not None else {}
root = top_level_window if top_level_window is not None else create_tk()
ico = os.path.realpath(
os.path.join(os.path.split(__file__)[0], "project_ico.ico"))
fr = FrameParams(
root, params=param, help=help_string, key_save=key_save)
fr.pack()
root.title(title)
if ico is not None and top_level_window is None and sys.platform.startswith(
"win"):
root.wm_iconbitmap(ico)
if top_level_window is None:
fr.focus_set()
root.focus_set()
if do_not_open:
return fr
else:
fr.mainloop()
return param
[docs]def open_window_params(params, help_string="", title="", top_level_window=None,
key_save="", do_not_open=False):
"""
Open a :epkg:`tkinter` window to set up parameters.
It adds entries for the parameters,
it displays the help given to this function.
It also memorizes the latest values used
(stored in `<user>/TEMP` folder).
:param help_string: help to de displayed
:param top_level_window: if you want this window to depend on a top level window from tkinter
:param params: if not None, overwrite values for some parameters,
it will be updated by the function (= returned value)
:param key_save: parameters are saved and restore from a file, key_save will make this file unique
:param title: title of the window
:param do_not_open: do not open the window, let you do it
:return: new parameters (or a the Windows object if *do_not_open* is True)
.. warning:: If the string "__cancel__" is present in the results, it means the users clicked on cancel.
The window looks like:
.. image:: ../../images/open_window_params.png
:align: center
.. exref::
:title: Open a tkinter window to ask parameters to a user
::
params = { "user": os.environ.get("USERNAME", os.environ["USER"]),
"password":"" }
newparams = open_window_params(params, title="try the password *",
help_string="unit test", key_save="my_key")
The program opens a window like the following one:
.. image:: ../../images/open_params.png
:align: center
The parameters ``key_save`` can be ignored but if you use this function
with different parameters, they should all appear after a couple of runs.
That is because the function uses ``key_save`` ot unique the file uses
to store the values for the parameters used in previous execution.
Password are not stored in a text file. You must type them again next time.
:githublink:`%|py|346`
"""
return FrameParams.open_window(params=params, help_string=help_string, title=title,
top_level_window=top_level_window, key_save=key_save,
do_not_open=do_not_open)