Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tqdm tk #1006

Merged
merged 25 commits into from
Jan 10, 2021
Merged

Tqdm tk #1006

Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e697193
Merge pull request #1108 from tqdm/devel
casperdcl Jan 9, 2021
2de334d
Add tqdm_tk and ttk_range to tqdm.gui
richardsheridan Jul 15, 2020
ee453f4
Optional cancel button
richardsheridan Jul 15, 2020
9a2bf6a
Code quality, py2 compat
richardsheridan Jul 15, 2020
3d9bad7
py27 kwargs compat, improve desc display
richardsheridan Jul 16, 2020
dea3109
subclass std_tqdm instead
richardsheridan Jul 16, 2020
5620dff
re-enable monitor thread
richardsheridan Jul 20, 2020
c40230e
Initially bring window to front
richardsheridan Jul 20, 2020
82cf621
early construction of tk_desc_label
richardsheridan Jul 20, 2020
f349fad
avoid tkinter imports and startup if initially disabled
richardsheridan Jul 20, 2020
244132d
make tkinter objects "private"
richardsheridan Jul 20, 2020
3ebb867
make desc appear only in title bar
richardsheridan Jul 20, 2020
22c4c7b
better docstring for tqdm_tk, tqdm_tk.cancel
richardsheridan Jul 20, 2020
ff7cdcc
make tqdm_tk the default tqdm_gui
richardsheridan Jul 20, 2020
4303ba3
clean up __init__
richardsheridan Jul 21, 2020
10e9d3b
reset can change determinate/indeterminate mode
richardsheridan Jul 21, 2020
bf33319
document additional params
richardsheridan Jul 21, 2020
c7c88fb
updates based on experience from usage
richardsheridan Oct 25, 2020
bc1bd7e
satisfy codacy
richardsheridan Oct 26, 2020
059e021
remove redundant flag toggle
richardsheridan Oct 26, 2020
63b884d
restore original tqdm.gui
casperdcl Jan 9, 2021
bffe77c
tk: tidy
casperdcl Jan 9, 2021
418f99a
respond to comments
richardsheridan Jan 10, 2021
13ccf1d
cut out default pipes from {l_bar}{r_bar}
richardsheridan Jan 10, 2021
6fb8cc8
more pipe removal
casperdcl Jan 10, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions tqdm/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

class tqdm_gui(std_tqdm): # pragma: no cover
"""
Experimental GUI version of tqdm!
Experimental Matplotlib GUI version of tqdm!
"""

# TODO: @classmethod: write() on GUI?
Expand All @@ -40,11 +40,10 @@ def __init__(self, *args, **kwargs):
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

Expand All @@ -69,20 +68,20 @@ def __init__(self, *args, **kwargs):
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)
else:
# 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)
Expand All @@ -93,8 +92,6 @@ def __init__(self, *args, **kwargs):
self.ax = ax

def close(self):
# if not self.gui:
# return super(tqdm_gui, self).close()
if self.disable:
return

Expand Down Expand Up @@ -175,7 +172,7 @@ def display(self):
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)

Expand Down
219 changes: 219 additions & 0 deletions tqdm/tk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
"""
GUI progressbar decorator for iterators.
Includes a default `range` iterator printing to `stderr`.

Usage:
>>> from tqdm.gui import trange, tqdm
>>> for i in trange(10):
... ...
"""
from __future__ import absolute_import, division

import sys
from warnings import warn

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
from .utils import _range

__author__ = {"github.com/": ["richardsheridan", "casperdcl"]}
__all__ = ['tqdm_tk', 'ttkrange', 'tqdm', 'trange']


class tqdm_tk(std_tqdm): # pragma: no cover
"""
Experimental Tkinter GUI version of tqdm!

Note: Window interactivity suffers if `tqdm_tk` is not running within
a Tkinter mainloop and values are generated infrequently. In this case,
consider calling `tqdm_tk.refresh()` frequently in the Tk thread.
"""

# TODO: @classmethod: write()?

def __init__(self, *args, **kwargs):
"""
This class accepts the following parameters *in addition* to
the parameters accepted by `tqdm`.

Parameters
----------
grab : bool, optional
Grab the input across all windows of the process.
tk_parent : `tkinter.Wm`, optional
Parent Tk window.
cancel_callback : Callable, optional
Create a cancel button and set `cancel_callback` to be called
when the cancel or window close button is clicked.
"""
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

if tk_parent is None: # Discover parent widget
try:
tk_parent = tkinter._default_root
except AttributeError:
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
self._tk_window = tkinter.Toplevel()
else:
self._tk_window = tkinter.Toplevel(tk_parent)

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_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.pack()
self._tk_pbar = ttk.Progressbar(
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.pack()
if grab:
self._tk_window.grab_set()

def close(self):
if self.disable:
return

self.disable = True

with self.get_lock():
self._instances.remove(self)

def _close():
self._tk_window.after('idle', self._tk_window.destroy)
if not self._tk_dispatching:
self._tk_window.update()

self._tk_window.protocol("WM_DELETE_WINDOW", _close)

# if leave is set but we are self-dispatching, the left window is
# totally unresponsive unless the user manually dispatches
if not self.leave:
_close()
elif not self._tk_dispatching:
if self._warn_leave:
warn('leave flag ignored if not in tkinter mainloop',
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"""
codes = {tkinter.mainloop.__code__, tkinter.Misc.mainloop.__code__}
for frame in sys._current_frames().values():
while frame:
if frame.f_code in codes:
return True
frame = frame.f_back
return False


def ttkrange(*args, **kwargs):
"""
A shortcut for `tqdm.gui.tqdm_tk(xrange(*args), **kwargs)`.
On Python3+, `range` is used instead of `xrange`.
"""
return tqdm_tk(_range(*args), **kwargs)


# Aliases
tqdm = tqdm_tk
trange = ttkrange