summaryrefslogtreecommitdiffstats
path: root/tqdm/tk.py
diff options
context:
space:
mode:
Diffstat (limited to 'tqdm/tk.py')
-rw-r--r--tqdm/tk.py207
1 files changed, 207 insertions, 0 deletions
diff --git a/tqdm/tk.py b/tqdm/tk.py
new file mode 100644
index 0000000..92adb51
--- /dev/null
+++ b/tqdm/tk.py
@@ -0,0 +1,207 @@
+"""
+Tkinter GUI progressbar decorator for iterators.
+
+Usage:
+>>> from tqdm.tk import trange, tqdm
+>>> for i in trange(10):
+... ...
+"""
+from __future__ import absolute_import, division
+
+import re
+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
+ # convert disable = None to False
+ kwargs['disable'] = bool(kwargs.get('disable', False))
+ 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)
+ 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
+ # remove {bar}
+ d['bar_format'] = (d['bar_format'] or "{l_bar}<bar/>{r_bar}").replace(
+ "{bar}", "<bar/>")
+ msg = self.format_meter(**d)
+ if '<bar/>' in msg:
+ msg = "".join(re.split(r'\|?<bar/>\|?', msg, 1))
+ self._tk_text_var.set(msg)
+ 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 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.tk.tqdm(xrange(*args), **kwargs)`.
+ On Python3+, `range` is used instead of `xrange`.
+ """
+ return tqdm_tk(_range(*args), **kwargs)
+
+
+# Aliases
+tqdm = tqdm_tk
+trange = ttkrange