Source code for tkinterquickhelper.funcwin.frame_function

# -*- coding: utf-8 -*-
"""
:class:`FrameFunction <tkinterquickhelper.funcwin.frame_function.FrameFunction>`


:githublink:`%|py|6`
"""
import sys
import os
import inspect
import threading
import tkinter
import tkinter.font as tkFont
from io import StringIO
from pyquickhelper.loghelper.flog import fLOG, GetLogFile
from .tk_window import create_tk
from .function_helper import has_unknown_parameters, extract_function_information, private_adjust_parameters, private_get_function
from .storing_functions import _private_restore, _private_store, interpret_parameter


[docs]class FrameFunction(tkinter.Frame): """ Creating a Frame window for a function. It will create an entry control for every parameter. If one of the parameter is 'password', the window will show only stars. The windows proposes to store the value and to restore them on the next call. This functionality is disable when 'password' is present in the list of parameters. :githublink:`%|py|27` """
[docs] def __init__(self, parent, function, restore=True, width=100, raise_exception=False, overwrite=None, hide=False, command_leave=None, key_save="e"): """ :param parent: window parent :param function: function object (can be a string) :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 overwrite: parameters to overwrite :param hide: if True, hide the window after clicking on OK :param command_leave: if not None, this function will be called when clicking on Cancel or Leave :param key_save: suffix to add to the filename used to store parameters :githublink:`%|py|41` """ if overwrite is None: overwrite = {} tkinter.Frame.__init__(self, parent) self.fdoc = tkinter.Frame(self) self.fpar = tkinter.Frame(self) self.flog = tkinter.Frame(self) self.fbut = tkinter.Frame(self) self.fpar.pack() self.fbut.pack() self.flog.pack() self.fdoc.pack() self.restore = restore self.parent = parent self.input = {} self.types = {} self.hide = hide self.raise_exception = raise_exception self._added = {} self.command_leave = command_leave self._suffix = key_save # retieve previous answers self._history = [] self._hpos = -1 typstr = str # unicode# if isinstance(function, typstr): if self.hide: fLOG(__file__, function) # , OutputPrint = False) else: fLOG(__file__, function, # , OutputPrint = False, LogFile=StringIO()) function = private_get_function(function) self.function = function self.info = extract_function_information(function) else: self.function = function self.info = extract_function_information(function) if self.hide: fLOG(__file__, self.info["name"], # , OutputPrint = False, LogFile=StringIO()) else: fLOG(__file__, self.info["name"], # , OutputPrint = False, LogFile=StringIO()) if restore: self._history = _private_restore( self.info["name"] + "." + self._suffix) 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["types"][k] if self.types[k] in [None, None.__class__]: self.types[k] = typstr 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 = [lab, 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) self.fdoc.bind('<Return>', self.run_function) self.fdoc.bind('<Escape>', self.run_cancel) # overwrite parameters if overwrite is not None: for k in self.info["param"]: if k in overwrite: self.info["param"][k] = overwrite[k] # optional parameters in **params has = has_unknown_parameters(function) if has and overwrite is not None: add = [] for a, b in overwrite.items(): if a not in self.info["param"]: add.append((a, b)) for a, b in add: self.info["param"][a] = b self.types[a] = type(b) sig = inspect.signature(function) params = list(_ for _ in sig.parameters.keys() if _ not in ('fLOG',)) if len(params) == 0: raise ValueError( "Unable to extract any parameter from function '{0}'.".format(function)) if params == ['args']: raise ValueError("Extracted weird parameters from function '{0}' (only {1})\nSIG={2}.".format( function, params, sig)) self._added = [_ for _ in self.info["param"] if _ not in params] self.fpar.bind('<Return>', self.run_function) self.fpar.bind('<Escape>', self.run_cancel) # 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 tex = tkinter.Text(self.flog, width=int(width * 3 / 2), height=15) tex.pack() self.LOG = tex tex.config(font=tkFont.Font(family="Courrier New", size=8)) self.cancel = tkinter.Button(self.fbut, text="cancel or leave") self.run = tkinter.Button(self.fbut, text=" run ") self.cancel.pack(side=tkinter.LEFT) self.run.pack(side=tkinter.LEFT) 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 + [tex, 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): """ Updates the parameters (ie ``self.info``). :githublink:`%|py|223` """ 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): """ Looks back in the history of used parameters and change the parameters. :githublink:`%|py|233` """ 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): """ Looks forward in the history of used parameters and change the parameters. :githublink:`%|py|243` """ 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 stop_thread(self): """ Stops the function execution. :githublink:`%|py|253` """ if "thread_started" in self.__dict__: for th in self.thread_started: if th.is_alive(): if not th.daemon: raise Exception( "the thread is not daemon, this case should not happen") th._stop()
[docs] def destroy(self): """ Stops the thread and destroy the function. The behaviour of method `Thread._stop <http://hg.python.org/cpython/file/3.4/Lib/threading.py>`_ changed in Python 3.4, See the `discussion <https://groups.google.com/forum/#!topic/comp.lang.python/sXXwTh9EHsI>`_. :githublink:`%|py|269` """ self.stop_thread() if self.command_leave is not None: f = self.command_leave self.command_leave = None f() else: try: tkinter.Frame.destroy(self) except Exception as e: if "application has been destroyed" in str(e): os._exit(0)
[docs] def run_cancel(self, *args): """ cancel :githublink:`%|py|285` """ if self.command_leave is not None: f = self.command_leave self.command_leave = None f() else: self.parent.destroy()
[docs] def get_parameters(self): """ Returns the parameters in a dictionary. :return: dictionary :githublink:`%|py|298` """ 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): """ :return: self.info ["name"] :githublink:`%|py|312` """ return self.info["name"]
[docs] def refresh(self): """ Refreshes the screen. :githublink:`%|py|318` """ temp = self.LOG.get("0.0", "end") log = GetLogFile().getvalue() log = log[len(temp):] try: self.LOG.insert("end", log) except Exception: self.LOG.insert("end", "\n".join(repr(log).split("\n"))) self.LOG.see("end") if self._already: self.after(1000, self.refresh) else: self.run.config(state=tkinter.NORMAL) if self.hide: self.parent.destroy()
[docs] def run_function(self, *args): """ Runs the function. :githublink:`%|py|339` """ if self.hide: self.parent.withdraw() else: self.run.config(state=tkinter.DISABLED) self._already = True res = self.get_parameters() if self.restore: _private_store(self.info["name"] + "." + self._suffix, res) self._history.append(res) for k in sorted(res): if k in ["password", "password1", "password2", "password3"]: fLOG("parameter ", k, " = ****") else: fLOG("parameter ", k, " = ", res[k], " type ", type(res[k])) fLOG("------------------------") self.after(100, self.refresh) th = FrameFunction_ThreadFunction(self, res) th.daemon = True th.start() if "thread_started" not in self.__dict__: self.thread_started = [] self.thread_started.append(th)
[docs] @staticmethod def open_window(func, top_level_window=None, params=None, key_save="f", do_not_open=False): """ Opens a :epkg:`tkinter` window to run a function. It adds entries for the parameters, it displays the help associated to this function, and it allows use to run the function in a window frame. Logs are also displayed. It also memorizes the latest values used (stored in <user>/TEMP folder). :param func: function (function object) :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 :param key_save: suffix added to the file used to store the parameters :param do_not_open: if True, the function do not open the window but returns it :return: None or windows if *do_not_open* is True The window looks like: .. image:: ../../images/open_window_function.png :align: center Example:: FrameFunction.open_window (file_head) .. versionadded:: 1.0 Parameter *do_not_open* was added. :githublink:`%|py|393` """ 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 = FrameFunction(root, func, overwrite=param, hide=False) fr.pack() root.title(fr.get_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 None
[docs]class FrameFunction_ThreadFunction (threading.Thread): """ Class associated to FrameFunction, it runs the function in a separate thread (in order to be able to stop its execution from the interface). :githublink:`%|py|422` """
[docs] def __init__(self, framewindow, parameter): """ constructor :githublink:`%|py|427` """ threading.Thread.__init__(self) # ne pas oublier cette ligne self.framewindow = framewindow self.parameter = parameter
[docs] def run(self): """ run the thread :githublink:`%|py|435` """ if "function" in self.framewindow.__dict__: function = self.framewindow.function else: function = None param = self.parameter # self.framewindow.info ["param"] if function is not None: if not self.framewindow.raise_exception: try: ret = function(**param) except Exception as e: fLOG("------------------------------ END with exception") fLOG(type(e)) fLOG(str(e)) ret = None fLOG("------------------------------ details") import traceback st = traceback.format_exc() fLOG(st) else: ret = function(**param) else: ret = None fLOG("END") fLOG("result:") fLOG(ret) self.framewindow._already = False
[docs]def open_window_function(func, top_level_window=None, params=None, key_save="f", do_not_open=False): """ Opens a :epkg:`tkinter` window to run a function. It adds entries for the parameters, it displays the help associated to this function, and it allows use to run the function in a window frame. Logs are also displayed. It also memorizes the latest values used (stored in ``<user>/TEMP folder``). :param func: function (function object) :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 :param key_save: suffix added to the file used to store the parameters :param do_not_open: if True, the function do not open the window but returns it :return: None or windows if *do_not_open* is True The window looks like: .. image:: ../../images/open_window_function.png :align: center .. exref:: :title: Open a tkinter windows to run a function:: open_window_function (test_regular_expression) The functions opens a window which looks like the following one: .. image:: ../../images/open_function.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. .. versionadded:: 1.0 Parameter *do_not_open* was added. :githublink:`%|py|505` """ return FrameFunction.open_window(func=func, top_level_window=top_level_window, params=params, key_save=key_save, do_not_open=do_not_open)