codekingpro/portable-devtools
114k
1"""2Tkinter GUI progressbar decorator for iterators.3 4Usage:5>>> from tqdm.tk import trange, tqdm6>>> for i in trange(10):7... ...8"""9import re10import sys11import tkinter12import tkinter.ttk as ttk13from warnings import warn14 15from .std import TqdmExperimentalWarning, TqdmWarning16from .std import tqdm as std_tqdm17 18__author__ = {"github.com/": ["richardsheridan", "casperdcl"]}19__all__ = ['tqdm_tk', 'ttkrange', 'tqdm', 'trange']20 21 22class tqdm_tk(std_tqdm): # pragma: no cover23 """24 Experimental Tkinter GUI version of tqdm!25 26 Note: Window interactivity suffers if `tqdm_tk` is not running within27 a Tkinter mainloop and values are generated infrequently. In this case,28 consider calling `tqdm_tk.refresh()` frequently in the Tk thread.29 """30 31 # TODO: @classmethod: write()?32 33 def __init__(self, *args, **kwargs):34 """35 This class accepts the following parameters *in addition* to36 the parameters accepted by `tqdm`.37 38 Parameters39 ----------40 grab : bool, optional41 Grab the input across all windows of the process.42 tk_parent : `tkinter.Wm`, optional43 Parent Tk window.44 cancel_callback : Callable, optional45 Create a cancel button and set `cancel_callback` to be called46 when the cancel or window close button is clicked.47 """48 kwargs = kwargs.copy()49 kwargs['gui'] = True50 # convert disable = None to False51 kwargs['disable'] = bool(kwargs.get('disable', False))52 self._warn_leave = 'leave' in kwargs53 grab = kwargs.pop('grab', False)54 tk_parent = kwargs.pop('tk_parent', None)55 self._cancel_callback = kwargs.pop('cancel_callback', None)56 super().__init__(*args, **kwargs)57 58 if self.disable:59 return60 61 if tk_parent is None: # Discover parent widget62 try:63 tk_parent = tkinter._default_root64 except AttributeError:65 raise AttributeError(66 "`tk_parent` required when using `tkinter.NoDefaultRoot()`")67 if tk_parent is None: # use new default root window as display68 self._tk_window = tkinter.Tk()69 else: # some other windows already exist70 self._tk_window = tkinter.Toplevel()71 else:72 self._tk_window = tkinter.Toplevel(tk_parent)73 74 warn("GUI is experimental/alpha", TqdmExperimentalWarning, stacklevel=2)75 self._tk_dispatching = self._tk_dispatching_helper()76 77 self._tk_window.protocol("WM_DELETE_WINDOW", self.cancel)78 self._tk_window.wm_title(self.desc)79 self._tk_window.wm_attributes("-topmost", 1)80 self._tk_window.after(0, lambda: self._tk_window.wm_attributes("-topmost", 0))81 self._tk_n_var = tkinter.DoubleVar(self._tk_window, value=0)82 self._tk_text_var = tkinter.StringVar(self._tk_window)83 pbar_frame = ttk.Frame(self._tk_window, padding=5)84 pbar_frame.pack()85 _tk_label = ttk.Label(pbar_frame, textvariable=self._tk_text_var,86 wraplength=600, anchor="center", justify="center")87 _tk_label.pack()88 self._tk_pbar = ttk.Progressbar(89 pbar_frame, variable=self._tk_n_var, length=450)90 if self.total is not None:91 self._tk_pbar.configure(maximum=self.total)92 else:93 self._tk_pbar.configure(mode="indeterminate")94 self._tk_pbar.pack()95 if self._cancel_callback is not None:96 _tk_button = ttk.Button(pbar_frame, text="Cancel", command=self.cancel)97 _tk_button.pack()98 if grab:99 self._tk_window.grab_set()100 101 def close(self):102 if self.disable:103 return104 105 self.disable = True106 107 with self.get_lock():108 self._instances.remove(self)109 110 def _close():111 self._tk_window.after('idle', self._tk_window.destroy)112 if not self._tk_dispatching:113 self._tk_window.update()114 115 self._tk_window.protocol("WM_DELETE_WINDOW", _close)116 117 # if leave is set but we are self-dispatching, the left window is118 # totally unresponsive unless the user manually dispatches119 if not self.leave:120 _close()121 elif not self._tk_dispatching:122 if self._warn_leave:123 warn("leave flag ignored if not in tkinter mainloop",124 TqdmWarning, stacklevel=2)125 _close()126 127 def clear(self, *_, **__):128 pass129 130 def display(self, *_, **__):131 self._tk_n_var.set(self.n)132 d = self.format_dict133 # remove {bar}134 d['bar_format'] = (d['bar_format'] or "{l_bar}<bar/>{r_bar}").replace(135 "{bar}", "<bar/>")136 msg = self.format_meter(**d)137 if '<bar/>' in msg:138 msg = "".join(re.split(r'\|?<bar/>\|?', msg, maxsplit=1))139 self._tk_text_var.set(msg)140 if not self._tk_dispatching:141 self._tk_window.update()142 143 def set_description(self, desc=None, refresh=True):144 self.set_description_str(desc, refresh)145 146 def set_description_str(self, desc=None, refresh=True):147 self.desc = desc148 if not self.disable:149 self._tk_window.wm_title(desc)150 if refresh and not self._tk_dispatching:151 self._tk_window.update()152 153 def cancel(self):154 """155 `cancel_callback()` followed by `close()`156 when close/cancel buttons clicked.157 """158 if self._cancel_callback is not None:159 self._cancel_callback()160 self.close()161 162 def reset(self, total=None):163 """164 Resets to 0 iterations for repeated use.165 166 Parameters167 ----------168 total : int or float, optional. Total to use for the new bar.169 """170 if hasattr(self, '_tk_pbar'):171 if total is None:172 self._tk_pbar.configure(maximum=100, mode="indeterminate")173 else:174 self._tk_pbar.configure(maximum=total, mode="determinate")175 super().reset(total=total)176 177 @staticmethod178 def _tk_dispatching_helper():179 """determine if Tkinter mainloop is dispatching events"""180 codes = {tkinter.mainloop.__code__, tkinter.Misc.mainloop.__code__}181 for frame in sys._current_frames().values():182 while frame:183 if frame.f_code in codes:184 return True185 frame = frame.f_back186 return False187 188 189def ttkrange(*args, **kwargs):190 """Shortcut for `tqdm.tk.tqdm(range(*args), **kwargs)`."""191 return tqdm_tk(range(*args), **kwargs)192 193 194# Aliases195tqdm = tqdm_tk196trange = ttkrange197 