j4321 / tkcalendar

Calendar widget for Tkinter
https://pypi.python.org/pypi/tkcalendar
GNU General Public License v3.0
97 stars 33 forks source link

DateEntry bug in with Python 3.8 #61

Closed jslvtr closed 4 years ago

jslvtr commented 4 years ago

Hello!

Superb package, thank you so much for making it. I've been testing in Python 3.8 and found a bug when following your example code:

import tkinter as tk
from tkinter import ttk
from tkcalendar import DateEntry

def example3():
    top = tk.Toplevel(root)

    ttk.Label(top, text="Choose date").pack(padx=10, pady=10)

    cal = DateEntry(
        top,
        width=12,
        background="darkblue",
        foreground="white",
        borderwidth=2,
        year=2010,
    )
    cal.pack(padx=10, pady=10)

root = tk.Tk()
ttk.Button(root, text="DateEntry", command=example3).pack(padx=10, pady=10)

root.mainloop()

The error is as follows:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\jose\AppData\Local\Programs\Python\Python38-32\Lib\tkinter\__init__.py", line 1883, in __call__
    return self.func(*args)
  File "C:\Users\jose\AppData\Local\Programs\Python\Python38-32\Lib\tkinter\__init__.py", line 804, in callit
    func(*args)
  File "C:\Users\jose\.virtualenvs\code-9xLWUBmM\lib\site-packages\tkcalendar\dateentry.py", line 202, in _on_theme_change
    self._setup_style()
  File "C:\Users\jose\.virtualenvs\code-9xLWUBmM\lib\site-packages\tkcalendar\dateentry.py", line 160, in _setup_style
    self.style.map('DateEntry', **maps)
  File "C:\Users\jose\AppData\Local\Programs\Python\Python38-32\Lib\tkinter\ttk.py", line 403, in map
    self.tk.call(self._name, "map", style, *_format_mapdict(kw)),
_tkinter.TclError: Invalid state name r

After a lot of debugging, I figured out the reason for this.

The problem starts at dateentry.py:160 as per the stacktrace:

 maps = self.style.map('TCombobox')
if maps:
    self.style.map('DateEntry', **maps)

Line 160 is the third line ^

This goes on to call an internal ttk function, which then has multiple other internal calls.

One of the internal calls is to _format_mapdict, and receives the keyword arguments passed to self.style.map:

...
self.tk.call(self._name, "map", style, *_format_mapdict(kw)),
...

Allegedly that function takes a mapdict and formats it to pass it off to tk.call. This is that function's documentation:

"""Formats mapdict to pass it to tk.call.

    E.g. (script=False):
      {'expand': [('active', 'selected', 'grey'), ('focus', [1, 2, 3, 4])]}

      returns:

      ('-expand', '{active selected} grey focus {1, 2, 3, 4}')"""

However, my debugging suggests that in Python3.8, it's actually returning this: ('-expand', '{ a c t i v e s e l e c t e d } g r e y f o c u s { 1 , 2 , 3 , 4 } ')

This causes big problems, naturally.

The solution seems to be to not pass keyword arguments individually, but to pass the dictionary of keyword arguments itself. Then the _format_mapdict function will work properly:

maps = self.style.map('TCombobox')
if maps:
    self.style.map('DateEntry', maps)

I can make a PR for this if you agree with this analysis.

jslvtr commented 4 years ago

Nevermind, I made a mistake here--clearly passing as not keyword arguments is just passing it as a different argument.

I believe this is caused by a bug in Python, which I have logged in their issue tracker: https://bugs.python.org/issue38661?@ok_message=msg%20355818%20created%0Aissue%2038661%20created&@template=item

Can we keep this open for now, and see what they say?

j4321 commented 4 years ago

Thanks a lot for noticing this and opening an issue in Python. I will leave this issue opened for now and if they decide not to restore the previous behavior, I will fix it in tkcalendar.

j4321 commented 4 years ago

What OS are you using? I have just installed python 3.8.0 in Archlinux and tkcalendar works fine, I have the same output of map as in python 3.7.

jslvtr commented 4 years ago

I am using Windows, which sounds like it would be part of the problem!

j4321 commented 4 years ago

Or maybe this issue is specific to the default windows ttk theme. Can you try to call style.map() but using the clam theme?

j4321 commented 4 years ago

I have made a temporary fix in branch https://github.com/j4321/tkcalendar/tree/fix_63 (I manually input the correct style map).

jslvtr commented 4 years ago

Sounds good, although the fix will be theme-specific :(

j4321 commented 4 years ago

If you give me the output style map for every ttk theme specific to windows (I cannot get them from linux), I can implement a more general fix which takes the theme into account. The themes I already have are 'clam' and 'alt'.

jslvtr commented 4 years ago

I can do! I generated them with this script:

from tkinter import ttk

style = ttk.Style()

for theme in style.theme_names():
    style.theme_use(theme)
    print(theme)
    print(style.map("TCombobox"))
    print()

This is the output:

winnative
{'focusfill': [('readonly', 'focus', 'SystemHighlight')], 'foreground': [('disabled', 'SystemGrayText'), ('readonly', 'focus', 'SystemHighlightText')], 'selectforeground': [('!focus', 'SystemWindowText')], 'fieldbackground': [('readonly', 'SystemButtonFace'), ('disabled', 'SystemButtonFace')], 'selectbackground': [('!focus', 'SystemWindow')]}

clam
{'foreground': [('readonly', 'focus', '#ffffff')], 'fieldbackground': ['readonly focus', '#4a6984', 'readonly', '#dcdad5'], 'background': [('active', '#eeebe7'), ('pressed', '#eeebe7')], 'arrowcolor': [('disabled', '#999999')]}

alt
{'fieldbackground': [('readonly', '#d9d9d9'), ('disabled', '#d9d9d9')], 'arrowcolor': ['disabled', '#a3a3a3']}

default
{'fieldbackground': [('readonly', '#d9d9d9'), ('disabled', '#d9d9d9')], 'arrowcolor': ['disabled', '#a3a3a3']}

classic
{'fieldbackground': [('readonly', '#d9d9d9'), ('disabled', '#d9d9d9')]}

vista
{'focusfill': [('readonly', 'focus', 'SystemHighlight')], 'foreground': [('disabled', 'SystemGrayText'), ('readonly', 'focus', 'SystemHighlightText')], 'selectforeground': [('!focus', 'SystemWindowText')], 'selectbackground': [('!focus', 'SystemWindow')]}

xpnative
{'focusfill': [('readonly', 'focus', 'SystemHighlight')], 'foreground': [('disabled', 'SystemGrayText'), ('readonly', 'focus', 'SystemHighlightText')], 'selectforeground': [('!focus', 'SystemWindowText')], 'selectbackground': [('!focus', 'SystemWindow')]}

I forgot to reply to your earlier comment--I was using clam when I encountered this bug!

j4321 commented 4 years ago

Thanks, I have updated the code in branch https://github.com/j4321/tkcalendar/tree/fix_63. Can you test it? If this works fine for you I will merge it into master.

jslvtr commented 4 years ago

Thank you @j4321 I haven't been able to try it yet I'm afraid, but it looks like a good temporary solution.

jslvtr commented 4 years ago

Hey @j4321 trying this in my project, but noticed the update is not yet in the pip release. Are you going to put it in pip, or wait until a proper fix is in place?

j4321 commented 4 years ago

Hello @jslvtr I would like to take the time to check #62 before publishing the next version, which will include this fix. Hopefully I will be able to do it sometime next week.

jslvtr commented 4 years ago

Sounds good @j4321 , looking forward to using the updated version in my project and the course I'm teaching. Thanks!