diff --git a/tqdm/gui.py b/tqdm/gui.py index 0becd0c1..0d8714b2 100644 --- a/tqdm/gui.py +++ b/tqdm/gui.py @@ -40,11 +40,10 @@ class tqdm_gui(std_tqdm): # pragma: no cover colour = kwargs.pop('colour', 'g') super(tqdm_gui, self).__init__(*args, **kwargs) - # Initialize the GUI display - if self.disable or not kwargs['gui']: + if self.disable: return - warn('GUI is experimental/alpha', TqdmExperimentalWarning, stacklevel=2) + warn("GUI is experimental/alpha", TqdmExperimentalWarning, stacklevel=2) self.mpl = mpl self.plt = plt @@ -69,8 +68,8 @@ class tqdm_gui(std_tqdm): # pragma: no cover ax.set_ylim(0, 0.001) if total is not None: ax.set_xlim(0, 100) - ax.set_xlabel('percent') - self.fig.legend((self.line1, self.line2), ('cur', 'est'), + ax.set_xlabel("percent") + self.fig.legend((self.line1, self.line2), ("cur", "est"), loc='center right') # progressbar self.hspan = plt.axhspan(0, 0.001, xmin=0, xmax=0, color=colour) @@ -78,11 +77,11 @@ class tqdm_gui(std_tqdm): # pragma: no cover # ax.set_xlim(-60, 0) ax.set_xlim(0, 60) ax.invert_xaxis() - ax.set_xlabel('seconds') - ax.legend(('cur', 'est'), loc='lower left') + ax.set_xlabel("seconds") + ax.legend(("cur", "est"), loc='lower left') ax.grid() # ax.set_xlabel('seconds') - ax.set_ylabel((self.unit if self.unit else 'it') + '/s') + ax.set_ylabel((self.unit if self.unit else "it") + "/s") if self.unit_scale: plt.ticklabel_format(style='sci', axis='y', scilimits=(0, 0)) ax.yaxis.get_offset_text().set_x(-0.15) @@ -93,8 +92,6 @@ class tqdm_gui(std_tqdm): # pragma: no cover self.ax = ax def close(self): - # if not self.gui: - # return super(tqdm_gui, self).close() if self.disable: return @@ -175,7 +172,7 @@ class tqdm_gui(std_tqdm): # pragma: no cover line2.set_data(t_ago, zdata) d = self.format_dict - d["ncols"] = 0 + d['ncols'] = 0 ax.set_title(self.format_meter(**d), fontname="DejaVu Sans Mono", fontsize=11) self.plt.pause(1e-9) diff --git a/tqdm/tk.py b/tqdm/tk.py index e82e7202..a12332c3 100644 --- a/tqdm/tk.py +++ b/tqdm/tk.py @@ -7,16 +7,20 @@ Usage: >>> for i in trange(10): ... ... """ -# future division is important to divide integers and get as -# a result precise floating numbers (instead of truncated int) from __future__ import absolute_import, division +import sys from warnings import warn -# to inherit from the tqdm class +try: + import tkinter + import tkinter.ttk as ttk +except ImportError: + import Tkinter as tkinter + import ttk as ttk + from .std import TqdmExperimentalWarning, TqdmWarning from .std import tqdm as std_tqdm -# import compatibility functions and utilities from .utils import _range __author__ = {"github.com/": ["richardsheridan", "casperdcl"]} @@ -32,195 +36,82 @@ class tqdm_tk(std_tqdm): # pragma: no cover consider calling `tqdm_tk.refresh()` frequently in the Tk thread. """ - # TODO: @classmethod: write() on GUI? + # TODO: @classmethod: write()? def __init__(self, *args, **kwargs): """ This class accepts the following parameters *in addition* to - the parameters accepted by tqdm. + the parameters accepted by `tqdm`. Parameters ---------- grab : bool, optional Grab the input across all windows of the process. - tk_parent : tkinter.Wm, optional + tk_parent : `tkinter.Wm`, optional Parent Tk window. cancel_callback : Callable, optional - Create a cancel button and set cancel_callback to be called + Create a cancel button and set `cancel_callback` to be called when the cancel or window close button is clicked. """ - # only meaningful to set this warning if someone sets the flag - self._warn_leave = "leave" in kwargs - try: - grab = kwargs.pop("grab") - except KeyError: - grab = False - try: - tk_parent = kwargs.pop("tk_parent") - except KeyError: - tk_parent = None - try: - self._cancel_callback = kwargs.pop("cancel_callback") - except KeyError: - self._cancel_callback = None - try: - bar_format = kwargs.pop("bar_format") - except KeyError: - bar_format = None - - # Tkinter specific default bar format - if bar_format is None: - kwargs["bar_format"] = ( - "{n_fmt}/{total_fmt}, {rate_noinv_fmt}\n" - "{elapsed} elapsed, {remaining} ETA\n\n" - "{percentage:3.0f}%" - ) - - # This signals std_tqdm that it's a GUI but no need to crash - # Maybe there is a better way? - self.sp = object() - kwargs["gui"] = True - + kwargs = kwargs.copy() + kwargs['gui'] = True + kwargs['bar_format'] = kwargs.get( + 'bar_format', "{l_bar}{r_bar}").replace("{bar}", "") + # convert disable = None to False + kwargs['disable'] = bool(kwargs.get('disable', False)) + colour = kwargs.pop('colour', "blue") + self._warn_leave = 'leave' in kwargs + grab = kwargs.pop('grab', False) + tk_parent = kwargs.pop('tk_parent', None) + self._cancel_callback = kwargs.pop('cancel_callback', None) super(tqdm_tk, self).__init__(*args, **kwargs) if self.disable: return - try: - import tkinter - import tkinter.ttk as ttk - except ImportError: - import Tkinter as tkinter - import ttk as ttk - - # Discover parent widget - if tk_parent is None: - # this will error if tkinter.NoDefaultRoot() called + if tk_parent is None: # Discover parent widget try: tk_parent = tkinter._default_root except AttributeError: - raise ValueError("tk_parent required when using NoDefaultRoot") - if tk_parent is None: - # use new default root window as display + raise AttributeError( + "`tk_parent` required when using `tkinter.NoDefaultRoot()`") + if tk_parent is None: # use new default root window as display self._tk_window = tkinter.Tk() - else: - # some other windows already exist + else: # some other windows already exist self._tk_window = tkinter.Toplevel() else: self._tk_window = tkinter.Toplevel(tk_parent) - warn('GUI is experimental/alpha', TqdmExperimentalWarning, stacklevel=2 - ) + warn('GUI is experimental/alpha', TqdmExperimentalWarning, stacklevel=2) self._tk_dispatching = self._tk_dispatching_helper() self._tk_window.protocol("WM_DELETE_WINDOW", self.cancel) self._tk_window.wm_title(self.desc) self._tk_window.wm_attributes("-topmost", 1) - self._tk_window.after( - 0, - lambda: self._tk_window.wm_attributes("-topmost", 0), - ) + self._tk_window.after(0, lambda: self._tk_window.wm_attributes("-topmost", 0)) self._tk_n_var = tkinter.DoubleVar(self._tk_window, value=0) self._tk_text_var = tkinter.StringVar(self._tk_window) pbar_frame = ttk.Frame(self._tk_window, padding=5) pbar_frame.pack() - _tk_label = ttk.Label( - pbar_frame, - textvariable=self._tk_text_var, - wraplength=600, - anchor="center", - justify="center", - ) + _tk_label = ttk.Label(pbar_frame, textvariable=self._tk_text_var, + wraplength=600, anchor="center", justify="center") _tk_label.pack() self._tk_pbar = ttk.Progressbar( - pbar_frame, - variable=self._tk_n_var, - length=450, - ) + pbar_frame, variable=self._tk_n_var, length=450) + # WIP: is this the best way to set colour? + # WIP: what about error/complete (green/red) as with notebook? + self._tk_pbar.tk_setPalette(colour) if self.total is not None: self._tk_pbar.configure(maximum=self.total) else: self._tk_pbar.configure(mode="indeterminate") self._tk_pbar.pack() if self._cancel_callback is not None: - _tk_button = ttk.Button( - pbar_frame, - text="Cancel", - command=self.cancel, - ) + _tk_button = ttk.Button(pbar_frame, text="Cancel", command=self.cancel) _tk_button.pack() if grab: self._tk_window.grab_set() - def set_description(self, desc=None, refresh=True): - self.set_description_str(desc, refresh) - - def set_description_str(self, desc=None, refresh=True): - self.desc = desc - if not self.disable: - self._tk_window.wm_title(desc) - if refresh and not self._tk_dispatching: - self._tk_window.update() - - def refresh(self, nolock=True, lock_args=None): - """ - Force refresh the display of this bar. - - Parameters - ---------- - nolock : bool, optional - Ignored, behaves as if always set True - lock_args : tuple, optional - Ignored - """ - nolock = True # necessary to force true or is default true enough? - return super(tqdm_tk, self).refresh(nolock, lock_args) - - def display(self): - self._tk_n_var.set(self.n) - self._tk_text_var.set( - self.format_meter( - n=self.n, - total=self.total, - elapsed=self._time() - self.start_t, - ncols=None, - prefix=self.desc, - ascii=self.ascii, - unit=self.unit, - unit_scale=self.unit_scale, - rate=1 / self.avg_time if self.avg_time else None, - bar_format=self.bar_format, - postfix=self.postfix, - unit_divisor=self.unit_divisor, - ) - ) - if not self._tk_dispatching: - self._tk_window.update() - - def cancel(self): - """Call cancel_callback and then close the progress bar - - Called when the window close or cancel buttons are clicked.""" - if self._cancel_callback is not None: - self._cancel_callback() - self.close() - - def reset(self, total=None): - """ - Resets to 0 iterations for repeated use. - - Parameters - ---------- - total : int or float, optional. Total to use for the new bar. - If not set, transform progress bar to indeterminate mode. - """ - if not self.disable: - if total is None: - self._tk_pbar.configure(maximum=100, mode="indeterminate") - else: - self._tk_pbar.configure(maximum=total, mode="determinate") - super(tqdm_tk, self).reset(total) - def close(self): if self.disable: return @@ -247,17 +138,66 @@ class tqdm_tk(std_tqdm): # pragma: no cover TqdmWarning, stacklevel=2) _close() + def clear(self, *_, **__): + pass + + def display(self): + self._tk_n_var.set(self.n) + d = self.format_dict + d['ncols'] = None + self._tk_text_var.set(self.format_meter(**d)) + if not self._tk_dispatching: + self._tk_window.update() + + def set_description(self, desc=None, refresh=True): + self.set_description_str(desc, refresh) + + def set_description_str(self, desc=None, refresh=True): + self.desc = desc + if not self.disable: + self._tk_window.wm_title(desc) + if refresh and not self._tk_dispatching: + self._tk_window.update() + + def refresh(self, nolock=True, **kwargs): # WIP: why is this needed? + """ + Force refresh the display of this bar. + + Parameters + ---------- + nolock : bool, optional + Ignored, behaves as if always `True`. + """ + return super(tqdm_tk, self).refresh(nolock=True, **kwargs) + + def cancel(self): + """ + `cancel_callback()` followed by `close()` + when close/cancel buttons clicked. + """ + if self._cancel_callback is not None: + self._cancel_callback() + self.close() + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + if hasattr(self, '_tk_pbar'): + if total is None: + self._tk_pbar.configure(maximum=100, mode="indeterminate") + else: + self._tk_pbar.configure(maximum=total, mode="determinate") + super(tqdm_tk, self).reset(total=total) + @staticmethod def _tk_dispatching_helper(): """determine if Tkinter mainloop is dispatching events""" - try: - import tkinter - except ImportError: - import Tkinter as tkinter - import sys - - codes = set((tkinter.mainloop.__code__, - tkinter.Misc.mainloop.__code__)) + codes = {tkinter.mainloop.__code__, tkinter.Misc.mainloop.__code__} for frame in sys._current_frames().values(): while frame: if frame.f_code in codes: