-
-
Notifications
You must be signed in to change notification settings - Fork 32k
tk busy command #72684
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
Comments
tcl tk 8.6.6 has a new busy command. The new tkinter library doesn't provide an interface for this command. https://www.tcl.tk/man/tcl/TkCmd/busy.htm The solution is to add to the class Misc of tkinter these methods: def tk_busy(self, *args, **kw):
self.tk_busy_hold(*args, **kw)
def tk_buy_hold(self, cnf=None, **kw)
self.tk.call(('tk', 'busy', 'hold', self._w) + self._options(cnf, kw))
def tk_buy_configure(self, cnf=None, **kw):
self.tk.call(('tk', 'busy', 'configure', self._w) + self._options(cnf, kw))
def tk_cget(self, option):
return self.tk.call('tk', 'busy', 'cget', self._w, option)
def tk_busy_forget(self):
self.tk_call('tk', 'busy', 'forget', self._w)
def tk_busy_current(self, pattern=None):
if pattern is None:
self.tk.call('tk', 'busy', 'current')
else:
self.tk.call('tk', 'busy', 'current', pattern)
def tk_busy_status(self):
return self.tk.call('tk', 'busy', 'status', self._w) |
Funny thing, just today I came across the same issue. def busy(self, **kw):
'''Shortcut for the busy_hold() command.'''
self.tk.call(('tk', 'busy', self._w) + self._options(kw))
def busy_cget(self, option):
'''Queries the busy command configuration options for
this window.
The window must have been previously made busy by
the busy_hold() command. Returns the present value
of the specified option. Option may have
any of the values accepted by busy_hold().'''
return(self.tk.call('tk', 'busy', 'cget', self._w, '-'+option))
def busy_configure(self, cnf=None, **kw):
'''Queries or modifies the tk busy command configuration
options. The window must have been previously made busy by
busy_hold(). Option may have any of the values accepted by
busy_hold().
Please note that the option database is referenced by the
window. For example, if a Frame widget is to be made busy,
the busy cursor can be specified for it by :
w.option_add("*Frame.BusyCursor", "gumby")'''
if kw:
cnf = _cnfmerge((cnf, kw))
elif cnf:
cnf = _cnfmerge(cnf)
if cnf is None:
return(self._getconfigure(
'tk', 'busy', 'configure', self._w))
if type(cnf) is StringType:
return(self._getconfigure1(
'tk', 'busy', 'configure', self._w, '-'+cnf))
self.tk.call((
'tk', 'busy', 'configure', self._w) + self._options(cnf))
busy_config = busy_configure
def busy_current(self, pattern=None):
'''Returns the widget objects of all widgets that are
currently busy.
If a pattern is given, only the path names of busy widgets
matching PATTERN are returned. '''
return([self._nametowidget(x) for x in
self.tk.splitlist(self.tk.call(
'tk', 'busy', 'current', self._w, pattern))])
def busy_forget(self):
'''Releases resources allocated by the busy() command for
this window, including the transparent window. User events will
again be received by the window. Resources are also released
when the window is destroyed. The window must have been
specified in the busy_hold() operation, otherwise an
exception is raised.'''
self.tk.call('tk', 'busy', 'forget', self._w)
def busy_hold(self, **kw):
'''Makes this window (and its descendants in the Tk window
hierarchy) appear busy. A transparent window is put in front
of the specified window. This transparent window is mapped
the next time idle tasks are processed, and the specified
window and its descendants will be blocked from user
interactions. Normally update() should be called immediately
afterward to insure that the hold operation is in effect before
the application starts its processing. The following
configuration options are valid:
-cursor cursorName
Specifies the cursor to be displayed when the widget
is made busy. CursorName can be in any form accepted
by Tk_GetCursor. The default cursor is "wait" on
Windows and "watch" on other platforms.'''
self.tk.call((
'tk', 'busy', 'hold', self._w) + self._options(kw))
def busy_status(self):
'''Returns the busy status of this window.
If the window presently can not receive user interactions,
True is returned, otherwise False.'''
return((self.tk.getboolean(self.tk.call(
'tk', 'busy', 'status', self._w)) and True) or False) |
Oops, just made a quick test with Python3, and found that I had missed that types.StringType is no longer present. So to be compatible with Python3 the busy_configure() func ought to look like: def busy_configure(self, cnf=None, **kw):
if kw:
cnf = _cnfmerge((cnf, kw))
elif cnf:
cnf = _cnfmerge(cnf)
if cnf is None:
return(self._getconfigure(
'tk', 'busy', 'configure', self._w))
if isinstance(cnf, str):
return(self._getconfigure1(
'tk', 'busy', 'configure', self._w, '-'+cnf))
self.tk.call((
'tk', 'busy', 'configure', self._w) + self._options(cnf)) |
Maybe it's better to add also these methods: Many other methods in tkinter module follow the same pattern. For example: iconbitmap = wm_iconbitmap
iconify = wm_iconify
group = wm_group
geometry = wm_geometry
focusmodel = wm_focusmodel
frame = wm_frame |
Ok, I investigated this a little further. First I noticed another bug with the code from my first post, the "self._w" must be omitted from the call to busy_current(), so the func should look like: def busy_current(self, pattern=None):
return([self._nametowidget(x) for x in
self.tk.splitlist(self.tk.call(
'tk', 'busy', 'current', pattern))]) I did then some more testing, the code now seems to work flawlessly both with Python-3.5.2 on windows 10 and with Python 2.7 on debian Jessie. So I finally prepared a patch (against the __init__.py file from the 3.5.2 windows installer). Following Miguel's suggestion I also added aliases prefixed with tk_ to the various busy_... methods. try: root = Tkinter.Tk()
f = Tkinter.Frame(root, name='f')
f.pack(fill='both', expand=1)
b=Tkinter.Button(f, name='b', text='hi', command=root.bell)
b.pack(padx=100, pady=100)
top = Tkinter.Toplevel(root, name='top')
def test1():
root.tk_busy()
def test2():
root.tk_busy_forget()
def test3():
root.tk_busy_hold(cursor='gumby')
def test4():
root.tk_busy_forget()
def test5():
root.tk_busy_hold()
top.tk_busy(cursor='gumby')
print(root.tk_busy_current())
print(root.tk_busy_current(pattern='*t*'))
def test6():
print(root.tk_busy_current())
def test7():
root.tk_busy_configure(cursor='gumby')
def test8():
print(root.tk_busy_configure())
print(root.tk_busy_configure('cursor'))
print(root.tk_busy_cget('cursor'))
def test9():
print(root.tk_busy_status())
def test10():
root.tk_busy_forget()
print(root.tk_busy_status())
print(root.tk_busy_current())
delay = 0
for test in (test1, test2, test3, test4, test5, test6, test7,
test8, test9, test10):
delay += 1000
root.after(delay, test)
root.mainloop() |
Could you provide a patch against the default branch? See https://docs.python.org/devguide/ for help. The patch should include not just changes to the tkinter module, but tests for new methods (add new class in Lib/tkinter/test/test_tkinter/test_widgets.py). Unfortunately there is no appropriate place for documenting this feature in the module documentation, but new methods should have docstrings. Would be nice to add an entry in Doc/whatsnew/3.7.rst. |
Thanks klappnase for your collaboration. I dont understand this function: This pattern is not used in other functions that make use of self.tk.getboolean. These functions simply returns the value of self.tk.getboolean directly. The code of your function busy_configure() is very similar to Misc._configure(). I think that you are duplicating code. Other functions related to configuration like pack_configure() and place_configure() simply use self._options(). For example: def place_configure(self, cnf={}, **kw):
self.tk.call(
('place', 'configure', self._w)
+ self._options(cnf, kw)) I think that my proposal can do the job well. It follows the same pattern than the other functions: But I am not totally sure whether it's better to call directly Misc._configure or Misc._options in this situation. Also if we follow the naming convention used in tkinter, it seems that we have to define first tk_busy_configure and then make this assignation: |
Misc._configure is only used when the first Tcl command is the name of the widget. Very probably my proposal for tk_busy_configure is a better candidate because it follows the conventions used in tkinter (it's similar to pack_configure and place_configure): |
@miguel About busy_status(): @serhiy |
Hi klappnase, I think that it's interesting to do this change because the function is more general and maybe can be used again in the future for new features in Tcl Tk. This way, we avoid code duplication (Dont Repeat Yourself is one of the philosophes of Python). This is the new version of tk_busy_configure using the mentioned change: It's more concise. The _getboolean function is implemented in _tkinter.c. This is the implementation:
static PyObject *
Tkapp_GetBoolean (self, args)
PyObject *self;
PyObject *args;
{
char *s;
int v;
A priori , I can't appreciate any bug in this function. If there is some bug, it's in another place of the C code. |
Tcl_GetBoolean() converts a boolean string to an integer 0 or 1: and then Py_BuildValue() converts the integer to a Python object: |
Ok. Maybe the bug is here: Misc.getboolean() This is the required change: |
As far as I can see, most internal Tkinter methods use _getboolean(), which currently looks like: def _getboolean(self, string):
"""Internal function."""
if string:
return self.tk.getboolean(string) I am not 100% sure about this, but I figure that when this was written it was intentional that if "string" is an empty string, it should return None instead of (back then) 0 or 1. Today this would probably translate into something like: def _getboolean(self, value):
if not value in ('', None):
return bool(self.tk.getboolean(value)) |
Hi Miguel, def _configure(self, cmd, cnf, kw):
"""Internal function."""
if kw:
cnf = _cnfmerge((cnf, kw))
elif cnf:
cnf = _cnfmerge(cnf)
if cnf is None:
return self._getconfigure(cmd)
if isinstance(cnf, str):
return self._getconfigure1(cmd + ('-'+cnf,))
self.tk.call(cmd + self._options(cnf)) will break all other methods that use configure() and friends. A possible way to work around this might be: def _configure(self, cmd, cnf, kw):
"""Internal function."""
if kw:
cnf = _cnfmerge((cnf, kw))
elif cnf:
cnf = _cnfmerge(cnf)
if not self._w in cmd:
cmd = _flatten((self._w, cmd))
if cnf is None:
return self._getconfigure(cmd)
if isinstance(cnf, str):
return self._getconfigure1(cmd + ('-'+cnf,))
self.tk.call(cmd + self._options(cnf)) Not sure if this is smart, though, it might require some thorough testing. About getboolean() : it seems like tkapp.getboolean() returns 1 or 0 when it is passed an integer value, at least this is still true here for Python-3.4.2: $ python3
Python 3.4.2 (default, Oct 8 2014, 10:45:20)
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from tkinter import *
>>> r=Tk()
>>> getboolean(1)
1
>>> getboolean('1')
True
>>> getboolean('yes')
True
>>> getboolean(True)
True
>>> Probably the best thing to do would be to fix this in the C code. The next best thing I think is to change tkinter.getboolean(), tkinter.Misc.getboolean() and tkinter.Misc._getboolean(). This however leaves a number of Tkinter methods like e.g. Text.compare() which directly use self.tk.getboolean() unresolved. |
Yes, sure. It will break code. Maybe it's better to be explicit than implicit (another Python motto). It's also necessary to change configure(). This is the code for my version of _configure() and configure(): # These used to be defined in Widget:
def configure(self, cnf=None, **kw):
"""Configure resources of a widget.
The semantics of getboolean is clear for me: Transform a true and false value in *Tcl* to an integer value: 1 or 0: def getboolean(s):
"""Convert true and false to integer values 1 and 0."""
return _default_root.tk.getboolean(s) I think that the C implementation of getboolean is right. >>> tk.getboolean("true")
True
>>> tk.getboolean("false")
False
>>> tk.getboolean("0")
False
>>> tk.getboolean("1")
True
>>> tk.getboolean("yes")
True
>>> tk.getboolean("no")
False |
It's also necessary in the same way to adapt these functions: |
Your proposal also makes an extra computation: an item (not) belongs to a list if not self._w in cmd:
cmd = _flatten((self._w, cmd)) Also it's more efficient to use this than the function _flaten() in that situation: if not self._w in cmd: |
Your changed _configure() will also break Canvas/Listbox.itemconfigure(), Menu.entryconfigure() and a number of other methods, Tix is also affected. It will also break third party extensions that use _configure(), like pybwidget. The change to _configure() I suggested otoh leaves all the existing configure()-like methods intact, and it seems at least very unlikely that some third party module uses a configure()-like method that adds the window path name to the cmd-tuple (which indeed would break my _configure() example. However, following the "explicit is better than implicit" motto, I believe the best idea, if _configure() should be changed at all, is to add a new option to let the programmer decide if the window path should be added to the cmd tuple, which defaults to a value that keeps the old behavior intact, as in this example: def _configure(self, cmd, cnf, kw, usewinpath=True):
"""Internal function."""
if kw:
cnf = _cnfmerge((cnf, kw))
elif cnf:
cnf = _cnfmerge(cnf)
if usewinpath:
cmd = _flatten((self._w, cmd))
else:
cmd = _flatten(cmd)
if cnf is None:
return self._getconfigure(cmd)
if isinstance(cnf, str):
return self._getconfigure1(cmd + ('-'+cnf,))
self.tk.call(cmd + self._options(cnf)) Then busy_configure might look like: def busy_configure(self, cnf=None, **kw):
return self._configure(('tk', 'busy', 'configure', self._w),
cnf, kw, usewinpath=False) |
"Also it's more efficient to use this than the function _flaten() in that situation: if not self._w in cmd: The construct with _flatten() was not my invention, it's probably something to discuss with Guido ;) |
Use "hg diff" command for creating a patch. |
This is my point of view: It's only to add self._w in the proper place. Only one line per method Other third party extensions should not rely on _configure() because it's an internal method (it starts with underscore). We have rights to change the semantics of this internal method in any moment without notification. |
Hi Serhiy, + def tk_busy_status(self): tk_busy_status should return the returned value of self.tk.getboolean directly like other methods in Tkinter using self.tk.getboolean. There is no test that shows that self.tk.getboolean is buggy. This is the right implementation: |
Hi, getboolean() converts Tcl strings to Boolean Python values according to the definition of True and False in Tcl. getboolean is only used for converting strings to boolean Python values. It's undefined the behaviour for other things different than strings. |
It's not defined the semantics for things different than strings. |
The bug is that tk_strictMotif (which uses self.tk.getboolean() itself) returns 0 instead of False. I used the "nested" command to point out, that self.tk.getboolean() is broken when used with wantobjects=True, because it does *not* return proper boolean values , i.e. True or False. It is probably the reduction to handling strings only that you mentioned that causes this wrong behavior, because with wantobjects=True self.tk.call() will convert the "0" tk_strictMotif returns into 0 which is not handled properly. With wantobjects=False, it will retunr True/False as expected. Its behavior is not consistent, *that* is the bug. But to be honest, personally I don't give a dime if these calls return True/False or 1/0, I just wanted to make clear why I explicitely converted the output of self.tk.getboolean() in my tk_busy_status function. I believed that proper boolean values (True/False) were the desired thing. And one more time about _configure(): "I like elegancy..." So do I, but can't expanding the functionality of a method by adding a new option be considered a more elegant solution than entirely changing its behavior with the effect of having to change a number of other things, too? (though I admit that the name for this option I picked first should seriously be reconsidered if it's supposed to be called "elegant", I confess that I am notoriously dumb in inventing names :) |
In the C source code that I am reading of tkinter: _tkinter.c. It seems that these parameters are not used:
It seems that it's dead code. I hope that somebody can tell me whether I am right. I suppose that now Python returns always Python objects instead of strings. For this reason, wantobjects is not any more necessary. This is the C code of Tkinter_Create that I am reading: static PyObject *
Tkinter_Create (self, args)
PyObject *self;
PyObject *args;
{
char *screenName = NULL;
char *baseName = NULL;
char *className = NULL;
int interactive = 0; baseName = strrchr (Py_GetProgramName (), '/');
if (baseName != NULL)
baseName++;
else
baseName = Py_GetProgramName ();
className = "Tk";
if (!PyArg_ParseTuple (args, "|zssi",
&screenName, &baseName, &className, &interactive))
return NULL;
And this is the call to Tkinter_Create in Tkinter.py: |
At least with python 3.4 here wantobjects still is valid, and personally I really hope that it remains this way, because I use to set wantobjects=False in my own code to avoid having to deal with errors because of some method or other unexpectedly returning TclObjects instead of Python objects (which has been happening here occasionally ever since they were invented). static TkappObject *
Tkapp_New(const char *screenName, const char *className,
int interactive, int wantobjects, int wantTk, int sync,
const char *use)
{
TkappObject *v;
char *argv0;
v = PyObject_New(TkappObject, (PyTypeObject *) Tkapp_Type);
if (v == NULL)
return NULL;
Py_INCREF(Tkapp_Type);
(...) static PyObject*
Tkapp_CallResult(TkappObject *self)
{
PyObject *res = NULL;
Tcl_Obj *value = Tcl_GetObjResult(self->interp);
if(self->wantobjects) {
/* Not sure whether the IncrRef is necessary, but something
may overwrite the interpreter result while we are
converting it. */
Tcl_IncrRefCount(value);
res = FromObj((PyObject*)self, value);
Tcl_DecrRefCount(value);
} else {
res = unicodeFromTclObj(value);
}
return res;
} |
First, thank you Miguel and klappnase for your patches. But they should be provided in different way, as described in Python Developer’s Guide [1].
This is not my patch. This is regenerated klappnase's. I expected this will allow to use the Rietveld Code Review Tool for reviewing, but unfortunately Rietveld don't accept these patches [2]. Below I added comments to the patch. I agree, that all these complications are not needed. Just use getboolean(). It always returns bool in recent Python. And don't bother about _configure(). Just duplicate the code. It can be refactored later, in separate issue. Since this is a new feature, it can added only in developing version (future 3.7). Forgot about 2.7 and 3.5. If make the patch fast, there is a chance to get it in 3.6 (if release manager accept this). But tests are needed.
There was an attempt to deprecate wantobjects=False (bpo-3015), but it is useful for testing and I think third-party program still can use it. If you encounter an error because some standard method return Tcl_Object, please file a bug. This is considered as a bug, and several similar bugs was fixed in last years. Here are comments to the last klappnase's patch. Please add a serial number to your next patch for easier referring patches. + def tk_busy(self, **kw): Shouldn't it be just an alias to tk_busy_hold? + '''Queries the busy command configuration options for PEP-257: "Multi-line docstrings consist of a summary line just like a one-line docstring, followed by a blank line, followed by a more elaborate description." + any of the values accepted by busy_hold().''' PEP-257: "Unless the entire docstring fits on a line, place the closing quotes on a line by themselves." + return(self.tk.call('tk', 'busy', 'cget', self._w, '-'+option)) Don't use parentheses around the return value. + the busy cursor can be specified for it by : Remove a space before colon, add an empty line after it. + def tk_busy_hold(self, **kw): Since the only supported option is cursor, just declare it. def tk_busy_hold(self, cursor=None): + -cursor cursorName "-cursor cursorName" is not Python syntax for passing arguments. + return((self.tk.getboolean(self.tk.call( Just return the result of self.tk.getboolean(). [1] https://docs.python.org/devguide/ |
Hi Serhiy, thanks for the feedback. "+ def tk_busy(self, **kw): Shouldn't it be just an alias to tk_busy_hold?" Not sure, I figured since it is a separate command in tk I would make it a separate command in Python, too. "+ def tk_busy_hold(self, **kw): Since the only supported option is cursor, just declare it. def tk_busy_hold(self, cursor=None):
" I thought so at first, too, but is this really wise, since if future versions of tk add other options another patch would be required? |
place is just an alias to place_configure despites the fact that in Tk "place"
Okay, let keep general kwarg. |
Ok, I hope I applied all the required changes now. |
Using the same reasoning applied to tk_busy_hold(), we have to change also these methods: and many other to adapt to future changes. |
This is something entirely different, since the commands you list here in tk do not accept keyword arguments at all. |
Why dont we do the job well from the beginning and refactor _configure() and adapt other dependent code? It's not so complicated to change the dependent code. Many people around the world use Tkinter. |
Added a number of style comments on Rietveld. I believe klappnase could address them, but since there are too little time to beta3 and I have a weak hope to push this in 3.6, I addressed them myself. Rewritten docstrings and test. Ned, is it good to add this feature in 3.6? The patch just adds a couple of new methods, it doesn't affect existing programs. |
With ActiveState 8.6.4.1 (the most recent version available) on macOS: ====================================================================== Traceback (most recent call last):
File "/py/dev/36/root/fwd_macports/Library/Frameworks/pytest_10.12.framework/Versions/3.6/lib/python3.6/tkinter/test/test_tkinter/test_misc.py", line 33, in test_tk_busy
f.tk_busy_hold(cursor='gumby')
File "/py/dev/36/root/fwd_macports/Library/Frameworks/pytest_10.12.framework/Versions/3.6/lib/python3.6/tkinter/__init__.py", line 849, in tk_busy_hold
self.tk.call('tk', 'busy', 'hold', self._w, *self._options(kw))
_tkinter.TclError: unknown option "-cursor" |
Ah, yes, this feature is not supported on OSX/Aqua. |
Updated patch skips tests with the cursor option on OSX/Aqua. |
There was a lot of work put into this patch, but I don't think it's been committed yet. Are any of the original authors interested in converting this to a Github pull request on the master branch? Thanks! |
Add tkinter.Misc methods: tk_busy_hold(), tk_busy_configure(), tk_busy_cget(), tk_busy_forget(), tk_busy_current(), and tk_busy_status().
…7684) Add tkinter.Misc methods: tk_busy_hold(), tk_busy_configure(), tk_busy_cget(), tk_busy_forget(), tk_busy_current(), and tk_busy_status().
Uh oh!
There was an error while loading. Please reload this page.
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
Linked PRs
The text was updated successfully, but these errors were encountered: