This commit is contained in:
Casper da Costa-Luis 2021-01-09 23:24:11 +00:00
parent 63b884d7b6
commit bffe77c508
No known key found for this signature in database
GPG Key ID: 986B408043AE090D
2 changed files with 102 additions and 165 deletions

View File

@ -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)

View File

@ -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: