mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Sync canonical Python 3.15 GUI libraries and tests (#9283)
Refresh the IDLE, Tkinter and turtle family through update_lib, including the direct canonical text-only helper and image fixture dependencies. Preserve capability/display guards without changing native Tk support. Assisted-by: OpenAI Codex:model-version-unavailable
This commit is contained in:
1 parent
bcfad3554e
commit
b976859e8d
87 files changed
+14930
-803
No files matched your search
Vendored
+47
-2
@@ -1,9 +1,54 @@
|
||||
What's New in IDLE 3.14.z
|
||||
What's New in IDLE 3.15.0
|
||||
(since 3.14.0)
|
||||
Released after 2025-10-07
|
||||
Released on 2026-10-01
|
||||
=========================
|
||||
|
||||
|
||||
gh-152745: When "Run... Customized" with "Restart shell" unchecked
|
||||
while Shell is running code, including waiting for an input('prompt:')
|
||||
response, just report that the shell is executing instead of
|
||||
reporting twice and restarting anyway. Patch by Serhiy Storchaka.
|
||||
|
||||
gh-152743: When an integer entry in IDLE's Settings dialog, such as
|
||||
"Auto squeeze min lines", is deleted, do not save '', which is an
|
||||
invalid configuration value. Patch by Serhiy Storchaka.
|
||||
|
||||
gh-152742: MakeChange the default extension when saving a Shell or
|
||||
Output window '.txt' and list text files before Python files. Their
|
||||
content is not Python source. Patch by Serhiy Storchaka and Claude Code.
|
||||
|
||||
gh-152740: Fill the "In files:" field of IDLE's Find in Files dialog
|
||||
with a full directory path, even for an unsaved editor or the Shell.
|
||||
In the grep output whow which directory was searched. Patch by Serhiy
|
||||
Storchaka and Claude Code.
|
||||
|
||||
gh-152739: Omit the idlelib directory from the path of the IDLE user
|
||||
process. User code imports such as `import help' can no longer import
|
||||
an idlelib submodule (here idlelib.help) instead of the intended
|
||||
top-level module. Patch by Serhiy Storchaka and Claude Code.
|
||||
|
||||
gh-152738: Fix a rare crash in the IDLE editor when the completion
|
||||
window is closed: deleting a key binding for a sequence that is not
|
||||
bound to the virtual event is now ignored instead of raising a
|
||||
ValueError. Patch by Serhiy Storchaka and Claude Code.
|
||||
|
||||
gh-152737: Fix Replace All in the IDLE editor's Replace dialog when the
|
||||
search direction is "Up" and "Wrap around" is off: it now replaces all
|
||||
matches above the current position instead of only the first one.
|
||||
Patch by Serhiy Storchaka and Claude Code.
|
||||
|
||||
gh-152733: For X11 window managers, set toplevel window classes to
|
||||
"Idle". Patch by Serhiy Storchaka and Claude Code.
|
||||
|
||||
gh-152728: Move functions run.fix_scaling, editor.fixwordbreaks
|
||||
(as 'fix_word_breaks') and pyshell.fix_x11_paste to module util.
|
||||
Patch by Terry J. Reedy.
|
||||
|
||||
gh-85320: IDLE now reads and writes its configuration files and the
|
||||
breakpoints file using UTF-8 instead of the locale encoding.
|
||||
Files with non-ASCII characters and non-UTF-8 encoding may need
|
||||
to be opened in an editor and resaved with UTF-8 encoding.
|
||||
|
||||
gh-143774: Better explain the operation of Format / Format Paragraph.
|
||||
Patch by Terry J. Reedy.
|
||||
|
||||
|
||||
Vendored
+7
-1
@@ -42,6 +42,11 @@ def make_pat():
|
||||
]) +
|
||||
r"))"
|
||||
)
|
||||
lazy_softkw = ( # lazy new in 3.15 (+ 2 lines below).
|
||||
r"^[ \t]*" + # at beginning of line + possible indentation
|
||||
r"(?P<LAZY_SOFTKW>lazy)" +
|
||||
r"(?=[ \t]+(?:import|from)\b)" # followed by 'import' or 'from'
|
||||
)
|
||||
builtinlist = [str(name) for name in dir(builtins)
|
||||
if not name.startswith('_') and
|
||||
name not in keyword.kwlist]
|
||||
@@ -56,7 +61,7 @@ def make_pat():
|
||||
prog = re.compile("|".join([
|
||||
builtin, comment, string, kw,
|
||||
match_softkw, case_default,
|
||||
case_softkw_and_pattern,
|
||||
case_softkw_and_pattern, lazy_softkw,
|
||||
any("SYNC", [r"\n"]),
|
||||
]),
|
||||
re.DOTALL | re.MULTILINE)
|
||||
@@ -70,6 +75,7 @@ prog_group_name_to_tag = {
|
||||
"CASE_SOFTKW": "KEYWORD",
|
||||
"CASE_DEFAULT_UNDERSCORE": "KEYWORD",
|
||||
"CASE_SOFTKW2": "KEYWORD",
|
||||
"LAZY_SOFTKW": "KEYWORD",
|
||||
}
|
||||
|
||||
|
||||
|
||||
Vendored
+73
-36
@@ -73,8 +73,9 @@ class IdleConfParser(ConfigParser):
|
||||
|
||||
def Load(self):
|
||||
"Load the configuration file from disk."
|
||||
if self.file:
|
||||
self.read(self.file)
|
||||
if self.file and os.path.exists(self.file):
|
||||
with open(self.file, encoding='utf-8', errors='replace') as f:
|
||||
self.read_file(f)
|
||||
|
||||
class IdleUserConfParser(IdleConfParser):
|
||||
"""
|
||||
@@ -133,10 +134,10 @@ class IdleUserConfParser(IdleConfParser):
|
||||
if fname and fname[0] != '#':
|
||||
if not self.IsEmpty():
|
||||
try:
|
||||
cfgFile = open(fname, 'w')
|
||||
cfgFile = open(fname, 'w', encoding='utf-8')
|
||||
except OSError:
|
||||
os.unlink(fname)
|
||||
cfgFile = open(fname, 'w')
|
||||
cfgFile = open(fname, 'w', encoding='utf-8')
|
||||
with cfgFile:
|
||||
self.write(cfgFile)
|
||||
elif os.path.exists(self.file):
|
||||
@@ -476,34 +477,58 @@ class IdleConf:
|
||||
Keybindings come from GetCurrentKeySet() active key dict,
|
||||
where previously used bindings are disabled.
|
||||
"""
|
||||
keysName = extensionName + '_cfgBindings'
|
||||
activeKeys = self.GetCurrentKeySet()
|
||||
extKeys = {}
|
||||
if self.defaultCfg['extensions'].has_section(keysName):
|
||||
eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
|
||||
for eventName in eventNames:
|
||||
event = '<<' + eventName + '>>'
|
||||
binding = activeKeys[event]
|
||||
extKeys[event] = binding
|
||||
return extKeys
|
||||
bindings_section = f'{extensionName}_cfgBindings'
|
||||
current_keyset = self.GetCurrentKeySet()
|
||||
extension_keys = {}
|
||||
|
||||
def __GetRawExtensionKeys(self,extensionName):
|
||||
event_names = set()
|
||||
if self.userCfg['extensions'].has_section(bindings_section):
|
||||
event_names |= set(
|
||||
self.userCfg['extensions'].GetOptionList(bindings_section)
|
||||
)
|
||||
if self.defaultCfg['extensions'].has_section(bindings_section):
|
||||
event_names |= set(
|
||||
self.defaultCfg['extensions'].GetOptionList(bindings_section)
|
||||
)
|
||||
|
||||
for event_name in event_names:
|
||||
event = f'<<{event_name}>>'
|
||||
binding = current_keyset.get(event, None)
|
||||
if binding is None:
|
||||
continue
|
||||
extension_keys[event] = binding
|
||||
return extension_keys
|
||||
|
||||
def __GetRawExtensionKeys(self, extension_name):
|
||||
"""Return dict {configurable extensionName event : keybinding list}.
|
||||
|
||||
Events come from default config extension_cfgBindings section.
|
||||
Keybindings list come from the splitting of GetOption, which
|
||||
tries user config before default config.
|
||||
"""
|
||||
keysName = extensionName+'_cfgBindings'
|
||||
extKeys = {}
|
||||
if self.defaultCfg['extensions'].has_section(keysName):
|
||||
eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
|
||||
for eventName in eventNames:
|
||||
binding = self.GetOption(
|
||||
'extensions', keysName, eventName, default='').split()
|
||||
event = '<<' + eventName + '>>'
|
||||
extKeys[event] = binding
|
||||
return extKeys
|
||||
bindings_section = f'{extension_name}_cfgBindings'
|
||||
extension_keys = {}
|
||||
|
||||
event_names = set()
|
||||
if self.userCfg['extensions'].has_section(bindings_section):
|
||||
event_names |= set(
|
||||
self.userCfg['extensions'].GetOptionList(bindings_section)
|
||||
)
|
||||
if self.defaultCfg['extensions'].has_section(bindings_section):
|
||||
event_names |= set(
|
||||
self.defaultCfg['extensions'].GetOptionList(bindings_section)
|
||||
)
|
||||
|
||||
for event_name in event_names:
|
||||
binding = self.GetOption(
|
||||
'extensions',
|
||||
bindings_section,
|
||||
event_name,
|
||||
default='',
|
||||
).split()
|
||||
event = f'<<{event_name}>>'
|
||||
extension_keys[event] = binding
|
||||
return extension_keys
|
||||
|
||||
def GetExtensionBindings(self, extensionName):
|
||||
"""Return dict {extensionName event : active or defined keybinding}.
|
||||
@@ -512,18 +537,30 @@ class IdleConf:
|
||||
configurable events (from default config) to GetOption splits,
|
||||
as in self.__GetRawExtensionKeys.
|
||||
"""
|
||||
bindsName = extensionName + '_bindings'
|
||||
extBinds = self.GetExtensionKeys(extensionName)
|
||||
#add the non-configurable bindings
|
||||
if self.defaultCfg['extensions'].has_section(bindsName):
|
||||
eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName)
|
||||
for eventName in eventNames:
|
||||
binding = self.GetOption(
|
||||
'extensions', bindsName, eventName, default='').split()
|
||||
event = '<<' + eventName + '>>'
|
||||
extBinds[event] = binding
|
||||
bindings_section = f'{extensionName}_bindings'
|
||||
extension_keys = self.GetExtensionKeys(extensionName)
|
||||
|
||||
return extBinds
|
||||
# add the non-configurable bindings
|
||||
event_names = set()
|
||||
if self.userCfg['extensions'].has_section(bindings_section):
|
||||
event_names |= set(
|
||||
self.userCfg['extensions'].GetOptionList(bindings_section)
|
||||
)
|
||||
if self.defaultCfg['extensions'].has_section(bindings_section):
|
||||
event_names |= set(
|
||||
self.defaultCfg['extensions'].GetOptionList(bindings_section)
|
||||
)
|
||||
|
||||
for event_name in event_names:
|
||||
binding = self.GetOption(
|
||||
'extensions',
|
||||
bindings_section,
|
||||
event_name,
|
||||
default=''
|
||||
).split()
|
||||
event = f'<<{event_name}>>'
|
||||
extension_keys[event] = binding
|
||||
return extension_keys
|
||||
|
||||
def GetKeyBinding(self, keySetName, eventStr):
|
||||
"""Return the keybinding list for keySetName eventStr.
|
||||
|
||||
Vendored
+49
-18
@@ -1146,8 +1146,8 @@ class KeysPage(Frame):
|
||||
selected keyset. The keybindings are loaded in load_keys_list()
|
||||
and are pairs of (event, [keys]) where keys can be a list
|
||||
of one or more key combinations to bind to the same event.
|
||||
Mouse button 1 click invokes on_bindingslist_select(), which
|
||||
allows button_new_keys to be clicked.
|
||||
Mouse button 1 click or Up or Down key invokes
|
||||
on_bindingslist_select(), which allows button_new_keys to be clicked.
|
||||
|
||||
So, an item is selected in listbindings, which activates
|
||||
button_new_keys, and clicking button_new_keys calls function
|
||||
@@ -1221,9 +1221,12 @@ class KeysPage(Frame):
|
||||
scroll_target_y = Scrollbar(frame_target)
|
||||
scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
|
||||
self.bindingslist = Listbox(
|
||||
frame_target, takefocus=FALSE, exportselection=FALSE)
|
||||
frame_target, takefocus=True, exportselection=FALSE)
|
||||
self.bindingslist.bind('<ButtonRelease-1>',
|
||||
self.on_bindingslist_select)
|
||||
self.bindingslist.bind('<KeyRelease-Up>', self.on_bindingslist_select)
|
||||
self.bindingslist.bind('<KeyRelease-Down>',
|
||||
self.on_bindingslist_select)
|
||||
scroll_target_y['command'] = self.bindingslist.yview
|
||||
scroll_target_x['command'] = self.bindingslist.xview
|
||||
self.bindingslist['yscrollcommand'] = scroll_target_y.set
|
||||
@@ -1427,7 +1430,14 @@ class KeysPage(Frame):
|
||||
self.create_new_key_set(new_keys_name)
|
||||
|
||||
def on_bindingslist_select(self, event):
|
||||
"Activate button to assign new keys to selected action."
|
||||
"""Activate button to assign new keys to selected action.
|
||||
|
||||
Event can result from either mouse click or Up or Down key.
|
||||
The keys move the selection, but not the anchor used by
|
||||
get_new_keys and var_changed_keybinding.
|
||||
"""
|
||||
if event.type.name == 'KeyRelease':
|
||||
self.bindingslist.selection_anchor(ACTIVE)
|
||||
self.button_new_keys.state(('!disabled',))
|
||||
|
||||
def create_new_key_set(self, new_key_set_name):
|
||||
@@ -1465,9 +1475,8 @@ class KeysPage(Frame):
|
||||
|
||||
An action/key binding can be selected to change the key binding.
|
||||
"""
|
||||
reselect = False
|
||||
list_index = 0
|
||||
if self.bindingslist.curselection():
|
||||
reselect = True
|
||||
list_index = self.bindingslist.index(ANCHOR)
|
||||
keyset = idleConf.GetKeySet(keyset_name)
|
||||
# 'set' is dict mapping virtual event to list of key events.
|
||||
@@ -1482,10 +1491,11 @@ class KeysPage(Frame):
|
||||
if bind_name in changes['keys'][keyset_name]:
|
||||
key = changes['keys'][keyset_name][bind_name]
|
||||
self.bindingslist.insert(END, bind_name+' - '+key)
|
||||
if reselect:
|
||||
self.bindingslist.see(list_index)
|
||||
self.bindingslist.select_set(list_index)
|
||||
self.bindingslist.select_anchor(list_index)
|
||||
self.bindingslist.see(list_index)
|
||||
self.bindingslist.select_set(list_index)
|
||||
self.bindingslist.select_anchor(list_index)
|
||||
self.bindingslist.activate(list_index)
|
||||
self.button_new_keys.state(('!disabled',))
|
||||
|
||||
@staticmethod
|
||||
def save_new_key_set(keyset_name, keyset):
|
||||
@@ -1960,12 +1970,15 @@ class ExtPage(Frame):
|
||||
def load_extensions(self):
|
||||
"Fill self.extensions with data from the default and user configs."
|
||||
self.extensions = {}
|
||||
|
||||
for ext_name in idleConf.GetExtensions(active_only=False):
|
||||
# Former built-in extensions are already filtered out.
|
||||
self.extensions[ext_name] = []
|
||||
|
||||
for ext_name in self.extensions:
|
||||
opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
|
||||
default = set(self.ext_defaultCfg.GetOptionList(ext_name))
|
||||
user = set(self.ext_userCfg.GetOptionList(ext_name))
|
||||
opt_list = sorted(default | user)
|
||||
|
||||
# Bring 'enable' options to the beginning of the list.
|
||||
enables = [opt_name for opt_name in opt_list
|
||||
@@ -1975,8 +1988,12 @@ class ExtPage(Frame):
|
||||
opt_list = enables + opt_list
|
||||
|
||||
for opt_name in opt_list:
|
||||
def_str = self.ext_defaultCfg.Get(
|
||||
ext_name, opt_name, raw=True)
|
||||
if opt_name in default:
|
||||
def_str = self.ext_defaultCfg.Get(
|
||||
ext_name, opt_name, raw=True)
|
||||
else:
|
||||
def_str = self.ext_userCfg.Get(
|
||||
ext_name, opt_name, raw=True)
|
||||
try:
|
||||
def_obj = {'True':True, 'False':False}[def_str]
|
||||
opt_type = 'bool'
|
||||
@@ -2054,10 +2071,11 @@ class ExtPage(Frame):
|
||||
default = opt['default']
|
||||
value = opt['var'].get().strip() or default
|
||||
opt['var'].set(value)
|
||||
# if self.defaultCfg.has_section(section):
|
||||
# Currently, always true; if not, indent to return.
|
||||
if (value == default):
|
||||
|
||||
# Only save option in user config if it differs from the default
|
||||
if self.ext_defaultCfg.has_section(section) and value == default:
|
||||
return self.ext_userCfg.RemoveOption(section, name)
|
||||
|
||||
# Set the option.
|
||||
return self.ext_userCfg.SetOption(section, name, value)
|
||||
|
||||
@@ -2116,6 +2134,8 @@ class HelpFrame(LabelFrame):
|
||||
scroll_helplist['command'] = self.helplist.yview
|
||||
self.helplist['yscrollcommand'] = scroll_helplist.set
|
||||
self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
|
||||
self.helplist.bind('<KeyRelease-Up>', self.help_source_selected)
|
||||
self.helplist.bind('<KeyRelease-Down>', self.help_source_selected)
|
||||
|
||||
frame_buttons = Frame(self)
|
||||
self.button_helplist_edit = Button(
|
||||
@@ -2138,7 +2158,14 @@ class HelpFrame(LabelFrame):
|
||||
self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
|
||||
|
||||
def help_source_selected(self, event):
|
||||
"Handle event for selecting additional help."
|
||||
"""Handle event for selecting additional help.
|
||||
|
||||
Event can result from either mouse click or Up or Down key.
|
||||
The keys move the selection, but not the anchor used by
|
||||
helplist_item_edit and helplist_item_remove.
|
||||
"""
|
||||
if event.type.name == 'KeyRelease':
|
||||
self.helplist.selection_anchor(ACTIVE)
|
||||
self.set_add_delete_state()
|
||||
|
||||
def set_add_delete_state(self):
|
||||
@@ -2257,7 +2284,11 @@ class VarTrace:
|
||||
"Return default callback function to add values to changes instance."
|
||||
def default_callback(*params):
|
||||
"Add config values to changes instance."
|
||||
changes.add_option(*config, var.get())
|
||||
value = var.get()
|
||||
# A blanked int entry is an empty string; do not save it as an
|
||||
# invalid config value (gh-83653).
|
||||
if value != '':
|
||||
changes.add_option(*config, value)
|
||||
return default_callback
|
||||
|
||||
def attach(self):
|
||||
|
||||
Vendored
+26
-16
@@ -312,6 +312,9 @@ class EditorWindow:
|
||||
else:
|
||||
self.update_menu_state('options', '*ine*umbers', 'disabled')
|
||||
|
||||
self.mtime = self.last_mtime()
|
||||
text_frame.bind('<FocusIn>', self.focus_in_event)
|
||||
|
||||
def handle_winconfig(self, event=None):
|
||||
self.set_width()
|
||||
|
||||
@@ -857,9 +860,8 @@ class EditorWindow:
|
||||
self.text.event_delete(event, *keylist)
|
||||
for extensionName in self.get_standard_extension_names():
|
||||
xkeydefs = idleConf.GetExtensionBindings(extensionName)
|
||||
if xkeydefs:
|
||||
for event, keylist in xkeydefs.items():
|
||||
self.text.event_delete(event, *keylist)
|
||||
for event, keylist in xkeydefs.items():
|
||||
self.text.event_delete(event, *keylist)
|
||||
|
||||
def ApplyKeybindings(self):
|
||||
"""Apply the virtual, configurable keybindings.
|
||||
@@ -1027,6 +1029,8 @@ class EditorWindow:
|
||||
|
||||
def set_saved(self, flag):
|
||||
self.undo.set_saved(flag)
|
||||
if flag:
|
||||
self.mtime = self.last_mtime()
|
||||
|
||||
def reset_undo(self):
|
||||
self.undo.reset_undo()
|
||||
@@ -1112,6 +1116,21 @@ class EditorWindow:
|
||||
# unless override: unregister from flist, terminate if last window
|
||||
self.close_hook()
|
||||
|
||||
def last_mtime(self):
|
||||
file = self.io.filename
|
||||
return os.path.getmtime(file) if file else 0
|
||||
|
||||
def focus_in_event(self, event):
|
||||
mtime = self.last_mtime()
|
||||
if self.mtime != mtime:
|
||||
self.mtime = mtime
|
||||
if self. askyesno(
|
||||
'Reload', '"%s"\n\nThis script has been modified by another program.'
|
||||
'\nDo you want to reload it?' % self.io.filename, parent=self.text):
|
||||
self.io.loadfile(self.io.filename)
|
||||
else:
|
||||
self.set_saved(False)
|
||||
|
||||
def load_extensions(self):
|
||||
self.extensions = {}
|
||||
self.load_standard_extensions()
|
||||
@@ -1685,19 +1704,10 @@ def get_accelerator(keydefs, eventname):
|
||||
return s
|
||||
|
||||
|
||||
def fixwordbreaks(root):
|
||||
# On Windows, tcl/tk breaks 'words' only on spaces, as in Command Prompt.
|
||||
# We want Motif style everywhere. See #21474, msg218992 and followup.
|
||||
tk = root.tk
|
||||
tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
|
||||
tk.call('set', 'tcl_wordchars', r'\w')
|
||||
tk.call('set', 'tcl_nonwordchars', r'\W')
|
||||
|
||||
|
||||
def _editor_window(parent): # htest #
|
||||
# error if close master window first - timer event, after script
|
||||
root = parent
|
||||
fixwordbreaks(root)
|
||||
def _editor_window(root): # htest #
|
||||
# Error if close master window first - timer event, after script
|
||||
from util import fix_word_breaks
|
||||
fix_word_breaks(root)
|
||||
if sys.argv[1:]:
|
||||
filename = sys.argv[1]
|
||||
else:
|
||||
|
||||
Vendored
+3
-3
@@ -112,12 +112,12 @@ class FileList:
|
||||
|
||||
|
||||
def _test(): # TODO check and convert to htest
|
||||
# Maybe redundant with test_filelist.FileListTest.test_new_empty.
|
||||
from tkinter import Tk
|
||||
from idlelib.editor import fixwordbreaks
|
||||
from idlelib.run import fix_scaling
|
||||
from idlelib.util import fix_scaling, fix_word_breaks
|
||||
root = Tk()
|
||||
fix_scaling(root)
|
||||
fixwordbreaks(root)
|
||||
fix_word_breaks(root)
|
||||
root.withdraw()
|
||||
flist = FileList(root)
|
||||
flist.new()
|
||||
|
||||
Vendored
+15
-5
@@ -40,6 +40,20 @@ def grep(text, io=None, flist=None):
|
||||
dialog.open(text, searchphrase, io)
|
||||
|
||||
|
||||
def default_glob(path):
|
||||
"""Return the initial "In files:" pattern for a file path (gh-80504).
|
||||
|
||||
Always include a full directory so that grep output shows which
|
||||
directory was searched.
|
||||
"""
|
||||
dir, base = os.path.split(path)
|
||||
dir = os.path.abspath(dir) # An empty dir becomes the current directory.
|
||||
head, tail = os.path.splitext(base)
|
||||
if not tail:
|
||||
tail = ".py"
|
||||
return os.path.join(dir, "*" + tail)
|
||||
|
||||
|
||||
def walk_error(msg):
|
||||
"Handle os.walk error."
|
||||
print(msg)
|
||||
@@ -103,11 +117,7 @@ class GrepDialog(SearchDialogBase):
|
||||
path = io.filename or ""
|
||||
else:
|
||||
path = ""
|
||||
dir, base = os.path.split(path)
|
||||
head, tail = os.path.splitext(base)
|
||||
if not tail:
|
||||
tail = ".py"
|
||||
self.globvar.set(os.path.join(dir, "*" + tail))
|
||||
self.globvar.set(default_glob(path))
|
||||
|
||||
def create_entries(self):
|
||||
"Create base entry widgets and add widget for search path."
|
||||
|
||||
Vendored
+1
-1
@@ -18,7 +18,7 @@ The idle directory, idlelib, has over 60 xyz.py files. The idle_test
|
||||
subdirectory contains test_xyz.py for each implementation file xyz.py.
|
||||
To add a test for abc.py, open idle_test/template.py and immediately
|
||||
Save As test_abc.py. Insert 'abc' on the first line, and replace
|
||||
'zzdummy' with 'abc.
|
||||
'zzdummy' with 'abc'.
|
||||
|
||||
Remove the imports of requires and tkinter if not needed. Otherwise,
|
||||
add to the tkinter imports as needed.
|
||||
|
||||
Vendored
+3
-3
@@ -216,9 +216,9 @@ _module_browser_spec = {
|
||||
'file': 'browser',
|
||||
'kwds': {},
|
||||
'msg': textwrap.dedent("""
|
||||
"Inspect names of module, class(with superclass if applicable),
|
||||
"methods and functions. Toggle nested items. Double clicking
|
||||
"on items prints a traceback for an exception that is ignored.""")
|
||||
Inspect names of module, class(with superclass if applicable),
|
||||
methods and functions. Toggle nested items. Double clicking
|
||||
on items prints a traceback for an exception that is ignored.""")
|
||||
}
|
||||
|
||||
_multistatus_bar_spec = {
|
||||
|
||||
Vendored
+3
-2
@@ -1,6 +1,6 @@
|
||||
"Test , coverage %."
|
||||
|
||||
from idlelib import zzdummy
|
||||
from idlelib import zzdummy # Replace with the module to test.
|
||||
import unittest
|
||||
from test.support import requires
|
||||
from tkinter import Tk
|
||||
@@ -17,11 +17,12 @@ class Test(unittest.TestCase):
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.root.update_idletasks()
|
||||
## for id in cls.root.tk.call('after', 'info'):
|
||||
## for id in cls.root.after_info():
|
||||
## cls.root.after_cancel(id) # Need for EditorWindow.
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
@unittest.skip('Dummy test')
|
||||
def test_init(self):
|
||||
self.assertTrue(True)
|
||||
|
||||
|
||||
+7
-9
@@ -230,16 +230,14 @@ class AutoCompleteTest(unittest.TestCase):
|
||||
# For file completion, a large list containing all files in the path,
|
||||
# and a small list containing files that do not start with '.'.
|
||||
acp = self.autocomplete
|
||||
small, large = acp.fetch_completions(
|
||||
'', ac.ATTRS)
|
||||
if hasattr(__main__, '__file__') and __main__.__file__ != ac.__file__:
|
||||
self.assertNotIn('AutoComplete', small) # See issue 36405.
|
||||
|
||||
# Test attributes
|
||||
s, b = acp.fetch_completions('', ac.ATTRS)
|
||||
self.assertLess(len(small), len(large))
|
||||
self.assertTrue(all(filter(lambda x: x.startswith('_'), s)))
|
||||
self.assertTrue(any(filter(lambda x: x.startswith('_'), b)))
|
||||
# Test current module (what='') attributes.
|
||||
small, large = acp.fetch_completions('', ac.ATTRS)
|
||||
if hasattr(__main__, '__file__') and __main__.__file__ != ac.__file__:
|
||||
self.assertNotIn('AutoComplete', small) # See gh-80586.
|
||||
self.assertLess(len(small), len(large)) # Not equal
|
||||
self.assertFalse(any(a[:1] == '_' for a in small))
|
||||
self.assertTrue(any(a[:1] == '_' for a in large))
|
||||
|
||||
# Test smalll should respect to __all__.
|
||||
with patch.dict('__main__.__dict__', {'__all__': ['a', 'b']}):
|
||||
|
||||
+2
-2
@@ -127,7 +127,7 @@ class CodeContextTest(unittest.TestCase):
|
||||
timer = self.cc.t1 = self.text.after(10000, lambda: None)
|
||||
self.cc.__del__()
|
||||
with self.assertRaises(TclError) as cm:
|
||||
self.root.tk.call('after', 'info', timer)
|
||||
self.root.after_info(timer)
|
||||
self.assertIn("doesn't exist", str(cm.exception))
|
||||
|
||||
def test_reload(self):
|
||||
@@ -151,7 +151,7 @@ class CodeContextTest(unittest.TestCase):
|
||||
eq(cc.context['bg'], self.highlight_cfg['background'])
|
||||
eq(cc.context.get('1.0', 'end-1c'), '')
|
||||
eq(cc.editwin.label, 'Hide Code Context')
|
||||
eq(self.root.tk.call('after', 'info', self.cc.t1)[1], 'timer')
|
||||
eq(self.root.after_info(self.cc.t1)[1], 'timer')
|
||||
|
||||
# Toggle off.
|
||||
toggle()
|
||||
|
||||
+23
-5
@@ -260,7 +260,7 @@ class ColorDelegatorTest(unittest.TestCase):
|
||||
|
||||
# Colorizing already scheduled.
|
||||
save_id = color.after_id
|
||||
eq(self.root.tk.call('after', 'info', save_id)[1], 'timer')
|
||||
eq(self.root.after_info(save_id)[1], 'timer')
|
||||
self.assertFalse(color.colorizing)
|
||||
self.assertFalse(color.stop_colorizing)
|
||||
self.assertTrue(color.allow_colorizing)
|
||||
@@ -277,7 +277,7 @@ class ColorDelegatorTest(unittest.TestCase):
|
||||
color.notify_range('1.0', '1.0+3c')
|
||||
self.assertTrue(color.stop_colorizing)
|
||||
self.assertIsNotNone(color.after_id)
|
||||
eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
|
||||
eq(self.root.after_info(color.after_id)[1], 'timer')
|
||||
# New event scheduled.
|
||||
self.assertNotEqual(color.after_id, save_id)
|
||||
|
||||
@@ -297,7 +297,7 @@ class ColorDelegatorTest(unittest.TestCase):
|
||||
self.assertFalse(color.colorizing)
|
||||
self.assertFalse(color.stop_colorizing)
|
||||
self.assertTrue(color.allow_colorizing)
|
||||
eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
|
||||
eq(self.root.after_info(color.after_id)[1], 'timer')
|
||||
|
||||
# Toggle colorizing off.
|
||||
color.toggle_colorize_event()
|
||||
@@ -324,7 +324,7 @@ class ColorDelegatorTest(unittest.TestCase):
|
||||
# Toggle on while colorizing not in progress.
|
||||
color.colorizing = False
|
||||
color.toggle_colorize_event()
|
||||
eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
|
||||
eq(self.root.after_info(color.after_id)[1], 'timer')
|
||||
self.assertFalse(color.colorizing)
|
||||
self.assertTrue(color.stop_colorizing)
|
||||
self.assertTrue(color.allow_colorizing)
|
||||
@@ -363,7 +363,7 @@ class ColorDelegatorTest(unittest.TestCase):
|
||||
mock_recmain.assert_called()
|
||||
eq(mock_recmain.call_count, 1)
|
||||
# Rescheduled when TODO tag still exists.
|
||||
eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
|
||||
eq(self.root.after_info(color.after_id)[1], 'timer')
|
||||
|
||||
# No changes to text, so no scheduling added.
|
||||
text.tag_remove('TODO', '1.0', 'end')
|
||||
@@ -542,6 +542,24 @@ class ColorDelegatorTest(unittest.TestCase):
|
||||
self._assert_highlighting('case _:', {'KEYWORD': [('1.0', '1.4'),
|
||||
('1.5', '1.6')]})
|
||||
|
||||
def test_lazy_soft_keyword(self): # lazy new in 3.15.
|
||||
# lazy followed by import
|
||||
self._assert_highlighting('lazy import foo',
|
||||
{'KEYWORD': [('1.0', '1.4'),
|
||||
('1.5', '1.11')]})
|
||||
self._assert_highlighting(' lazy import foo',
|
||||
{'KEYWORD': [('1.4', '1.8'),
|
||||
('1.9', '1.15')]})
|
||||
|
||||
# lazy followed by from
|
||||
self._assert_highlighting('lazy from foo import bar',
|
||||
{'KEYWORD': [('1.0', '1.4'), ('1.5', '1.9'),
|
||||
('1.14', '1.20')]})
|
||||
|
||||
# lazy not followed by import/from (not highlighted)
|
||||
self._assert_highlighting('lazy = 1', {})
|
||||
self._assert_highlighting('lazy foo', {})
|
||||
|
||||
def test_long_multiline_string(self):
|
||||
source = textwrap.dedent('''\
|
||||
"""a
|
||||
|
||||
Vendored
+42
-16
@@ -454,9 +454,6 @@ class IdleConfTest(unittest.TestCase):
|
||||
self.assertEqual(idleConf.GetExtensionKeys('ZzDummy'),
|
||||
{'<<z-in>>': ['<Control-Shift-KeyRelease-Insert>']})
|
||||
userextn.remove_section('ZzDummy')
|
||||
# need option key test
|
||||
## key = ['<Option-Key-2>'] if sys.platform == 'darwin' else ['<Alt-Key-2>']
|
||||
## eq(conf.GetExtensionKeys('ZoomHeight'), {'<<zoom-height>>': key})
|
||||
|
||||
def test_get_extension_bindings(self):
|
||||
userextn.read_string('''
|
||||
@@ -491,19 +488,27 @@ class IdleConfTest(unittest.TestCase):
|
||||
def test_get_current_keyset(self):
|
||||
current_platform = sys.platform
|
||||
conf = self.mock_config()
|
||||
try:
|
||||
# Ensure that platform isn't darwin
|
||||
sys.platform = 'some-linux'
|
||||
self.assertEqual(conf.GetCurrentKeySet(),
|
||||
conf.GetKeySet(conf.CurrentKeys()))
|
||||
|
||||
# Ensure that platform isn't darwin
|
||||
sys.platform = 'some-linux'
|
||||
self.assertEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
|
||||
|
||||
# This should not be the same, since replace <Alt- to <Option-.
|
||||
# Above depended on config-extensions.def having Alt keys,
|
||||
# which is no longer true.
|
||||
# sys.platform = 'darwin'
|
||||
# self.assertNotEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
|
||||
|
||||
# Restore platform
|
||||
sys.platform = current_platform
|
||||
# On darwin, '<Alt-' is replaced with '<Option-'. Add an
|
||||
# extension binding, as the default key sets have no Alt keys.
|
||||
conf.defaultCfg['extensions'].add_section('Foobar')
|
||||
conf.defaultCfg['extensions'].add_section('Foobar_cfgBindings')
|
||||
conf.defaultCfg['extensions'].set('Foobar', 'enable', 'True')
|
||||
conf.defaultCfg['extensions'].set('Foobar_cfgBindings', 'newfoo',
|
||||
'<Alt-Shift-Key-F12>')
|
||||
self.assertEqual(conf.GetKeySet(conf.CurrentKeys())['<<newfoo>>'],
|
||||
['<Alt-Shift-Key-F12>'])
|
||||
sys.platform = 'darwin'
|
||||
self.assertEqual(conf.GetCurrentKeySet()['<<newfoo>>'],
|
||||
['<Option-Shift-Key-F12>'])
|
||||
finally:
|
||||
# Restore platform
|
||||
sys.platform = current_platform
|
||||
|
||||
def test_get_keyset(self):
|
||||
conf = self.mock_config()
|
||||
@@ -762,8 +767,29 @@ class ChangesTest(unittest.TestCase):
|
||||
changes = self.changes
|
||||
changes.add_option('main', 'Indent', 'use-spaces', '1')
|
||||
# save_option returns False; cfg_type_changed remains False.
|
||||
self.assertFalse(changes.save_all())
|
||||
self.assertFalse(usermain.has_option('Indent', 'use-spaces'))
|
||||
self.assertEqual(changes, self.empty)
|
||||
|
||||
# TODO: test that save_all calls usercfg Saves.
|
||||
def test_save_all_saves_files(self):
|
||||
eq = self.assertEqual
|
||||
changes = self.changes
|
||||
for config in testcfg.values():
|
||||
config.Save = Func()
|
||||
try:
|
||||
# 'main', 'highlight' and 'keys' are saved even if unchanged.
|
||||
self.assertFalse(changes.save_all())
|
||||
eq([testcfg[cfgtype].Save.called
|
||||
for cfgtype in ('main', 'highlight', 'keys', 'extensions')],
|
||||
[1, 1, 1, 0])
|
||||
# A changed configuration type is saved too.
|
||||
changes.add_option('extensions', 'Esec', 'eitem', 'eval')
|
||||
self.assertTrue(changes.save_all())
|
||||
eq(testcfg['extensions'].Save.called, 1)
|
||||
finally:
|
||||
for config in testcfg.values():
|
||||
del config.Save
|
||||
userextn.remove_section('Esec')
|
||||
|
||||
def test_delete_section(self):
|
||||
changes = self.load()
|
||||
|
||||
+213
-44
@@ -3,12 +3,15 @@
|
||||
Half the class creates dialog, half works with user customizations.
|
||||
"""
|
||||
from idlelib import configdialog
|
||||
from idlelib.editor import EditorWindow
|
||||
from test.support import requires
|
||||
requires('gui')
|
||||
import unittest
|
||||
from unittest import mock
|
||||
from idlelib.idle_test.mock_idle import Func
|
||||
from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL)
|
||||
from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL,
|
||||
EventType)
|
||||
from types import SimpleNamespace
|
||||
from idlelib import config
|
||||
from idlelib.configdialog import idleConf, changes, tracers
|
||||
|
||||
@@ -51,12 +54,27 @@ def tearDownModule():
|
||||
|
||||
|
||||
class ConfigDialogTest(unittest.TestCase):
|
||||
# The methods tested here are mocked out in the tests below.
|
||||
|
||||
def setUp(self):
|
||||
self.parent = dialog.parent
|
||||
self.instance = mock.create_autospec(EditorWindow, instance=True)
|
||||
dialog.parent = mock.Mock(instance_dict={self.instance: []})
|
||||
|
||||
def tearDown(self):
|
||||
dialog.parent = self.parent
|
||||
|
||||
def test_deactivate_current_config(self):
|
||||
pass
|
||||
dialog.deactivate_current_config()
|
||||
self.instance.RemoveKeybindings.assert_called_once_with()
|
||||
|
||||
def activate_config_changes(self):
|
||||
pass
|
||||
def test_activate_config_changes(self):
|
||||
dialog.activate_config_changes()
|
||||
for name in ('ResetColorizer', 'ResetFont', 'set_notabs_indentwidth',
|
||||
'ApplyKeybindings', 'reset_help_menu_entries',
|
||||
'update_cursor_blink'):
|
||||
with self.subTest(name=name):
|
||||
getattr(self.instance, name).assert_called_once_with()
|
||||
|
||||
|
||||
class ButtonTest(unittest.TestCase):
|
||||
@@ -149,8 +167,8 @@ class FontPageTest(unittest.TestCase):
|
||||
font = d.fontlist.get('active')
|
||||
|
||||
# Test Down key.
|
||||
fontlist.focus_force()
|
||||
fontlist.update()
|
||||
fontlist.focus_force()
|
||||
fontlist.event_generate('<Key-Down>')
|
||||
fontlist.event_generate('<KeyRelease-Down>')
|
||||
|
||||
@@ -159,8 +177,8 @@ class FontPageTest(unittest.TestCase):
|
||||
self.assertIn(d.font_name.get(), down_font.lower())
|
||||
|
||||
# Test Up key.
|
||||
fontlist.focus_force()
|
||||
fontlist.update()
|
||||
fontlist.focus_force()
|
||||
fontlist.event_generate('<Key-Up>')
|
||||
fontlist.event_generate('<KeyRelease-Up>')
|
||||
|
||||
@@ -244,6 +262,7 @@ class FontPageTest(unittest.TestCase):
|
||||
|
||||
def test_set_samples(self):
|
||||
d = self.page
|
||||
self.addCleanup(setattr, d, 'set_samples', Func()) # Re-mask for other tests.
|
||||
del d.set_samples # Unmask method for test
|
||||
orig_samples = d.font_sample, d.highlight_sample
|
||||
d.font_sample, d.highlight_sample = {}, {}
|
||||
@@ -257,7 +276,6 @@ class FontPageTest(unittest.TestCase):
|
||||
self.assertTrue(d.font_sample == d.highlight_sample == expected)
|
||||
|
||||
d.font_sample, d.highlight_sample = orig_samples
|
||||
d.set_samples = Func() # Re-mask for other tests.
|
||||
|
||||
|
||||
class HighPageTest(unittest.TestCase):
|
||||
@@ -307,7 +325,7 @@ class HighPageTest(unittest.TestCase):
|
||||
# builtinlist sets variable builtin_name to the CurrentTheme default.
|
||||
eq(d.builtin_name.get(), 'IDLE Classic')
|
||||
eq(d.custom_name.get(), '- no custom themes -')
|
||||
eq(d.custom_theme_on.state(), ('disabled',))
|
||||
self.assertIn('disabled', d.custom_theme_on.state())
|
||||
eq(d.set_theme_type.called, 1)
|
||||
eq(d.paint_theme_sample.called, 1)
|
||||
eq(d.set_highlight_target.called, 1)
|
||||
@@ -495,6 +513,7 @@ class HighPageTest(unittest.TestCase):
|
||||
def test_set_theme_type(self):
|
||||
eq = self.assertEqual
|
||||
d = self.page
|
||||
self.addCleanup(setattr, d, 'set_theme_type', Func()) # Re-mask method.
|
||||
del d.set_theme_type
|
||||
|
||||
# Builtin theme selected.
|
||||
@@ -502,16 +521,15 @@ class HighPageTest(unittest.TestCase):
|
||||
d.set_theme_type()
|
||||
eq(d.builtinlist['state'], NORMAL)
|
||||
eq(d.customlist['state'], DISABLED)
|
||||
eq(d.button_delete_custom.state(), ('disabled',))
|
||||
self.assertIn('disabled', d.button_delete_custom.state())
|
||||
|
||||
# Custom theme selected.
|
||||
d.theme_source.set(False)
|
||||
d.set_theme_type()
|
||||
eq(d.builtinlist['state'], DISABLED)
|
||||
eq(d.custom_theme_on.state(), ('selected',))
|
||||
self.assertNotIn('disabled', d.custom_theme_on.state())
|
||||
eq(d.customlist['state'], NORMAL)
|
||||
eq(d.button_delete_custom.state(), ())
|
||||
d.set_theme_type = Func()
|
||||
self.assertNotIn('disabled', d.button_delete_custom.state())
|
||||
|
||||
def test_get_color(self):
|
||||
eq = self.assertEqual
|
||||
@@ -637,24 +655,23 @@ class HighPageTest(unittest.TestCase):
|
||||
def test_set_highlight_target(self):
|
||||
eq = self.assertEqual
|
||||
d = self.page
|
||||
self.addCleanup(setattr, d, 'set_highlight_target', Func()) # Re-mask method.
|
||||
del d.set_highlight_target
|
||||
|
||||
# Target is cursor.
|
||||
d.highlight_target.set('Cursor')
|
||||
eq(d.fg_on.state(), ('disabled', 'selected'))
|
||||
eq(d.bg_on.state(), ('disabled',))
|
||||
self.assertIn('disabled', d.fg_on.state())
|
||||
self.assertIn('disabled', d.bg_on.state())
|
||||
self.assertTrue(d.fg_bg_toggle)
|
||||
eq(d.set_color_sample.called, 1)
|
||||
|
||||
# Target is not cursor.
|
||||
d.highlight_target.set('Comment')
|
||||
eq(d.fg_on.state(), ('selected',))
|
||||
eq(d.bg_on.state(), ())
|
||||
self.assertNotIn('disabled', d.fg_on.state())
|
||||
self.assertNotIn('disabled', d.bg_on.state())
|
||||
self.assertTrue(d.fg_bg_toggle)
|
||||
eq(d.set_color_sample.called, 2)
|
||||
|
||||
d.set_highlight_target = Func()
|
||||
|
||||
def test_set_color_sample_binding(self):
|
||||
d = self.page
|
||||
scs = d.set_color_sample
|
||||
@@ -667,6 +684,7 @@ class HighPageTest(unittest.TestCase):
|
||||
|
||||
def test_set_color_sample(self):
|
||||
d = self.page
|
||||
self.addCleanup(setattr, d, 'set_color_sample', Func()) # Re-mask method.
|
||||
del d.set_color_sample
|
||||
d.highlight_target.set('Selected Text')
|
||||
d.fg_bg_toggle.set(True)
|
||||
@@ -674,7 +692,6 @@ class HighPageTest(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
d.style.lookup(d.frame_color_set['style'], 'background'),
|
||||
d.highlight_sample.tag_cget('hilite', 'foreground'))
|
||||
d.set_color_sample = Func()
|
||||
|
||||
def test_paint_theme_sample(self):
|
||||
eq = self.assertEqual
|
||||
@@ -745,7 +762,7 @@ class HighPageTest(unittest.TestCase):
|
||||
eq(yesno.called, 2)
|
||||
self.assertNotIn(theme_name, highpage)
|
||||
eq(idleConf.GetSectionList('user', 'highlight'), [theme_name2])
|
||||
eq(d.custom_theme_on.state(), ())
|
||||
self.assertNotIn('disabled', d.custom_theme_on.state())
|
||||
eq(d.custom_name.get(), theme_name2)
|
||||
eq(dialog.deactivate_current_config.called, 1)
|
||||
eq(dialog.activate_config_changes.called, 1)
|
||||
@@ -758,7 +775,7 @@ class HighPageTest(unittest.TestCase):
|
||||
eq(yesno.called, 3)
|
||||
self.assertNotIn(theme_name, highpage)
|
||||
eq(idleConf.GetSectionList('user', 'highlight'), [])
|
||||
eq(d.custom_theme_on.state(), ('disabled',))
|
||||
self.assertIn('disabled', d.custom_theme_on.state())
|
||||
eq(d.custom_name.get(), '- no custom themes -')
|
||||
eq(dialog.deactivate_current_config.called, 2)
|
||||
eq(dialog.activate_config_changes.called, 2)
|
||||
@@ -809,7 +826,7 @@ class KeysPageTest(unittest.TestCase):
|
||||
# builtinlist sets variable builtin_name to the CurrentKeys default.
|
||||
eq(d.builtin_name.get(), 'IDLE Classic OSX')
|
||||
eq(d.custom_name.get(), '- no custom keys -')
|
||||
eq(d.custom_keyset_on.state(), ('disabled',))
|
||||
self.assertIn('disabled', d.custom_keyset_on.state())
|
||||
eq(d.set_keys_type.called, 1)
|
||||
eq(d.load_keys_list.called, 1)
|
||||
eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
|
||||
@@ -930,6 +947,7 @@ class KeysPageTest(unittest.TestCase):
|
||||
def test_set_keys_type(self):
|
||||
eq = self.assertEqual
|
||||
d = self.page
|
||||
self.addCleanup(setattr, d, 'set_keys_type', Func()) # Re-mask method.
|
||||
del d.set_keys_type
|
||||
|
||||
# Builtin keyset selected.
|
||||
@@ -937,16 +955,15 @@ class KeysPageTest(unittest.TestCase):
|
||||
d.set_keys_type()
|
||||
eq(d.builtinlist['state'], NORMAL)
|
||||
eq(d.customlist['state'], DISABLED)
|
||||
eq(d.button_delete_custom_keys.state(), ('disabled',))
|
||||
self.assertIn('disabled', d.button_delete_custom_keys.state())
|
||||
|
||||
# Custom keyset selected.
|
||||
d.keyset_source.set(False)
|
||||
d.set_keys_type()
|
||||
eq(d.builtinlist['state'], DISABLED)
|
||||
eq(d.custom_keyset_on.state(), ('selected',))
|
||||
self.assertNotIn('disabled', d.custom_keyset_on.state())
|
||||
eq(d.customlist['state'], NORMAL)
|
||||
eq(d.button_delete_custom_keys.state(), ())
|
||||
d.set_keys_type = Func()
|
||||
self.assertNotIn('disabled', d.button_delete_custom_keys.state())
|
||||
|
||||
def test_get_new_keys(self):
|
||||
eq = self.assertEqual
|
||||
@@ -1043,7 +1060,15 @@ class KeysPageTest(unittest.TestCase):
|
||||
b.event_generate('<Button-1>', x=x, y=y)
|
||||
b.event_generate('<ButtonRelease-1>', x=x, y=y)
|
||||
self.assertEqual(b.get('anchor'), 'find')
|
||||
self.assertEqual(d.button_new_keys.state(), ())
|
||||
self.assertNotIn('disabled', d.button_new_keys.state())
|
||||
|
||||
# gh-75234: Up and Down keys move the active item, but not the
|
||||
# anchor; the handler moves the anchor.
|
||||
d.button_new_keys.state(('disabled',))
|
||||
b.activate(0)
|
||||
d.on_bindingslist_select(SimpleNamespace(type=EventType.KeyRelease))
|
||||
self.assertEqual(b.get('anchor'), 'copy')
|
||||
self.assertNotIn('disabled', d.button_new_keys.state())
|
||||
|
||||
def test_create_new_key_set_and_save_new_key_set(self):
|
||||
eq = self.assertEqual
|
||||
@@ -1080,6 +1105,7 @@ class KeysPageTest(unittest.TestCase):
|
||||
eq = self.assertEqual
|
||||
d = self.page
|
||||
gks = idleConf.GetKeySet = Func()
|
||||
self.addCleanup(setattr, d, 'load_keys_list', Func()) # Re-mask method.
|
||||
del d.load_keys_list
|
||||
b = d.bindingslist
|
||||
|
||||
@@ -1094,11 +1120,14 @@ class KeysPageTest(unittest.TestCase):
|
||||
'force-open-completions - <Control-Key-space>',
|
||||
'spam - <Shift-Key-a>')
|
||||
|
||||
# No current selection.
|
||||
# No current selection: select the first item.
|
||||
d.button_new_keys.state(('disabled',))
|
||||
d.load_keys_list('my keys')
|
||||
eq(b.get(0, 'end'), expected)
|
||||
eq(b.get('anchor'), '')
|
||||
eq(b.curselection(), ())
|
||||
eq(b.get('anchor'), 'copy - <Control-Key-c> <Control-Key-C>')
|
||||
eq(b.curselection(), (0, ))
|
||||
eq(b.index('active'), 0)
|
||||
self.assertNotIn('disabled', d.button_new_keys.state())
|
||||
|
||||
# Check selection.
|
||||
b.selection_set(1)
|
||||
@@ -1115,7 +1144,6 @@ class KeysPageTest(unittest.TestCase):
|
||||
eq(b.get(0, 'end'), expected)
|
||||
eq(b.get('anchor'), 'spam - <Shift-Key-a>')
|
||||
eq(b.curselection(), (2, ))
|
||||
d.load_keys_list = Func()
|
||||
|
||||
del idleConf.GetKeySet
|
||||
|
||||
@@ -1156,7 +1184,7 @@ class KeysPageTest(unittest.TestCase):
|
||||
eq(yesno.called, 2)
|
||||
self.assertNotIn(keyset_name, keyspage)
|
||||
eq(idleConf.GetSectionList('user', 'keys'), [keyset_name2])
|
||||
eq(d.custom_keyset_on.state(), ())
|
||||
self.assertNotIn('disabled', d.custom_keyset_on.state())
|
||||
eq(d.custom_name.get(), keyset_name2)
|
||||
eq(dialog.deactivate_current_config.called, 1)
|
||||
eq(dialog.activate_config_changes.called, 1)
|
||||
@@ -1169,7 +1197,7 @@ class KeysPageTest(unittest.TestCase):
|
||||
eq(yesno.called, 3)
|
||||
self.assertNotIn(keyset_name, keyspage)
|
||||
eq(idleConf.GetSectionList('user', 'keys'), [])
|
||||
eq(d.custom_keyset_on.state(), ('disabled',))
|
||||
self.assertIn('disabled', d.custom_keyset_on.state())
|
||||
eq(d.custom_name.get(), '- no custom keys -')
|
||||
eq(dialog.deactivate_current_config.called, 2)
|
||||
eq(dialog.activate_config_changes.called, 2)
|
||||
@@ -1296,13 +1324,123 @@ class ShedPageTest(unittest.TestCase):
|
||||
self.assertEqual(extpage, {'CodeContext': {'maxlines': '1'}})
|
||||
|
||||
|
||||
#unittest.skip("Nothing here yet TODO")
|
||||
class ExtPageTest(unittest.TestCase):
|
||||
"""Test that the help source list works correctly."""
|
||||
"""Test that the extension page works correctly.
|
||||
|
||||
The page loads the options of each extension from the default and
|
||||
user config files, displays those of the selected extension, and
|
||||
saves the changed ones to the user config file. ZzDummy is the
|
||||
only extension shipped with IDLE.
|
||||
"""
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
page = dialog.extpage
|
||||
page = cls.page = dialog.extpage
|
||||
dialog.note.select(page)
|
||||
page.update()
|
||||
|
||||
def setUp(self):
|
||||
# Restore the option vars changed by the previous test.
|
||||
self.page.load_extensions()
|
||||
|
||||
def tearDown(self):
|
||||
self.page.ext_userCfg.remove_section('ZzDummy')
|
||||
|
||||
def test_load_extensions(self):
|
||||
eq = self.assertEqual
|
||||
extensions = self.page.extensions
|
||||
eq(list(extensions), sorted(idleConf.GetExtensions(active_only=False)))
|
||||
opts = extensions['ZzDummy']
|
||||
# The 'enable' options come first, the others follow, both sorted.
|
||||
eq([opt['name'] for opt in opts],
|
||||
['enable', 'enable_editor', 'enable_shell', 'z-text'])
|
||||
eq([opt['type'] for opt in opts], ['bool', 'bool', 'bool', None])
|
||||
eq([opt['default'] for opt in opts], ['False', 'True', 'False', 'Z'])
|
||||
eq([opt['value'] for opt in opts], [False, True, False, 'Z'])
|
||||
for opt in opts:
|
||||
with self.subTest(name=opt['name']):
|
||||
eq(opt['var'].get(), str(opt['value']))
|
||||
|
||||
def test_load_extensions_enable_first(self):
|
||||
# The 'enable' options come first even if they sort last.
|
||||
page = self.page
|
||||
page.ext_userCfg.SetOption('ZzDummy', 'a-text', 'A')
|
||||
page.load_extensions()
|
||||
self.assertEqual([opt['name'] for opt in page.extensions['ZzDummy']],
|
||||
['enable', 'enable_editor', 'enable_shell',
|
||||
'a-text', 'z-text'])
|
||||
|
||||
def test_load_extensions_user_value(self):
|
||||
# A user option overrides the default value, but not the default.
|
||||
page = self.page
|
||||
page.ext_userCfg.SetOption('ZzDummy', 'z-text', 'user')
|
||||
page.load_extensions()
|
||||
opt = page.extensions['ZzDummy'][-1]
|
||||
self.assertEqual(opt['name'], 'z-text')
|
||||
self.assertEqual(opt['default'], 'Z')
|
||||
self.assertEqual(opt['value'], 'user')
|
||||
self.assertEqual(opt['var'].get(), 'user')
|
||||
|
||||
def test_extension_selected(self):
|
||||
eq = self.assertEqual
|
||||
page = self.page
|
||||
frame = page.config_frame['ZzDummy']
|
||||
# Deselecting hides the options of the current extension.
|
||||
page.extension_list.selection_clear(0, 'end')
|
||||
page.extension_selected(None)
|
||||
eq(page.current_extension, None)
|
||||
eq(page.details_frame.cget('text'), '')
|
||||
eq(frame.winfo_manager(), '')
|
||||
# Selecting shows the options of the selected extension.
|
||||
page.extension_list.selection_set(0)
|
||||
page.extension_selected(None)
|
||||
eq(page.current_extension, 'ZzDummy')
|
||||
eq(page.details_frame.cget('text'), 'ZzDummy')
|
||||
eq(frame.winfo_manager(), 'grid')
|
||||
|
||||
def test_set_extension_value_changed(self):
|
||||
page = self.page
|
||||
opt = page.extensions['ZzDummy'][-1] # z-text, default 'Z'.
|
||||
opt['var'].set('user')
|
||||
self.assertTrue(page.set_extension_value('ZzDummy', opt))
|
||||
self.assertEqual(page.ext_userCfg.Get('ZzDummy', 'z-text'), 'user')
|
||||
# Saving the same value again is not a change.
|
||||
self.assertFalse(page.set_extension_value('ZzDummy', opt))
|
||||
|
||||
def test_set_extension_value_default(self):
|
||||
page = self.page
|
||||
opt = page.extensions['ZzDummy'][-1]
|
||||
# The default value is not saved in the user config file.
|
||||
opt['var'].set('Z')
|
||||
self.assertFalse(page.set_extension_value('ZzDummy', opt))
|
||||
self.assertFalse(page.ext_userCfg.has_option('ZzDummy', 'z-text'))
|
||||
# Setting it back to the default removes the user option.
|
||||
page.ext_userCfg.SetOption('ZzDummy', 'z-text', 'user')
|
||||
self.assertTrue(page.set_extension_value('ZzDummy', opt))
|
||||
self.assertFalse(page.ext_userCfg.has_option('ZzDummy', 'z-text'))
|
||||
|
||||
def test_set_extension_value_empty(self):
|
||||
# An empty value is replaced with the default.
|
||||
page = self.page
|
||||
opt = page.extensions['ZzDummy'][-1]
|
||||
opt['var'].set(' ')
|
||||
self.assertFalse(page.set_extension_value('ZzDummy', opt))
|
||||
self.assertEqual(opt['var'].get(), 'Z')
|
||||
|
||||
def test_save_all_changed_extensions(self):
|
||||
page = self.page
|
||||
page.ext_userCfg.Save = Func()
|
||||
try:
|
||||
# Nothing is saved if nothing is changed.
|
||||
page.save_all_changed_extensions()
|
||||
self.assertEqual(page.ext_userCfg.Save.called, 0)
|
||||
page.extensions['ZzDummy'][0]['var'].set('True')
|
||||
page.extensions['ZzDummy'][-1]['var'].set('user')
|
||||
page.save_all_changed_extensions()
|
||||
self.assertEqual(page.ext_userCfg.Save.called, 1)
|
||||
self.assertEqual(page.ext_userCfg.Get('ZzDummy', 'enable'), 'True')
|
||||
self.assertEqual(page.ext_userCfg.Get('ZzDummy', 'z-text'), 'user')
|
||||
finally:
|
||||
del page.ext_userCfg.Save
|
||||
|
||||
|
||||
class HelpSourceTest(unittest.TestCase):
|
||||
@@ -1365,25 +1503,25 @@ class HelpSourceTest(unittest.TestCase):
|
||||
# Call with 0 items, 1 unselected item, 1 selected item.
|
||||
eq = self.assertEqual
|
||||
fr = self.frame
|
||||
self.addCleanup(setattr, fr, 'set_add_delete_state', Func()) # Re-mask method.
|
||||
del fr.set_add_delete_state # Unmask method.
|
||||
sad = fr.set_add_delete_state
|
||||
h = fr.helplist
|
||||
|
||||
h.delete(0, 'end')
|
||||
sad()
|
||||
eq(fr.button_helplist_edit.state(), ('disabled',))
|
||||
eq(fr.button_helplist_remove.state(), ('disabled',))
|
||||
self.assertIn('disabled', fr.button_helplist_edit.state())
|
||||
self.assertIn('disabled', fr.button_helplist_remove.state())
|
||||
|
||||
h.insert(0, 'source')
|
||||
sad()
|
||||
eq(fr.button_helplist_edit.state(), ('disabled',))
|
||||
eq(fr.button_helplist_remove.state(), ('disabled',))
|
||||
self.assertIn('disabled', fr.button_helplist_edit.state())
|
||||
self.assertIn('disabled', fr.button_helplist_remove.state())
|
||||
|
||||
h.selection_set(0)
|
||||
sad()
|
||||
eq(fr.button_helplist_edit.state(), ())
|
||||
eq(fr.button_helplist_remove.state(), ())
|
||||
fr.set_add_delete_state = Func() # Mask method.
|
||||
self.assertNotIn('disabled', fr.button_helplist_edit.state())
|
||||
self.assertNotIn('disabled', fr.button_helplist_remove.state())
|
||||
|
||||
def test_helplist_item_add(self):
|
||||
# Call without and twice with HelpSource result.
|
||||
@@ -1459,8 +1597,29 @@ class HelpSourceTest(unittest.TestCase):
|
||||
eq(fr.user_helplist, [])
|
||||
self.assertTrue(fr.upc.called == fr.set.called == 1)
|
||||
|
||||
def test_helplist_item_remove_keyboard_selection(self):
|
||||
# gh-75234: Up and Down keys move the active item, but not the
|
||||
# anchor; the handler moves the anchor.
|
||||
eq = self.assertEqual
|
||||
fr = self.frame
|
||||
fr.helplist.delete(0, 'end')
|
||||
fr.helplist.insert('end', 'name1', 'name2')
|
||||
fr.helplist.selection_anchor(0)
|
||||
fr.helplist.selection_set(1)
|
||||
fr.helplist.activate(1)
|
||||
fr.user_helplist.clear()
|
||||
fr.user_helplist.extend([('name1', 'file1'), ('name2', 'file2')])
|
||||
fr.set.called = fr.upc.called = 0
|
||||
|
||||
fr.help_source_selected(SimpleNamespace(type=EventType.KeyRelease))
|
||||
eq(fr.helplist.get('anchor'), 'name2')
|
||||
fr.helplist_item_remove()
|
||||
eq(fr.helplist.get(0, 'end'), ('name1',))
|
||||
eq(fr.user_helplist, [('name1', 'file1')])
|
||||
|
||||
def test_update_help_changes(self):
|
||||
fr = self.frame
|
||||
self.addCleanup(setattr, fr, 'update_help_changes', Func()) # Re-mask method.
|
||||
del fr.update_help_changes
|
||||
fr.user_helplist.clear()
|
||||
fr.user_helplist.append(('name1', 'file1'))
|
||||
@@ -1469,7 +1628,6 @@ class HelpSourceTest(unittest.TestCase):
|
||||
fr.update_help_changes()
|
||||
self.assertEqual(mainpage['HelpFiles'],
|
||||
{'1': 'name1;file1', '2': 'name2;file2'})
|
||||
fr.update_help_changes = Func()
|
||||
|
||||
|
||||
class VarTraceTest(unittest.TestCase):
|
||||
@@ -1544,6 +1702,17 @@ class VarTraceTest(unittest.TestCase):
|
||||
self.assertEqual(changes['main']['section']['option'], '42')
|
||||
changes.clear()
|
||||
|
||||
# gh-83653: a blank int entry is not saved as bad config data.
|
||||
sv = StringVar(root)
|
||||
cb = self.tracers.make_callback(sv, ('main', 'section', 'option'))
|
||||
sv.set('')
|
||||
cb()
|
||||
self.assertNotIn('section', changes['main'])
|
||||
sv.set('5')
|
||||
cb()
|
||||
self.assertEqual(changes['main']['section']['option'], '5')
|
||||
changes.clear()
|
||||
|
||||
def test_attach_detach(self):
|
||||
tr = self.tracers
|
||||
iv = tr.add(self.iv, self.var_changed_increment)
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ requires('gui')
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import unittest
|
||||
from idlelib import pyshell
|
||||
from idlelib.util import fix_x11_paste
|
||||
|
||||
class PasteTest(unittest.TestCase):
|
||||
'''Test pasting into widgets that allow pasting.
|
||||
@@ -18,7 +18,7 @@ class PasteTest(unittest.TestCase):
|
||||
def setUpClass(cls):
|
||||
cls.root = root = tk.Tk()
|
||||
cls.root.withdraw()
|
||||
pyshell.fix_x11_paste(root)
|
||||
fix_x11_paste(root)
|
||||
cls.text = tk.Text(root)
|
||||
cls.entry = tk.Entry(root)
|
||||
cls.tentry = ttk.Entry(root)
|
||||
|
||||
Vendored
+57
-6
@@ -20,7 +20,7 @@ class EditorWindowTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.root.update_idletasks()
|
||||
for id in cls.root.tk.call('after', 'info'):
|
||||
for id in cls.root.after_info():
|
||||
cls.root.after_cancel(id)
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
@@ -114,7 +114,7 @@ class IndentAndNewlineTest(unittest.TestCase):
|
||||
cls.window._close()
|
||||
del cls.window
|
||||
cls.root.update_idletasks()
|
||||
for id in cls.root.tk.call('after', 'info'):
|
||||
for id in cls.root.after_info():
|
||||
cls.root.after_cancel(id)
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
@@ -212,6 +212,9 @@ class IndentSearcherTest(unittest.TestCase):
|
||||
|
||||
|
||||
class RMenuTest(unittest.TestCase):
|
||||
# Test selection-rclick interaction in right_click_event and
|
||||
# rmenu_check_copy(cut) status settings. These are part of the rmenu
|
||||
# functions common to all text windows with context windows.
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@@ -219,13 +222,15 @@ class RMenuTest(unittest.TestCase):
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.window = Editor(root=cls.root)
|
||||
cls.text = cls.window.text
|
||||
cls.window.rmenu = cls.DummyRMenu
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.window._close()
|
||||
del cls.window
|
||||
del cls.window, cls.text
|
||||
cls.root.update_idletasks()
|
||||
for id in cls.root.tk.call('after', 'info'):
|
||||
for id in cls.root.after_info():
|
||||
cls.root.after_cancel(id)
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
@@ -233,8 +238,54 @@ class RMenuTest(unittest.TestCase):
|
||||
class DummyRMenu:
|
||||
def tk_popup(x, y): pass
|
||||
|
||||
def test_rclick(self):
|
||||
pass
|
||||
def click(self):
|
||||
"""Simulate a right click at(text pixel 0,0).
|
||||
"""
|
||||
Event = namedtuple('Event', ['x', 'y', 'x_root', 'y_root'])
|
||||
event = Event(0, 0, 0, 0)
|
||||
self.assertEqual(self.window.right_menu_event(event), 'break')
|
||||
# This assertion should be moved to a new method that also
|
||||
# text that dummy rmenu.tk_popup is called.
|
||||
|
||||
def test_rclick_not_in_a_selection(self):
|
||||
# Like left click, 'insert' moves to click and any selection is deleted.
|
||||
eq = self.assertEqual
|
||||
text = self.text
|
||||
insert(text, 'one two three')
|
||||
# Selection exists but not clicked.
|
||||
text.tag_add('sel', '1.4', '1.7') # 'two' selected.
|
||||
text.mark_set('insert', '1.7') # Outside of selection.
|
||||
self.click()
|
||||
eq(text.tag_ranges('sel'), ())
|
||||
eq(text.index('insert'), '1.0')
|
||||
# No selection to click, same result.
|
||||
text.mark_set('insert', '1.8')
|
||||
index = self.click()
|
||||
eq(text.tag_ranges('sel'), ())
|
||||
eq(text.index('insert'), '1.0')
|
||||
|
||||
def test_rclick_inside_selection(self):
|
||||
# Unlike left click, selection is not deleted.
|
||||
eq = self.assertEqual
|
||||
text = self.text
|
||||
insert(text, 'one two three') # 'insert' at 1.13.
|
||||
# The selection contains the clicked character.
|
||||
text.tag_add('sel', '1.0', '1.3') # Select 'one'.
|
||||
text.mark_set('insert', '1.3') # If select rightward, 'insert' at 1.3.
|
||||
self.click()
|
||||
eq((text.index('sel.first'), text.index('sel.last')), ('1.0', '1.3'))
|
||||
eq(text.index('insert'), '1.3')
|
||||
|
||||
def test_rmenu_check_copy(self):
|
||||
# copy and cut only valid for click inside selection.
|
||||
eq = self.assertEqual
|
||||
text = self.text
|
||||
insert(text, 'one two three')
|
||||
eq(self.window.rmenu_check_copy(), 'disabled')
|
||||
eq(self.window.rmenu_check_cut(), 'disabled')
|
||||
text.tag_add('sel', '1.0', '1.3') # Includes '1.0' click.
|
||||
eq(self.window.rmenu_check_copy(), 'normal')
|
||||
eq(self.window.rmenu_check_cut(), 'normal')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ class FileListTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.root.update_idletasks()
|
||||
for id in cls.root.tk.call('after', 'info'):
|
||||
for id in cls.root.after_info():
|
||||
cls.root.after_cancel(id)
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
Vendored
+78
-10
@@ -1,16 +1,19 @@
|
||||
""" !Changing this line will break Test_findfile.test_found!
|
||||
Non-gui unit tests for grep.GrepDialog methods.
|
||||
dummy_command calls grep_it calls findfiles.
|
||||
default_command calls grep_it calls findfiles.
|
||||
An exception raised in one method will fail callers.
|
||||
Otherwise, tests are mostly independent.
|
||||
Currently only test grep_it, coverage 51%.
|
||||
Currently test default_command and grep_it, coverage 51%.
|
||||
"""
|
||||
from idlelib import grep
|
||||
import unittest
|
||||
from unittest import mock
|
||||
from test.support import captured_stdout
|
||||
from idlelib.idle_test.mock_tk import Var
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
class Dummy_searchengine:
|
||||
@@ -21,22 +24,48 @@ class Dummy_searchengine:
|
||||
def getpat(self):
|
||||
return self._pat
|
||||
|
||||
def getprog(self):
|
||||
return self._prog
|
||||
|
||||
searchengine = Dummy_searchengine()
|
||||
|
||||
|
||||
class Dummy_grep:
|
||||
# Methods tested
|
||||
#default_command = GrepDialog.default_command
|
||||
# Simplifications: 1. Don't initialize superclass SearchEngineBase
|
||||
# with searchengine. 2. Use directly set class vars instead of
|
||||
# instance vars set from the GUI.
|
||||
globvar = Var('') # File name.
|
||||
recvar = Var(False) # Recurse down directories?
|
||||
engine = searchengine # Pattern and flags.
|
||||
# Methods tested.
|
||||
grep_it = grep.GrepDialog.grep_it
|
||||
# Other stuff needed
|
||||
recvar = Var(False)
|
||||
engine = searchengine
|
||||
default_command = grep.GrepDialog.default_command # Uses grep_it.
|
||||
|
||||
def close(self): # gui method
|
||||
pass
|
||||
|
||||
_grep = Dummy_grep()
|
||||
|
||||
|
||||
class DefaultGlobTest(unittest.TestCase):
|
||||
|
||||
def test_no_path(self):
|
||||
# gh-80504: an unsaved editor or the Shell has no path, so the
|
||||
# pattern uses the current directory, not just '*.py'.
|
||||
self.assertEqual(grep.default_glob(''),
|
||||
os.path.join(os.getcwd(), '*.py'))
|
||||
|
||||
def test_full_path(self):
|
||||
path = os.path.join(os.path.abspath(os.sep), 'ab', 'foo.py')
|
||||
self.assertEqual(grep.default_glob(path),
|
||||
os.path.join(os.path.dirname(path), '*.py'))
|
||||
|
||||
def test_other_extension(self):
|
||||
path = os.path.join(os.path.abspath(os.sep), 'ab', 'foo.txt')
|
||||
self.assertEqual(grep.default_glob(path),
|
||||
os.path.join(os.path.dirname(path), '*.txt'))
|
||||
|
||||
|
||||
class FindfilesTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
@@ -147,9 +176,48 @@ class Grep_itTest(unittest.TestCase):
|
||||
|
||||
|
||||
class Default_commandTest(unittest.TestCase):
|
||||
# To write this, move outwin import to top of GrepDialog
|
||||
# so it can be replaced by captured_stdout in class setup/teardown.
|
||||
pass
|
||||
# default_command searches with grep_it, tested above, writing to an
|
||||
# OutputWindow, which is replaced here by a text buffer.
|
||||
|
||||
def setUp(self):
|
||||
self.dialog = Dummy_grep()
|
||||
self.dialog.top = mock.Mock() # For top.bell().
|
||||
self.dialog.flist = 'flist'
|
||||
self.dialog.globvar = Var(value=__file__)
|
||||
searchengine._pat = pat = 'xyz*' * 7 # Not in this file.
|
||||
searchengine._prog = re.compile(pat)
|
||||
|
||||
def default_command(self):
|
||||
"Return the mock OutputWindow class and what was written to it."
|
||||
save = sys.stdout
|
||||
with mock.patch('idlelib.outwin.OutputWindow') as OutputWindow:
|
||||
OutputWindow.return_value = out = io.StringIO()
|
||||
self.dialog.default_command()
|
||||
self.assertIs(sys.stdout, save) # Restored even when not replaced.
|
||||
return OutputWindow, out.getvalue()
|
||||
|
||||
def test_no_pattern(self):
|
||||
# An invalid pattern is reported by getprog, not here.
|
||||
searchengine._prog = None
|
||||
OutputWindow, output = self.default_command()
|
||||
OutputWindow.assert_not_called()
|
||||
self.assertEqual(output, '')
|
||||
self.dialog.top.bell.assert_not_called()
|
||||
|
||||
def test_no_path(self):
|
||||
self.dialog.globvar = Var(value='')
|
||||
OutputWindow, output = self.default_command()
|
||||
self.dialog.top.bell.assert_called_once()
|
||||
OutputWindow.assert_not_called()
|
||||
self.assertEqual(output, '')
|
||||
|
||||
def test_search(self):
|
||||
OutputWindow, output = self.default_command()
|
||||
OutputWindow.assert_called_once_with('flist') # The flist argument.
|
||||
lines = output.split('\n')
|
||||
self.assertIn(searchengine._pat, lines[0])
|
||||
self.assertEqual(lines[1], 'No hits.')
|
||||
self.dialog.top.bell.assert_not_called()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Vendored
-1
@@ -4,7 +4,6 @@ from idlelib import help
|
||||
import unittest
|
||||
from test.support import requires
|
||||
requires('gui')
|
||||
from os.path import abspath, dirname, join
|
||||
from tkinter import Tk
|
||||
|
||||
|
||||
|
||||
Vendored
+17
-5
@@ -1,11 +1,13 @@
|
||||
"Test , coverage 17%."
|
||||
|
||||
from idlelib import iomenu
|
||||
import os
|
||||
import unittest
|
||||
from unittest import mock
|
||||
from test.support import requires
|
||||
from tkinter import Tk
|
||||
from tkinter import Tk, TclVersion
|
||||
from idlelib.editor import EditorWindow
|
||||
from idlelib import util
|
||||
from idlelib import macosx, util
|
||||
from idlelib.idle_test.mock_idle import Func
|
||||
|
||||
# Fail if either tokenize.open and t.detect_encoding does not exist.
|
||||
@@ -23,15 +25,14 @@ class IOBindingTest(unittest.TestCase):
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.editwin = EditorWindow(root=cls.root)
|
||||
cls.io = iomenu.IOBinding(cls.editwin)
|
||||
cls.io = cls.editwin.io
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.io.close()
|
||||
cls.editwin._close()
|
||||
del cls.editwin
|
||||
cls.root.update_idletasks()
|
||||
for id in cls.root.tk.call('after', 'info'):
|
||||
for id in cls.root.after_info():
|
||||
cls.root.after_cancel(id) # Need for EditorWindow.
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
@@ -39,6 +40,17 @@ class IOBindingTest(unittest.TestCase):
|
||||
def test_init(self):
|
||||
self.assertIs(self.io.editwin, self.editwin)
|
||||
|
||||
def test_defaultfilename_tilde(self):
|
||||
# gh-59568: escape a leading tilde for Tcl older than 9.
|
||||
io = self.io
|
||||
with mock.patch.object(io, 'filename', os.path.join('dir', '~file.py')):
|
||||
dirname, base = io.defaultfilename()
|
||||
self.assertEqual(dirname, 'dir')
|
||||
if TclVersion < 9 and not macosx.isAquaTk():
|
||||
self.assertEqual(base, os.path.join(os.curdir, '~file.py'))
|
||||
else:
|
||||
self.assertEqual(base, '~file.py')
|
||||
|
||||
def test_fixnewlines_end(self):
|
||||
eq = self.assertEqual
|
||||
io = self.io
|
||||
|
||||
+1
@@ -22,6 +22,7 @@ class MainMenuTest(unittest.TestCase):
|
||||
# On failure, change pattern here and in function(s).
|
||||
# Patterns here have '.*' for re instead of '*' for tcl.
|
||||
for menu, pattern in (
|
||||
('debug', '.*ebugger'), # PyShell.debug_menu_postcommand
|
||||
('debug', '.*tack.*iewer'), # PyShell.debug_menu_postcommand
|
||||
('options', '.*ode.*ontext'), # EW.__init__, CodeContext.toggle...
|
||||
('options', '.*ine.*umbers'), # EW.__init__, EW.toggle...event.
|
||||
|
||||
+48
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
from idlelib import multicall
|
||||
import unittest
|
||||
from test.support import requires
|
||||
from test.support import requires, captured_stderr
|
||||
from tkinter import Tk, Text
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class MultiCallTest(unittest.TestCase):
|
||||
def tearDownClass(cls):
|
||||
del cls.mc
|
||||
cls.root.update_idletasks()
|
||||
## for id in cls.root.tk.call('after', 'info'):
|
||||
## for id in cls.root.after_info():
|
||||
## cls.root.after_cancel(id) # Need for EditorWindow.
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
@@ -43,6 +43,52 @@ class MultiCallTest(unittest.TestCase):
|
||||
mctext = self.mc(self.root)
|
||||
self.assertIs(mctext.yview.__func__, Text.yview)
|
||||
|
||||
def test_valid_binding(self):
|
||||
# A valid key binding must bind without a warning (cf. gh-55646).
|
||||
mctext = self.mc(self.root)
|
||||
with captured_stderr() as stderr:
|
||||
mctext.event_add('<<test-good>>', '<Control-Key-Up>')
|
||||
mctext.bind('<<test-good>>', lambda e: None)
|
||||
self.assertEqual(stderr.getvalue(), '')
|
||||
|
||||
def test_invalid_triplet_binding(self):
|
||||
# gh-55646: '<Control-Key-up>' parses into a triplet on every platform,
|
||||
# so Tk rejects it in bind().
|
||||
mctext = self.mc(self.root)
|
||||
with captured_stderr() as stderr:
|
||||
mctext.event_add('<<test-bad>>', '<Control-Key-up>') # Must not raise.
|
||||
mctext.bind('<<test-bad>>', lambda e: None) # Must not raise.
|
||||
warning = stderr.getvalue()
|
||||
self.assertIn('invalid key binding', warning)
|
||||
self.assertIn('test-bad', warning) # The offending action is named.
|
||||
|
||||
def test_invalid_nontriplet_binding(self):
|
||||
# gh-55646: '<Foo-Key-Up>' has no valid modifier, so MultiCall does not
|
||||
# parse it and falls back to Tk's event_add, which rejects it.
|
||||
mctext = self.mc(self.root)
|
||||
with captured_stderr() as stderr:
|
||||
mctext.event_add('<<test-bad>>', '<Foo-Key-Up>') # Must not raise.
|
||||
mctext.bind('<<test-bad>>', lambda e: None) # Must not raise.
|
||||
warning = stderr.getvalue()
|
||||
self.assertIn('invalid key binding', warning)
|
||||
self.assertIn('test-bad', warning) # The offending action is named.
|
||||
|
||||
def test_event_delete_unbound_sequence(self):
|
||||
# gh-89360: deleting a sequence that was not added to a virtual
|
||||
# event is ignored instead of raising ValueError.
|
||||
mctext = self.mc(self.root)
|
||||
mctext.event_add('<<tester>>', '<Control-Key-a>')
|
||||
info = mctext.event_info('<<tester>>')
|
||||
self.assertEqual(len(info), 1)
|
||||
|
||||
# A different sequence, never added: a no-op, not an error.
|
||||
mctext.event_delete('<<tester>>', '<Control-Key-b>')
|
||||
self.assertEqual(mctext.event_info('<<tester>>'), info)
|
||||
|
||||
# The added sequence can still be deleted normally.
|
||||
mctext.event_delete('<<tester>>', '<Control-Key-a>')
|
||||
self.assertNotIn(info[0], mctext.event_info('<<tester>>'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
Vendored
+11
@@ -41,6 +41,17 @@ class OutputWindowTest(unittest.TestCase):
|
||||
self.assertFalse(w.ispythonsource('test.txt'))
|
||||
self.assertFalse(w.ispythonsource(__file__))
|
||||
|
||||
def test_save_defaults_to_text(self):
|
||||
# gh-65339: output is saved as text, not Python source.
|
||||
io = self.window.io
|
||||
self.assertEqual(io.defaultextension, '.txt')
|
||||
# Text files are offered before Python files.
|
||||
self.assertEqual(io.save_filetypes[0][0], 'Text files')
|
||||
|
||||
def test_open_defaults_to_python(self):
|
||||
# gh-158067: File Open still lists Python files first.
|
||||
self.assertEqual(self.window.io.filetypes[0][0], 'Python files')
|
||||
|
||||
def test_window_title(self):
|
||||
self.assertEqual(self.window.top.title(), 'Output' + ' (%s)' % platform.python_version())
|
||||
|
||||
|
||||
+50
-1
@@ -2,6 +2,7 @@
|
||||
# Plus coverage of test_warning. Was 20% with test_openshell.
|
||||
|
||||
from idlelib import pyshell
|
||||
import os
|
||||
import unittest
|
||||
from test.support import requires
|
||||
from tkinter import Tk
|
||||
@@ -28,6 +29,14 @@ class FunctionTest(unittest.TestCase):
|
||||
self.assertEqual(pyshell.restart_line(width, ''), expect)
|
||||
self.assertEqual(pyshell.restart_line(taglen+2, ''), expect+' =')
|
||||
|
||||
def test_fix_user_path(self):
|
||||
# gh-134300: the idlelib directory is removed, other entries kept.
|
||||
eq = self.assertEqual
|
||||
idlelib_dir = os.path.dirname(os.path.abspath(pyshell.__file__))
|
||||
eq(pyshell.fix_user_path(['', '/a', idlelib_dir, '/b']), ['', '/a', '/b'])
|
||||
eq(pyshell.fix_user_path(['/a', '/b']), ['/a', '/b'])
|
||||
eq(pyshell.fix_user_path([idlelib_dir]), [])
|
||||
|
||||
|
||||
class PyShellFileListTest(unittest.TestCase):
|
||||
|
||||
@@ -40,7 +49,7 @@ class PyShellFileListTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
#cls.root.update_idletasks()
|
||||
## for id in cls.root.tk.call('after', 'info'):
|
||||
## for id in cls.root.after_info():
|
||||
## cls.root.after_cancel(id) # Need for EditorWindow.
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
@@ -60,6 +69,46 @@ class PyShellFileListTest(unittest.TestCase):
|
||||
## self.assertIsInstance(ps, pyshell.PyShell)
|
||||
|
||||
|
||||
class PyShellTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.shell = pyshell.PyShell(pyshell.PyShellFileList(cls.root))
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.shell.close()
|
||||
del cls.shell
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def setUp(self):
|
||||
text = self.shell.text
|
||||
self.shell.per.bottom.delete('1.0', 'end')
|
||||
text.mark_set('iomark', '1.0') # As after begin().
|
||||
text.mark_gravity('iomark', 'left')
|
||||
self.shell.undo.reset_undo()
|
||||
|
||||
def test_output_at_prompt(self):
|
||||
# gh-75512: output from a thread while a prompt is shown goes
|
||||
# before the prompt, which stays with the input in progress.
|
||||
shell = self.shell
|
||||
text = shell.text
|
||||
shell.write('x\n', 'stdout')
|
||||
shell.resetoutput()
|
||||
text.tag_add('console', 'iomark-1c') # As showprompt() does.
|
||||
text.insert('end-1c', 'a = (')
|
||||
shell.write('hel', 'stdout')
|
||||
shell.write('lo\n', 'stdout')
|
||||
self.assertEqual(text.get('iomark-6c', 'end-1c'), 'hello\na = (')
|
||||
self.assertIn('console', text.tag_names('iomark-1c'))
|
||||
self.assertNotIn('console', text.tag_names('iomark-7c'))
|
||||
self.assertEqual(shell.shell_sidebar.line_prompts, {3: '>>>'})
|
||||
|
||||
|
||||
class PyShellRemoveLastNewlineAndSurroundingWhitespaceTest(unittest.TestCase):
|
||||
regexp = pyshell.PyShell._last_newline_re
|
||||
|
||||
|
||||
+81
-5
@@ -246,32 +246,108 @@ class ReplaceDialogTest(unittest.TestCase):
|
||||
equal(text.get('1.2', '1.5'), 'was')
|
||||
|
||||
def test_replace_all(self):
|
||||
# The default mode, forward with wrap around, replaces every
|
||||
# match, both below and above the current position.
|
||||
equal = self.assertEqual
|
||||
text = self.text
|
||||
pv = self.engine.patvar
|
||||
rv = self.dialog.replvar
|
||||
replace_all = self.dialog.replace_all
|
||||
|
||||
text.insert('insert', '\n')
|
||||
text.insert('insert', text.get('1.0', 'end')*100)
|
||||
pv.set('is')
|
||||
rv.set('was')
|
||||
text.delete('1.0', 'end')
|
||||
text.insert('1.0', 'a\na\na\n')
|
||||
text.mark_set('insert', '2.1')
|
||||
pv.set('a')
|
||||
rv.set('b')
|
||||
replace_all()
|
||||
self.assertNotIn('is', text.get('1.0', 'end'))
|
||||
equal(text.get('1.0', '3.end'), 'b\nb\nb') # Wrapped around.
|
||||
|
||||
# An empty regular expression is reported as an error.
|
||||
self.engine.revar.set(True)
|
||||
pv.set('')
|
||||
replace_all()
|
||||
self.assertIn('error', showerror.title)
|
||||
self.assertIn('Empty', showerror.message)
|
||||
|
||||
# An invalid replacement expression is reported as an error,
|
||||
# and nothing is replaced.
|
||||
text.delete('1.0', 'end')
|
||||
text.insert('1.0', 'asT')
|
||||
pv.set('[s][T]')
|
||||
rv.set('\\')
|
||||
replace_all()
|
||||
self.assertIn('error', showerror.title)
|
||||
self.assertIn('Invalid Replace Expression', showerror.message)
|
||||
equal(text.get('1.0', '1.end'), 'asT')
|
||||
|
||||
# A pattern that is not present replaces nothing.
|
||||
self.engine.revar.set(False)
|
||||
text.delete('1.0', 'end')
|
||||
text.insert('1.0', 'unchanged')
|
||||
pv.set('text which is not present')
|
||||
rv.set('foobar')
|
||||
replace_all()
|
||||
equal(text.get('1.0', '1.end'), 'unchanged')
|
||||
|
||||
def test_replace_all_backwards_no_wrap(self):
|
||||
# gh-71956: 'up' without wrap replaces all matches from the start
|
||||
# of the text down to the current position, not just one up.
|
||||
equal = self.assertEqual
|
||||
text = self.text
|
||||
pv = self.engine.patvar
|
||||
rv = self.dialog.replvar
|
||||
replace_all = self.dialog.replace_all
|
||||
self.engine.backvar.set(True)
|
||||
self.engine.wrapvar.set(False)
|
||||
|
||||
text.delete('1.0', 'end')
|
||||
text.insert('1.0', 'a\na\na\n')
|
||||
text.mark_set('insert', '2.1')
|
||||
pv.set('a')
|
||||
rv.set('b')
|
||||
replace_all()
|
||||
equal(text.get('1.0', '1.end'), 'b') # Above the cursor.
|
||||
equal(text.get('2.0', '2.end'), 'b') # At the cursor.
|
||||
equal(text.get('3.0', '3.end'), 'a') # Below the cursor, untouched.
|
||||
|
||||
def test_replace_all_forwards_no_wrap(self):
|
||||
# 'down' without wrap replaces all matches from the current
|
||||
# position to the end of the text, and none before it.
|
||||
equal = self.assertEqual
|
||||
text = self.text
|
||||
pv = self.engine.patvar
|
||||
rv = self.dialog.replvar
|
||||
replace_all = self.dialog.replace_all
|
||||
self.engine.backvar.set(False)
|
||||
self.engine.wrapvar.set(False)
|
||||
|
||||
text.delete('1.0', 'end')
|
||||
text.insert('1.0', 'a\na\na\n')
|
||||
text.mark_set('insert', '2.1')
|
||||
pv.set('a')
|
||||
rv.set('b')
|
||||
replace_all()
|
||||
equal(text.get('1.0', '1.end'), 'a') # Before the cursor, untouched.
|
||||
equal(text.get('2.0', '2.end'), 'a') # Before the cursor, untouched.
|
||||
equal(text.get('3.0', '3.end'), 'b') # After the cursor.
|
||||
|
||||
def test_replace_all_backwards_wrap(self):
|
||||
# With wrap around, an 'up' search also replaces every match.
|
||||
equal = self.assertEqual
|
||||
text = self.text
|
||||
pv = self.engine.patvar
|
||||
rv = self.dialog.replvar
|
||||
replace_all = self.dialog.replace_all
|
||||
self.engine.backvar.set(True)
|
||||
self.engine.wrapvar.set(True)
|
||||
|
||||
text.delete('1.0', 'end')
|
||||
text.insert('1.0', 'a\na\na\n')
|
||||
text.mark_set('insert', '2.1')
|
||||
pv.set('a')
|
||||
rv.set('b')
|
||||
replace_all()
|
||||
equal(text.get('1.0', '3.end'), 'b\nb\nb')
|
||||
|
||||
def test_default_command(self):
|
||||
text = self.text
|
||||
|
||||
Vendored
+19
@@ -1,9 +1,28 @@
|
||||
"Test rpc, coverage 20%."
|
||||
|
||||
from idlelib import rpc
|
||||
import socket
|
||||
import struct
|
||||
import unittest
|
||||
|
||||
|
||||
class SocketIOTest(unittest.TestCase):
|
||||
|
||||
def test_reconnect_discards_partial_packet(self):
|
||||
# gh-89544: RPCClient.accept() reinitializes the SocketIO after
|
||||
# a restart; a partially received packet must not be kept.
|
||||
old_sock, old_peer = socket.socketpair()
|
||||
new_sock, new_peer = socket.socketpair()
|
||||
with old_sock, old_peer, new_sock, new_peer:
|
||||
sockio = rpc.SocketIO(old_sock)
|
||||
old_peer.sendall(struct.pack('<i', 100) + b'x' * 10)
|
||||
self.assertIsNone(sockio.pollpacket(1))
|
||||
sockio.close()
|
||||
rpc.SocketIO.__init__(sockio, new_sock)
|
||||
new_peer.sendall(struct.pack('<i', 3) + b'abc')
|
||||
self.assertEqual(sockio.pollpacket(1), b'abc')
|
||||
|
||||
|
||||
|
||||
class CodePicklerTest(unittest.TestCase):
|
||||
|
||||
|
||||
Vendored
+94
-1
@@ -44,7 +44,7 @@ class ExceptionTest(unittest.TestCase):
|
||||
"Or did you forget to import 'abc'?\n"),
|
||||
('int.reel', AttributeError,
|
||||
"type object 'int' has no attribute 'reel'. "
|
||||
"Did you mean: 'real'?\n"),
|
||||
"Did you mean '.real' instead of '.reel'?\n"), # More in 3.15.
|
||||
)
|
||||
|
||||
@force_not_colorized
|
||||
@@ -82,6 +82,99 @@ class ExceptionTest(unittest.TestCase):
|
||||
subtests += 1
|
||||
self.assertEqual(subtests, len(data2)) # All subtests ran?
|
||||
|
||||
def _capture_exception(self):
|
||||
"""Call run.print_exception() and return its stderr output."""
|
||||
with captured_stderr() as output:
|
||||
with mock.patch.object(run, 'cleanup_traceback') as ct:
|
||||
ct.side_effect = lambda t, e: t
|
||||
run.print_exception()
|
||||
return output.getvalue()
|
||||
|
||||
@force_not_colorized
|
||||
def test_print_exception_group_nested(self):
|
||||
try:
|
||||
try:
|
||||
raise ExceptionGroup('inner', [ValueError('v1')])
|
||||
except ExceptionGroup as inner:
|
||||
raise ExceptionGroup('outer', [inner, TypeError('t1')])
|
||||
except ExceptionGroup:
|
||||
tb = self._capture_exception()
|
||||
|
||||
self.assertIn('ExceptionGroup: outer (2 sub-exceptions)', tb)
|
||||
self.assertIn('ExceptionGroup: inner', tb)
|
||||
self.assertIn('ValueError: v1', tb)
|
||||
self.assertIn('TypeError: t1', tb)
|
||||
# Verify tree structure characters.
|
||||
self.assertIn('+-+---------------- 1 ----------------', tb)
|
||||
self.assertIn('+---------------- 2 ----------------', tb)
|
||||
self.assertIn('+------------------------------------', tb)
|
||||
|
||||
@force_not_colorized
|
||||
def test_print_exception_group_chaining(self):
|
||||
# __cause__ on a sub-exception exercises the prefixed
|
||||
# chaining-message path (margin chars on separator lines).
|
||||
sub = TypeError('t1')
|
||||
sub.__cause__ = ValueError('original')
|
||||
try:
|
||||
raise ExceptionGroup('eg1', [sub])
|
||||
except ExceptionGroup:
|
||||
tb = self._capture_exception()
|
||||
self.assertIn('ValueError: original', tb)
|
||||
self.assertIn('| The above exception was the direct cause', tb)
|
||||
self.assertIn('ExceptionGroup: eg1', tb)
|
||||
|
||||
# __context__ (implicit chaining) on a sub-exception.
|
||||
sub = TypeError('t2')
|
||||
sub.__context__ = ValueError('first')
|
||||
try:
|
||||
raise ExceptionGroup('eg2', [sub])
|
||||
except ExceptionGroup:
|
||||
tb = self._capture_exception()
|
||||
self.assertIn('ValueError: first', tb)
|
||||
self.assertIn('| During handling of the above exception', tb)
|
||||
self.assertIn('ExceptionGroup: eg2', tb)
|
||||
|
||||
@force_not_colorized
|
||||
def test_print_exception_group_seen(self):
|
||||
shared = ValueError('shared')
|
||||
try:
|
||||
raise ExceptionGroup('eg', [shared, shared])
|
||||
except ExceptionGroup:
|
||||
tb = self._capture_exception()
|
||||
|
||||
self.assertIn('ValueError: shared', tb)
|
||||
self.assertIn('<exception ValueError has printed>', tb)
|
||||
|
||||
@force_not_colorized
|
||||
def test_print_exception_group_max_width(self):
|
||||
excs = [ValueError(f'v{i}') for i in range(20)]
|
||||
try:
|
||||
raise ExceptionGroup('eg', excs)
|
||||
except ExceptionGroup:
|
||||
tb = self._capture_exception()
|
||||
|
||||
self.assertIn('+---------------- 15 ----------------', tb)
|
||||
self.assertIn('+---------------- ... ----------------', tb)
|
||||
self.assertIn('and 5 more exceptions', tb)
|
||||
self.assertNotIn('+---------------- 16 ----------------', tb)
|
||||
|
||||
@force_not_colorized
|
||||
def test_print_exception_group_max_depth(self):
|
||||
def make_nested(depth):
|
||||
if depth == 0:
|
||||
return ValueError('leaf')
|
||||
return ExceptionGroup(f'level{depth}',
|
||||
[make_nested(depth - 1)])
|
||||
|
||||
try:
|
||||
raise make_nested(15)
|
||||
except ExceptionGroup:
|
||||
tb = self._capture_exception()
|
||||
|
||||
self.assertIn('... (max_group_depth is 10)', tb)
|
||||
self.assertIn('ExceptionGroup: level15', tb)
|
||||
self.assertNotIn('ValueError: leaf', tb)
|
||||
|
||||
# StdioFile tests.
|
||||
|
||||
class S(str):
|
||||
|
||||
+22
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
from idlelib import runscript
|
||||
import unittest
|
||||
from unittest import mock
|
||||
from test.support import requires
|
||||
from tkinter import Tk
|
||||
from idlelib.editor import EditorWindow
|
||||
@@ -18,7 +19,7 @@ class ScriptBindingTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.root.update_idletasks()
|
||||
for id in cls.root.tk.call('after', 'info'):
|
||||
for id in cls.root.after_info():
|
||||
cls.root.after_cancel(id) # Need for EditorWindow.
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
@@ -28,6 +29,26 @@ class ScriptBindingTest(unittest.TestCase):
|
||||
sb = runscript.ScriptBinding(ew)
|
||||
ew._close()
|
||||
|
||||
def test_run_module_event_shell_busy_no_restart(self):
|
||||
# gh-82183: running without restarting the busy shell aborts.
|
||||
ew = EditorWindow(root=self.root)
|
||||
sb = runscript.ScriptBinding(ew)
|
||||
sb.getfilename = mock.Mock(return_value='test.py')
|
||||
sb.checksyntax = mock.Mock(return_value='code')
|
||||
sb.tabnanny = mock.Mock(return_value=True)
|
||||
sb.shell = shell = mock.Mock()
|
||||
shell.executing = True
|
||||
interp = shell.interp
|
||||
with mock.patch.object(runscript, 'CustomRun') as customrun:
|
||||
# Restart shell unchecked.
|
||||
customrun.return_value.result = (['arg'], False)
|
||||
result = sb.run_module_event(None, customize=True)
|
||||
self.assertEqual(result, 'break')
|
||||
interp.display_executing_dialog.assert_called_once()
|
||||
interp.restart_subprocess.assert_not_called()
|
||||
interp.runcode.assert_not_called()
|
||||
ew._close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
+10
-7
@@ -11,11 +11,10 @@ import tkinter as tk
|
||||
from idlelib.idle_test.tkinter_testing_utils import run_in_tk_mainloop
|
||||
|
||||
from idlelib.delegator import Delegator
|
||||
from idlelib.editor import fixwordbreaks
|
||||
from idlelib.percolator import Percolator
|
||||
import idlelib.pyshell
|
||||
from idlelib.pyshell import fix_x11_paste, PyShell, PyShellFileList
|
||||
from idlelib.run import fix_scaling
|
||||
from idlelib.pyshell import PyShell, PyShellFileList
|
||||
from idlelib.util import fix_scaling, fix_word_breaks, fix_x11_paste
|
||||
import idlelib.sidebar
|
||||
from idlelib.sidebar import get_end_linenumber, get_lineno
|
||||
|
||||
@@ -403,7 +402,7 @@ class ShellSidebarTest(unittest.TestCase):
|
||||
root.withdraw()
|
||||
|
||||
fix_scaling(root)
|
||||
fixwordbreaks(root)
|
||||
fix_word_breaks(root)
|
||||
fix_x11_paste(root)
|
||||
|
||||
cls.flist = flist = PyShellFileList(root)
|
||||
@@ -690,11 +689,15 @@ class ShellSidebarTest(unittest.TestCase):
|
||||
last_lineno = get_end_linenumber(text)
|
||||
self.assertIsNotNone(text.dlineinfo(text.index(f'{last_lineno}.0')))
|
||||
|
||||
# Delta for <MouseWheel>, whose meaning is platform-dependent.
|
||||
# Simulate a mouse wheel notch. Tk 8.7 replaced the X11
|
||||
# <Button-4>/<Button-5> wheel events with <MouseWheel> (whose delta is
|
||||
# platform-dependent); older Tk on X11 still uses the button events.
|
||||
x11_buttons = (sidebar.canvas._windowingsystem == 'x11'
|
||||
and tk.TkVersion < 8.7)
|
||||
delta = 1 if sidebar.canvas._windowingsystem == 'aqua' else 120
|
||||
|
||||
# Scroll up.
|
||||
if sidebar.canvas._windowingsystem == 'x11':
|
||||
if x11_buttons:
|
||||
sidebar.canvas.event_generate('<Button-4>', x=0, y=0)
|
||||
else:
|
||||
sidebar.canvas.event_generate('<MouseWheel>', x=0, y=0, delta=delta)
|
||||
@@ -702,7 +705,7 @@ class ShellSidebarTest(unittest.TestCase):
|
||||
self.assertIsNone(text.dlineinfo(text.index(f'{last_lineno}.0')))
|
||||
|
||||
# Scroll back down.
|
||||
if sidebar.canvas._windowingsystem == 'x11':
|
||||
if x11_buttons:
|
||||
sidebar.canvas.event_generate('<Button-5>', x=0, y=0)
|
||||
else:
|
||||
sidebar.canvas.event_generate('<MouseWheel>', x=0, y=0, delta=-delta)
|
||||
|
||||
+3
-1
@@ -21,7 +21,7 @@ class StackBrowserTest(unittest.TestCase):
|
||||
def tearDownClass(cls):
|
||||
|
||||
cls.root.update_idletasks()
|
||||
## for id in cls.root.tk.call('after', 'info'):
|
||||
## for id in cls.root.after_info():
|
||||
## cls.root.after_cancel(id) # Need for EditorWindow.
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
@@ -35,6 +35,8 @@ class StackBrowserTest(unittest.TestCase):
|
||||
isi(stackviewer.sc, ScrolledCanvas)
|
||||
isi(stackviewer.item, stackviewer.StackTreeItem)
|
||||
isi(stackviewer.node, TreeNode)
|
||||
top = stackviewer.sc.frame.winfo_toplevel()
|
||||
self.assertEqual(top.winfo_class(), 'Idle')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Vendored
+6
-1
@@ -29,7 +29,7 @@ class ListedToplevelTest(unittest.TestCase):
|
||||
def tearDownClass(cls):
|
||||
window.registry = window.WindowList()
|
||||
cls.root.update_idletasks()
|
||||
## for id in cls.root.tk.call('after', 'info'):
|
||||
## for id in cls.root.after_info():
|
||||
## cls.root.after_cancel(id) # Need for EditorWindow.
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
@@ -39,6 +39,11 @@ class ListedToplevelTest(unittest.TestCase):
|
||||
win = window.ListedToplevel(self.root)
|
||||
self.assertIn(win, window.registry)
|
||||
self.assertEqual(win.focused_widget, win)
|
||||
self.assertEqual(win.winfo_class(), 'Idle')
|
||||
|
||||
def test_init_class_override(self):
|
||||
win = window.ListedToplevel(self.root, class_='Other')
|
||||
self.assertEqual(win.winfo_class(), 'Other')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ class Test(unittest.TestCase):
|
||||
def tearDownClass(cls):
|
||||
cls.editwin._close()
|
||||
cls.root.update_idletasks()
|
||||
for id in cls.root.tk.call('after', 'info'):
|
||||
for id in cls.root.after_info():
|
||||
cls.root.after_cancel(id) # Need for EditorWindow.
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
Vendored
+20
-2
@@ -5,11 +5,13 @@ import sys
|
||||
import tempfile
|
||||
import tokenize
|
||||
|
||||
from tkinter import TclVersion
|
||||
from tkinter import filedialog
|
||||
from tkinter import messagebox
|
||||
from tkinter.simpledialog import askstring # loadfile encoding.
|
||||
|
||||
from idlelib.config import idleConf
|
||||
from idlelib import macosx
|
||||
from idlelib.util import py_extensions
|
||||
|
||||
py_extensions = ' '.join("*"+ext for ext in py_extensions)
|
||||
@@ -384,7 +386,18 @@ class IOBinding:
|
||||
("All files", "*"),
|
||||
)
|
||||
|
||||
save_filetypes = filetypes
|
||||
|
||||
# Output windows (Shell, Output) are not Python source, so they save
|
||||
# with text files listed first and default to ".txt" (gh-65339).
|
||||
text_filetypes = (
|
||||
("Text files", "*.txt", "TEXT"),
|
||||
("Python files", py_extensions, "TEXT"),
|
||||
("All files", "*"),
|
||||
)
|
||||
|
||||
defaultextension = '.py' if sys.platform == 'darwin' else ''
|
||||
text_defaultextension = '.txt'
|
||||
|
||||
def askopenfile(self):
|
||||
dir, base = self.defaultfilename("open")
|
||||
@@ -396,7 +409,12 @@ class IOBinding:
|
||||
|
||||
def defaultfilename(self, mode="open"):
|
||||
if self.filename:
|
||||
return os.path.split(self.filename)
|
||||
dirname, base = os.path.split(self.filename)
|
||||
if base[:1] == '~' and TclVersion < 9 and not macosx.isAquaTk():
|
||||
# Tcl before version 9 substitutes a leading tilde in
|
||||
# a file name (gh-59568). The macOS dialog does not.
|
||||
base = os.path.join(os.curdir, base)
|
||||
return dirname, base
|
||||
elif self.dirname:
|
||||
return self.dirname, ""
|
||||
else:
|
||||
@@ -411,7 +429,7 @@ class IOBinding:
|
||||
if not self.savedialog:
|
||||
self.savedialog = filedialog.SaveAs(
|
||||
parent=self.text,
|
||||
filetypes=self.filetypes,
|
||||
filetypes=self.save_filetypes,
|
||||
defaultextension=self.defaultextension)
|
||||
filename = self.savedialog.show(initialdir=dir, initialfile=base)
|
||||
return filename
|
||||
|
||||
Vendored
+1
-1
@@ -39,7 +39,7 @@ def _init_tk_type():
|
||||
_tk_type = "xquartz"
|
||||
elif 'aqua' not in ws:
|
||||
_tk_type = "other"
|
||||
elif 'AppKit' in root.tk.call('winfo', 'server', '.'):
|
||||
elif 'AppKit' in root.winfo_server():
|
||||
_tk_type = "cocoa"
|
||||
else:
|
||||
_tk_type = "carbon"
|
||||
|
||||
Vendored
+34
-4
@@ -310,6 +310,17 @@ def _triplet_to_sequence(triplet):
|
||||
else:
|
||||
return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
|
||||
|
||||
|
||||
def warn_bad_binding(virtual, sequence, err):
|
||||
# gh-55646: warn instead of crashing on an invalid key binding.
|
||||
action = virtual[2:-2] if virtual[:2] == '<<' and virtual[-2:] == '>>' \
|
||||
else virtual
|
||||
print(f'Warning: ignoring invalid key binding {sequence!r} '
|
||||
f'for {action!r}: {err}. '
|
||||
f'Please reconfigure it in the IDLE Settings dialog.',
|
||||
file=sys.stderr)
|
||||
|
||||
|
||||
_multicall_dict = {}
|
||||
def MultiCallCreator(widget):
|
||||
"""Return a MultiCall class which inherits its methods from the
|
||||
@@ -343,8 +354,17 @@ def MultiCallCreator(widget):
|
||||
self.__binders[triplet[1]].unbind(triplet, ei[0])
|
||||
ei[0] = func
|
||||
if ei[0] is not None:
|
||||
bad = []
|
||||
for triplet in ei[1]:
|
||||
self.__binders[triplet[1]].bind(triplet, func)
|
||||
try:
|
||||
self.__binders[triplet[1]].bind(triplet, func)
|
||||
except tkinter.TclError as err:
|
||||
warn_bad_binding(sequence,
|
||||
_triplet_to_sequence(triplet),
|
||||
err)
|
||||
bad.append(triplet)
|
||||
for triplet in bad: # Drop the invalid sequences.
|
||||
ei[1].remove(triplet)
|
||||
else:
|
||||
self.__eventinfo[sequence] = [func, []]
|
||||
return widget.bind(self, sequence, func, add)
|
||||
@@ -371,10 +391,19 @@ def MultiCallCreator(widget):
|
||||
triplet = _parse_sequence(seq)
|
||||
if triplet is None:
|
||||
#print("Tkinter event_add(%s)" % seq, file=sys.__stderr__)
|
||||
widget.event_add(self, virtual, seq)
|
||||
try:
|
||||
widget.event_add(self, virtual, seq)
|
||||
except tkinter.TclError as err:
|
||||
warn_bad_binding(virtual, seq, err)
|
||||
continue # Drop the invalid sequence.
|
||||
else:
|
||||
if func is not None:
|
||||
self.__binders[triplet[1]].bind(triplet, func)
|
||||
try:
|
||||
self.__binders[triplet[1]].bind(triplet, func)
|
||||
except tkinter.TclError as err:
|
||||
warn_bad_binding(virtual,
|
||||
_triplet_to_sequence(triplet), err)
|
||||
continue # Drop the invalid sequence.
|
||||
triplets.append(triplet)
|
||||
|
||||
def event_delete(self, virtual, *sequences):
|
||||
@@ -386,10 +415,11 @@ def MultiCallCreator(widget):
|
||||
if triplet is None:
|
||||
#print("Tkinter event_delete: %s" % seq, file=sys.__stderr__)
|
||||
widget.event_delete(self, virtual, seq)
|
||||
else:
|
||||
elif triplet in triplets:
|
||||
if func is not None:
|
||||
self.__binders[triplet[1]].unbind(triplet, func)
|
||||
triplets.remove(triplet)
|
||||
# Else the sequence is not bound; ignore it (gh-89360).
|
||||
|
||||
def event_info(self, virtual=None):
|
||||
if virtual is None or virtual not in self.__eventinfo:
|
||||
|
||||
Vendored
+4
@@ -78,6 +78,10 @@ class OutputWindow(EditorWindow):
|
||||
def __init__(self, *args):
|
||||
EditorWindow.__init__(self, *args)
|
||||
self.text.bind("<<goto-file-line>>", self.goto_file_line)
|
||||
# Output is not Python source, so save it as text by default
|
||||
# (gh-65339).
|
||||
self.io.save_filetypes = self.io.text_filetypes
|
||||
self.io.defaultextension = self.io.text_defaultextension
|
||||
|
||||
# Customize EditorWindow
|
||||
def ispythonsource(self, filename):
|
||||
|
||||
Vendored
+40
-24
@@ -36,13 +36,14 @@ from idlelib.config import idleConf
|
||||
from idlelib.delegator import Delegator
|
||||
from idlelib import debugger
|
||||
from idlelib import debugger_r
|
||||
from idlelib.editor import EditorWindow, fixwordbreaks
|
||||
from idlelib.editor import EditorWindow
|
||||
from idlelib.filelist import FileList
|
||||
from idlelib.outwin import OutputWindow
|
||||
from idlelib import replace
|
||||
from idlelib import rpc
|
||||
from idlelib.run import idle_formatwarning, StdInputFile, StdOutputFile
|
||||
from idlelib.undo import UndoDelegator
|
||||
from idlelib.util import fix_word_breaks
|
||||
|
||||
# Default for testing; defaults to True in main() for running.
|
||||
use_subprocess = False
|
||||
@@ -242,12 +243,13 @@ class PyShellEditorWindow(EditorWindow):
|
||||
breaks = self.breakpoints
|
||||
filename = self.io.filename
|
||||
try:
|
||||
with open(self.breakpointPath) as fp:
|
||||
with open(self.breakpointPath,
|
||||
encoding='utf-8', errors='replace') as fp:
|
||||
lines = fp.readlines()
|
||||
except OSError:
|
||||
lines = []
|
||||
try:
|
||||
with open(self.breakpointPath, "w") as new_file:
|
||||
with open(self.breakpointPath, "w", encoding='utf-8') as new_file:
|
||||
for line in lines:
|
||||
if not line.startswith(filename + '='):
|
||||
new_file.write(line)
|
||||
@@ -272,7 +274,8 @@ class PyShellEditorWindow(EditorWindow):
|
||||
if filename is None:
|
||||
return
|
||||
if os.path.isfile(self.breakpointPath):
|
||||
with open(self.breakpointPath) as fp:
|
||||
with open(self.breakpointPath,
|
||||
encoding='utf-8', errors='replace') as fp:
|
||||
lines = fp.readlines()
|
||||
for line in lines:
|
||||
if line.startswith(filename + '='):
|
||||
@@ -405,6 +408,17 @@ def restart_line(width, filename): # See bpo-38141.
|
||||
return tag[:-2] # Remove ' ='.
|
||||
|
||||
|
||||
def fix_user_path(path):
|
||||
"""Return path without the idlelib directory (gh-134300).
|
||||
|
||||
That directory is on sys.path when idle.py is run as a script.
|
||||
Otherwise user code could import idlelib submodules as top-level
|
||||
modules, such as "import help".
|
||||
"""
|
||||
idlelib_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
return [p for p in path if p != idlelib_dir]
|
||||
|
||||
|
||||
class ModifiedInterpreter(InteractiveInterpreter):
|
||||
|
||||
def __init__(self, tkconsole):
|
||||
@@ -498,7 +512,6 @@ class ModifiedInterpreter(InteractiveInterpreter):
|
||||
self.rpcclt.close()
|
||||
self.terminate_subprocess()
|
||||
console = self.tkconsole
|
||||
was_executing = console.executing
|
||||
console.executing = False
|
||||
self.spawn_subprocess()
|
||||
try:
|
||||
@@ -566,6 +579,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
|
||||
path.extend(sys.path)
|
||||
else:
|
||||
path = sys.path
|
||||
path = fix_user_path(path) # gh-134300
|
||||
|
||||
self.runcommand("""if 1:
|
||||
import sys as _sys
|
||||
@@ -642,7 +656,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
|
||||
return
|
||||
item = debugobj_r.StubObjectTreeItem(self.rpcclt, oid)
|
||||
from idlelib.tree import ScrolledCanvas, TreeNode
|
||||
top = Toplevel(self.tkconsole.root)
|
||||
top = Toplevel(self.tkconsole.root, class_='Idle')
|
||||
theme = idleConf.CurrentTheme()
|
||||
background = idleConf.GetHighlight(theme, 'normal')['background']
|
||||
sc = ScrolledCanvas(top, bg=background, highlightthickness=0)
|
||||
@@ -680,6 +694,8 @@ class ModifiedInterpreter(InteractiveInterpreter):
|
||||
|
||||
def runsource(self, source):
|
||||
"Extend base class method: Stuff the source in the line cache first"
|
||||
# Remove the highlighting of a previous syntax error (gh-93966).
|
||||
self.tkconsole.text.tag_remove("ERROR", "1.0", "end")
|
||||
filename = self.stuffsource(source)
|
||||
# at the moment, InteractiveInterpreter expects str
|
||||
assert isinstance(source, str)
|
||||
@@ -716,7 +732,6 @@ class ModifiedInterpreter(InteractiveInterpreter):
|
||||
"""
|
||||
tkconsole = self.tkconsole
|
||||
text = tkconsole.text
|
||||
text.tag_remove("ERROR", "1.0", "end")
|
||||
type, value, tb = sys.exc_info()
|
||||
msg = getattr(value, 'msg', '') or value or "<no detail available>"
|
||||
lineno = getattr(value, 'lineno', '') or 1
|
||||
@@ -880,9 +895,9 @@ class PyShell(OutputWindow):
|
||||
if ms[2][0] != "shell":
|
||||
ms.insert(2, ("shell", "She_ll"))
|
||||
self.interp = ModifiedInterpreter(self)
|
||||
if flist is None:
|
||||
if flist is None: # TODO possible? root and flist in main.
|
||||
root = Tk()
|
||||
fixwordbreaks(root)
|
||||
fix_word_breaks(root)
|
||||
root.withdraw()
|
||||
flist = PyShellFileList(root)
|
||||
|
||||
@@ -1085,6 +1100,7 @@ class PyShell(OutputWindow):
|
||||
|
||||
def debug_menu_postcommand(self):
|
||||
state = 'disabled' if self.executing else 'normal'
|
||||
self.update_menu_state('debug', '*ebugger', state)
|
||||
self.update_menu_state('debug', '*tack*iewer', state)
|
||||
|
||||
def beginexecuting(self):
|
||||
@@ -1417,13 +1433,24 @@ class PyShell(OutputWindow):
|
||||
self.ctip.remove_calltip_window()
|
||||
|
||||
def write(self, s, tags=()):
|
||||
text = self.text
|
||||
# Move the prompt (the "console" tag on the preceding newline)
|
||||
# after output which comes while it is shown (gh-75512).
|
||||
at_prompt = (s and tags in ("stdout", "stderr")
|
||||
and not self.executing and not self.reading)
|
||||
if at_prompt:
|
||||
text.tag_remove("console", "iomark-1c")
|
||||
text.tag_remove("stdin", "iomark-1c")
|
||||
try:
|
||||
self.text.mark_gravity("iomark", "right")
|
||||
text.mark_gravity("iomark", "right")
|
||||
count = OutputWindow.write(self, s, tags, "iomark")
|
||||
self.text.mark_gravity("iomark", "left")
|
||||
text.mark_gravity("iomark", "left")
|
||||
except:
|
||||
raise ###pass # ### 11Aug07 KBK if we are expecting exceptions
|
||||
# let's find out what they are and be specific.
|
||||
if at_prompt and s.endswith('\n'):
|
||||
text.tag_add("console", "iomark-1c")
|
||||
self.shell_sidebar.update_sidebar()
|
||||
if self.canceled:
|
||||
self.canceled = False
|
||||
if not use_subprocess:
|
||||
@@ -1451,17 +1478,6 @@ class PyShell(OutputWindow):
|
||||
self.shell_sidebar.update_sidebar()
|
||||
|
||||
|
||||
def fix_x11_paste(root):
|
||||
"Make paste replace selection on x11. See issue #5124."
|
||||
if root._windowingsystem == 'x11':
|
||||
for cls in 'Text', 'Entry', 'Spinbox':
|
||||
root.bind_class(
|
||||
cls,
|
||||
'<<Paste>>',
|
||||
'catch {%W delete sel.first sel.last}\n' +
|
||||
root.bind_class(cls, '<<Paste>>'))
|
||||
|
||||
|
||||
usage_msg = """\
|
||||
|
||||
USAGE: idle [-deins] [-t title] [file]*
|
||||
@@ -1521,6 +1537,7 @@ def main():
|
||||
from platform import system
|
||||
from idlelib import testing # bool value
|
||||
from idlelib import macosx
|
||||
from idlelib.util import fix_scaling, fix_x11_paste
|
||||
|
||||
global flist, root, use_subprocess
|
||||
|
||||
@@ -1606,7 +1623,6 @@ def main():
|
||||
NoDefaultRoot()
|
||||
root = Tk(className="Idle")
|
||||
root.withdraw()
|
||||
from idlelib.run import fix_scaling
|
||||
fix_scaling(root)
|
||||
|
||||
# set application icon
|
||||
@@ -1628,7 +1644,7 @@ def main():
|
||||
root.wm_iconphoto(True, *icons)
|
||||
|
||||
# start editor and/or shell windows:
|
||||
fixwordbreaks(root)
|
||||
fix_word_breaks(root)
|
||||
fix_x11_paste(root)
|
||||
flist = PyShellFileList(root)
|
||||
macosx.setupApp(root, flist)
|
||||
|
||||
Vendored
+21
-8
@@ -122,12 +122,13 @@ class ReplaceDialog(SearchDialogBase):
|
||||
def replace_all(self, event=None):
|
||||
"""Handle the Replace All button.
|
||||
|
||||
Search text for occurrences of the Find value and replace
|
||||
each of them. The 'wrap around' value controls the start
|
||||
point for searching. If wrap isn't set, then the searching
|
||||
starts at the first occurrence after the current selection;
|
||||
if wrap is set, the replacement starts at the first line.
|
||||
The replacement is always done top-to-bottom in the text.
|
||||
Search text for occurrences of the Find value and replace each
|
||||
of them. The 'wrap around' and direction values control which
|
||||
occurrences are replaced. With wrap around, every occurrence is
|
||||
replaced. Without it, a forward search replaces occurrences from
|
||||
the current position to the end of the text, and a backward search
|
||||
replaces occurrences from the beginning of the text to the current
|
||||
position. The replacement is always done top-to-bottom.
|
||||
"""
|
||||
prog = self.engine.getprog()
|
||||
if not prog:
|
||||
@@ -142,9 +143,18 @@ class ReplaceDialog(SearchDialogBase):
|
||||
text.tag_remove("hit", "1.0", "end")
|
||||
line = res[0]
|
||||
col = res[1].start()
|
||||
# For a backward search without wrap, replace top-to-bottom from
|
||||
# the start of the text down to the first match at or above the
|
||||
# current position (gh-71956). A mark tracks that stop point.
|
||||
stop = None
|
||||
if self.engine.iswrap():
|
||||
line = 1
|
||||
col = 0
|
||||
elif self.engine.isback():
|
||||
stop = "replace_all_stop"
|
||||
text.mark_set(stop, "%d.%d" % (line, res[1].end()))
|
||||
line = 1
|
||||
col = 0
|
||||
ok = True
|
||||
first = last = None
|
||||
# XXX ought to replace circular instead of top-to-bottom when wrapping
|
||||
@@ -152,12 +162,13 @@ class ReplaceDialog(SearchDialogBase):
|
||||
while res := self.engine.search_forward(
|
||||
text, prog, line, col, wrap=False, ok=ok):
|
||||
line, m = res
|
||||
chars = text.get("%d.0" % line, "%d.0" % (line+1))
|
||||
i, j = m.span()
|
||||
if stop is not None and text.compare("%d.%d" % (line, i), ">=", stop):
|
||||
break
|
||||
orig = m.group()
|
||||
new = self._replace_expand(m, repl)
|
||||
if new is None:
|
||||
break
|
||||
i, j = m.span()
|
||||
first = "%d.%d" % (line, i)
|
||||
last = "%d.%d" % (line, j)
|
||||
if new == orig:
|
||||
@@ -170,6 +181,8 @@ class ReplaceDialog(SearchDialogBase):
|
||||
text.insert(first, new, self.insert_tags)
|
||||
col = i + len(new)
|
||||
ok = False
|
||||
if stop is not None:
|
||||
text.mark_unset(stop)
|
||||
text.undo_block_stop()
|
||||
if first and last:
|
||||
self.show_hit(first, last)
|
||||
|
||||
Vendored
+5
-4
@@ -139,6 +139,11 @@ class SocketIO:
|
||||
self.objtable = objtable
|
||||
self.responses = {}
|
||||
self.cvars = {}
|
||||
# Receive buffer state. A new connection must not inherit a
|
||||
# partially received packet from the old one (gh-89544).
|
||||
self.buff = b''
|
||||
self.bufneed = 4
|
||||
self.bufstate = 0 # meaning: 0 => reading count; 1 => reading data
|
||||
|
||||
def close(self):
|
||||
sock = self.sock
|
||||
@@ -345,10 +350,6 @@ class SocketIO:
|
||||
raise OSError("socket no longer exists")
|
||||
s = s[n:]
|
||||
|
||||
buff = b''
|
||||
bufneed = 4
|
||||
bufstate = 0 # meaning: 0 => reading count; 1 => reading data
|
||||
|
||||
def pollpacket(self, wait):
|
||||
self._stage0()
|
||||
if len(self.buff) < self.bufneed:
|
||||
|
||||
Vendored
+82
-28
@@ -25,6 +25,7 @@ from idlelib import debugobj_r # remote_object_tree_item
|
||||
from idlelib import iomenu # encoding
|
||||
from idlelib import rpc # multiple objects
|
||||
from idlelib import stackviewer # StackTreeItem
|
||||
from idlelib import util # fix_scaling
|
||||
import __main__
|
||||
|
||||
import tkinter # Use tcl and, if startup fails, messagebox.
|
||||
@@ -216,7 +217,7 @@ def show_socket_error(err, address):
|
||||
import tkinter
|
||||
from tkinter.messagebox import showerror
|
||||
root = tkinter.Tk()
|
||||
fix_scaling(root)
|
||||
util.fix_scaling(root)
|
||||
root.withdraw()
|
||||
showerror(
|
||||
"Subprocess Connection Error",
|
||||
@@ -249,31 +250,94 @@ def print_exception():
|
||||
sys.last_type, sys.last_value, sys.last_traceback = excinfo
|
||||
sys.last_exc = val
|
||||
seen = set()
|
||||
exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
|
||||
"debugger_r.py", "bdb.py")
|
||||
max_group_width = 15
|
||||
max_group_depth = 10
|
||||
group_depth = 0
|
||||
|
||||
def print_exc(typ, exc, tb):
|
||||
def print_exc_group(typ, exc, tb, prefix=""):
|
||||
nonlocal group_depth
|
||||
group_depth += 1
|
||||
prefix2 = prefix or " "
|
||||
if group_depth > max_group_depth:
|
||||
print(f"{prefix2}| ... (max_group_depth is {max_group_depth})",
|
||||
file=efile)
|
||||
group_depth -= 1
|
||||
return
|
||||
if tb:
|
||||
if not prefix:
|
||||
print(" + Exception Group Traceback (most recent call last):", file=efile)
|
||||
else:
|
||||
print(f"{prefix}| Exception Group Traceback (most recent call last):", file=efile)
|
||||
tbe = traceback.extract_tb(tb)
|
||||
cleanup_traceback(tbe, exclude)
|
||||
for line in traceback.format_list(tbe):
|
||||
for subline in line.rstrip().splitlines():
|
||||
print(f"{prefix2}| {subline}", file=efile)
|
||||
lines = get_message_lines(typ, exc, tb)
|
||||
for line in lines:
|
||||
print(f"{prefix2}| {line}", end="", file=efile)
|
||||
num_excs = len(exc.exceptions)
|
||||
if num_excs <= max_group_width:
|
||||
n = num_excs
|
||||
else:
|
||||
n = max_group_width + 1
|
||||
for i, sub in enumerate(exc.exceptions[:n], 1):
|
||||
truncated = (i > max_group_width)
|
||||
first_line_pre = "+-" if i == 1 else " "
|
||||
title = str(i) if not truncated else '...'
|
||||
print(f"{prefix2}{first_line_pre}+---------------- {title} ----------------", file=efile)
|
||||
if truncated:
|
||||
remaining = num_excs - max_group_width
|
||||
plural = 's' if remaining > 1 else ''
|
||||
print(f"{prefix2} | and {remaining} more exception{plural}",
|
||||
file=efile)
|
||||
need_print_underline = True
|
||||
elif id(sub) not in seen:
|
||||
if not prefix:
|
||||
print_exc(type(sub), sub, sub.__traceback__, " ")
|
||||
else:
|
||||
print_exc(type(sub), sub, sub.__traceback__, prefix + " ")
|
||||
need_print_underline = not isinstance(sub, BaseExceptionGroup)
|
||||
else:
|
||||
print(f"{prefix2} | <exception {type(sub).__name__} has printed>", file=efile)
|
||||
need_print_underline = True
|
||||
if need_print_underline and i == n:
|
||||
print(f"{prefix2} +------------------------------------", file=efile)
|
||||
group_depth -= 1
|
||||
|
||||
def print_exc(typ, exc, tb, prefix=""):
|
||||
seen.add(id(exc))
|
||||
context = exc.__context__
|
||||
cause = exc.__cause__
|
||||
prefix2 = f"{prefix}| " if prefix else ""
|
||||
if cause is not None and id(cause) not in seen:
|
||||
print_exc(type(cause), cause, cause.__traceback__)
|
||||
print("\nThe above exception was the direct cause "
|
||||
"of the following exception:\n", file=efile)
|
||||
print_exc(type(cause), cause, cause.__traceback__, prefix)
|
||||
print(f"{prefix2}\n{prefix2}The above exception was the direct cause "
|
||||
f"of the following exception:\n{prefix2}", file=efile)
|
||||
elif (context is not None and
|
||||
not exc.__suppress_context__ and
|
||||
id(context) not in seen):
|
||||
print_exc(type(context), context, context.__traceback__)
|
||||
print("\nDuring handling of the above exception, "
|
||||
"another exception occurred:\n", file=efile)
|
||||
if tb:
|
||||
tbe = traceback.extract_tb(tb)
|
||||
print('Traceback (most recent call last):', file=efile)
|
||||
exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
|
||||
"debugger_r.py", "bdb.py")
|
||||
cleanup_traceback(tbe, exclude)
|
||||
traceback.print_list(tbe, file=efile)
|
||||
lines = get_message_lines(typ, exc, tb)
|
||||
for line in lines:
|
||||
print(line, end='', file=efile)
|
||||
print_exc(type(context), context, context.__traceback__, prefix)
|
||||
print(f"{prefix2}\n{prefix2}During handling of the above exception, "
|
||||
f"another exception occurred:\n{prefix2}", file=efile)
|
||||
if isinstance(exc, BaseExceptionGroup):
|
||||
print_exc_group(typ, exc, tb, prefix=prefix)
|
||||
else:
|
||||
if tb:
|
||||
print(f"{prefix2}Traceback (most recent call last):", file=efile)
|
||||
tbe = traceback.extract_tb(tb)
|
||||
cleanup_traceback(tbe, exclude)
|
||||
if prefix:
|
||||
for line in traceback.format_list(tbe):
|
||||
for subline in line.rstrip().splitlines():
|
||||
print(f"{prefix}| {subline}", file=efile)
|
||||
else:
|
||||
traceback.print_list(tbe, file=efile)
|
||||
lines = get_message_lines(typ, exc, tb)
|
||||
for line in lines:
|
||||
print(f"{prefix2}{line}", end="", file=efile)
|
||||
|
||||
print_exc(typ, val, tb)
|
||||
|
||||
@@ -326,16 +390,6 @@ def exit():
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def fix_scaling(root):
|
||||
"""Scale fonts on HiDPI displays."""
|
||||
import tkinter.font
|
||||
scaling = float(root.tk.call('tk', 'scaling'))
|
||||
if scaling > 1.4:
|
||||
for name in tkinter.font.names(root):
|
||||
font = tkinter.font.Font(root=root, name=name, exists=True)
|
||||
size = int(font['size'])
|
||||
if size < 0:
|
||||
font['size'] = round(-0.75*size)
|
||||
|
||||
|
||||
def fixdoc(fun, text):
|
||||
|
||||
Vendored
+4
@@ -139,6 +139,10 @@ class ScriptBinding:
|
||||
return 'break'
|
||||
self.cli_args, restart = run_args if customize else ([], True)
|
||||
interp = self.shell.interp
|
||||
if self.shell.executing and not restart:
|
||||
# Cannot run without restarting the busy shell (gh-82183).
|
||||
interp.display_executing_dialog()
|
||||
return 'break'
|
||||
if pyshell.use_subprocess and restart:
|
||||
interp.restart_subprocess(
|
||||
with_cwd=False, filename=filename)
|
||||
|
||||
Vendored
+1
-1
@@ -11,7 +11,7 @@ from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas
|
||||
def StackBrowser(root, exc, flist=None, top=None):
|
||||
global sc, item, node # For testing.
|
||||
if top is None:
|
||||
top = tk.Toplevel(root)
|
||||
top = tk.Toplevel(root, class_='Idle')
|
||||
sc = ScrolledCanvas(top, bg="white", highlightthickness=0)
|
||||
sc.frame.pack(expand=1, fill="both")
|
||||
item = StackTreeItem(exc, flist)
|
||||
|
||||
Vendored
+3
-5
@@ -92,10 +92,10 @@ class ViewFrame(Frame):
|
||||
color_config(text)
|
||||
text.focus_set()
|
||||
|
||||
self.button_ok = button_ok = Button(
|
||||
self.button_ok = Button(
|
||||
self, text='Close', command=self.ok, takefocus=False)
|
||||
self.textframe.pack(side='top', expand=True, fill='both')
|
||||
button_ok.pack(side='bottom')
|
||||
self.button_ok.pack(side='bottom')
|
||||
|
||||
def ok(self, event=None):
|
||||
"""Dismiss text viewer dialog."""
|
||||
@@ -127,10 +127,8 @@ class ViewWindow(Toplevel):
|
||||
self.geometry(f'=750x500+{x}+{y}')
|
||||
|
||||
self.title(title)
|
||||
self.viewframe = ViewFrame(self, contents, wrap=wrap)
|
||||
self.protocol("WM_DELETE_WINDOW", self.ok)
|
||||
self.button_ok = button_ok = Button(self, text='Close',
|
||||
command=self.ok, takefocus=False)
|
||||
self.viewframe = ViewFrame(self, contents, wrap=wrap)
|
||||
self.viewframe.pack(side='top', expand=True, fill='both')
|
||||
|
||||
self.is_modal = modal
|
||||
|
||||
Vendored
+33
@@ -19,6 +19,20 @@ import sys
|
||||
py_extensions = ('.py', '.pyw', '.pyi')
|
||||
|
||||
|
||||
# fix_x functions seem only needed once per process.
|
||||
|
||||
def fix_scaling(root): # Called in filelist _test, pyshell, and run.
|
||||
"""Scale fonts on HiDPI displays, once per process."""
|
||||
import tkinter.font
|
||||
scaling = float(root.tk.call('tk', 'scaling'))
|
||||
if scaling > 1.4:
|
||||
for name in tkinter.font.names(root):
|
||||
font = tkinter.font.Font(root=root, name=name, exists=True)
|
||||
size = int(font['size'])
|
||||
if size < 0:
|
||||
font['size'] = round(-0.75*size)
|
||||
|
||||
|
||||
# Fix for HiDPI screens on Windows. CALL BEFORE ANY TK OPERATIONS!
|
||||
# URL for arguments for the ...Awareness call below.
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/dn280512(v=vs.85).aspx
|
||||
@@ -31,6 +45,25 @@ if sys.platform == 'win32': # pragma: no cover
|
||||
except (ImportError, AttributeError, OSError):
|
||||
pass
|
||||
|
||||
def fix_word_breaks(root): # Called in editor htest, filelist _test, pyshell.
|
||||
# On Windows, tcl/tk breaks 'words' only on spaces, as in Command Prompt.
|
||||
# We want Motif style everywhere. See #21474, msg218992 and followup.
|
||||
tk = root.tk
|
||||
tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
|
||||
tk.call('set', 'tcl_wordchars', r'\w')
|
||||
tk.call('set', 'tcl_nonwordchars', r'\W')
|
||||
|
||||
|
||||
def fix_x11_paste(root):
|
||||
"Make paste replace selection on x11. See issue #5124."
|
||||
if root._windowingsystem == 'x11':
|
||||
for cls in 'Text', 'Entry', 'Spinbox':
|
||||
root.bind_class(
|
||||
cls,
|
||||
'<<Paste>>',
|
||||
'catch {%W delete sel.first sel.last}\n' +
|
||||
root.bind_class(cls, '<<Paste>>'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from unittest import main
|
||||
|
||||
Vendored
+4
@@ -61,6 +61,10 @@ unregister_callback = registry.unregister_callback
|
||||
class ListedToplevel(Toplevel):
|
||||
|
||||
def __init__(self, master, **kw):
|
||||
# Set the WM_CLASS property so that X11 window managers group and
|
||||
# label IDLE's windows under 'Idle' instead of the default
|
||||
# 'Toplevel' (gh-66331). It matches the class name passed to Tk().
|
||||
kw.setdefault('class_', 'Idle')
|
||||
Toplevel.__init__(self, master, kw)
|
||||
registry.add(self)
|
||||
self.focused_widget = self
|
||||
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
Writing new tests
|
||||
=================
|
||||
|
||||
Precaution
|
||||
----------
|
||||
|
||||
New tests should always use only one Tk window at once, like all the
|
||||
current tests do. This means that you have to destroy the current window
|
||||
before creating another one, and clean up after the test. The motivation
|
||||
behind this is that some tests may depend on having its window focused
|
||||
while it is running to work properly, and it may be hard to force focus
|
||||
on your window across platforms (right now only test_traversal at
|
||||
test_ttk.test_widgets.NotebookTest depends on this).
|
||||
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
import os.path
|
||||
import unittest
|
||||
|
||||
from test.support import (
|
||||
check_sanitizer,
|
||||
import_helper,
|
||||
load_package_tests,
|
||||
requires,
|
||||
)
|
||||
|
||||
|
||||
if check_sanitizer(address=True, memory=True):
|
||||
# See gh-90791 for details
|
||||
raise unittest.SkipTest("Tests involving libX11 can SEGFAULT on ASAN/MSAN builds")
|
||||
|
||||
# Skip test if _tkinter wasn't built.
|
||||
import_helper.import_module('_tkinter')
|
||||
|
||||
# Skip test if tk cannot be initialized.
|
||||
requires('gui')
|
||||
|
||||
|
||||
def load_tests(*args):
|
||||
return load_package_tests(os.path.dirname(__file__), *args)
|
||||
|
||||
def setUpModule():
|
||||
wantobjects = support.get_resource_value('wantobjects')
|
||||
if wantobjects is not None:
|
||||
unittest.enterModuleContext(
|
||||
support.swap_attr(tkinter, 'wantobjects', int(wantobjects)))
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
from . import load_tests
|
||||
import unittest
|
||||
|
||||
unittest.main()
|
||||
Vendored
+211
@@ -0,0 +1,211 @@
|
||||
import functools
|
||||
import time
|
||||
import tkinter
|
||||
import unittest
|
||||
from test import support
|
||||
|
||||
|
||||
def setUpModule():
|
||||
wantobjects = support.get_resource_value('wantobjects')
|
||||
if wantobjects is not None:
|
||||
unittest.enterModuleContext(
|
||||
support.swap_attr(tkinter, 'wantobjects', int(wantobjects)))
|
||||
|
||||
class AbstractTkTest:
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls._old_support_default_root = tkinter._support_default_root
|
||||
destroy_default_root()
|
||||
tkinter.NoDefaultRoot()
|
||||
cls.root = tkinter.Tk()
|
||||
cls.wantobjects = cls.root.wantobjects()
|
||||
if support.is_resource_enabled('wantobjects'):
|
||||
assert cls.wantobjects == int(support.get_resource_value('wantobjects'))
|
||||
# De-maximize main window.
|
||||
# Some window managers can maximize new windows.
|
||||
cls.root.wm_state('normal')
|
||||
try:
|
||||
cls.root.wm_attributes(zoomed=False)
|
||||
except tkinter.TclError:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.root.update_idletasks()
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
tkinter._default_root = None
|
||||
tkinter._support_default_root = cls._old_support_default_root
|
||||
|
||||
def setUp(self):
|
||||
self.root.deiconify()
|
||||
|
||||
def tearDown(self):
|
||||
for w in self.root.winfo_children():
|
||||
w.destroy()
|
||||
self.root.withdraw()
|
||||
|
||||
def require_mapped(self, widget, timeout=None):
|
||||
"""Realize *widget*, or skip the test if the window manager will
|
||||
not map it (e.g. a tiling WM or a headless/contended display).
|
||||
|
||||
Use this instead of a bare update() before querying realized
|
||||
geometry (winfo_width(), identify(), coords(), place_info(), ...).
|
||||
See gh-69134, gh-74941 and bpo-40722.
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = support.LOOPBACK_TIMEOUT
|
||||
if not wait_until_mapped(widget, timeout):
|
||||
self.skipTest('widget was not mapped by the window manager '
|
||||
f'(timed out after {timeout:g}s)')
|
||||
|
||||
|
||||
class AbstractDialogTest(AbstractTkTest):
|
||||
# Tk delivers generated keyboard events to the focused window. Hide the
|
||||
# root window, otherwise the window manager can take the focus back from
|
||||
# the dialog (gh-154357).
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.root.withdraw()
|
||||
|
||||
|
||||
class AbstractDefaultRootTest:
|
||||
|
||||
def setUp(self):
|
||||
self._old_support_default_root = tkinter._support_default_root
|
||||
destroy_default_root()
|
||||
tkinter._support_default_root = True
|
||||
self.wantobjects = tkinter.wantobjects
|
||||
|
||||
def tearDown(self):
|
||||
destroy_default_root()
|
||||
tkinter._default_root = None
|
||||
tkinter._support_default_root = self._old_support_default_root
|
||||
|
||||
def _test_widget(self, constructor):
|
||||
# no master passing
|
||||
x = constructor()
|
||||
self.assertIsNotNone(tkinter._default_root)
|
||||
self.assertIs(x.master, tkinter._default_root)
|
||||
self.assertIs(x.tk, tkinter._default_root.tk)
|
||||
x.destroy()
|
||||
destroy_default_root()
|
||||
tkinter.NoDefaultRoot()
|
||||
self.assertRaises(RuntimeError, constructor)
|
||||
self.assertNotHasAttr(tkinter, '_default_root')
|
||||
|
||||
|
||||
def destroy_default_root():
|
||||
if getattr(tkinter, '_default_root', None):
|
||||
tkinter._default_root.update_idletasks()
|
||||
tkinter._default_root.destroy()
|
||||
tkinter._default_root = None
|
||||
|
||||
def wait_until_mapped(widget, timeout=None, *, full_size=False):
|
||||
"""Wait until *widget* is actually mapped and laid out by the window
|
||||
manager, so that realized-geometry queries (winfo_width(), identify(),
|
||||
coords(), ...) return meaningful values.
|
||||
|
||||
Return True once the widget is mapped with a non-trivial size, or False
|
||||
if that has not happened within *timeout* seconds (default:
|
||||
``support.LOOPBACK_TIMEOUT``). Unlike Misc.wait_visibility(), this
|
||||
never blocks indefinitely, so it is safe under a window manager that
|
||||
never maps the window (see gh-69134, gh-74941, bpo-40722).
|
||||
|
||||
If *full_size* is true, also wait until the realized size reaches the
|
||||
requested size, so that per-pixel queries near an edge (e.g. identify())
|
||||
are reliable even under load.
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = support.LOOPBACK_TIMEOUT
|
||||
deadline = time.monotonic() + timeout
|
||||
widget.update_idletasks()
|
||||
reset = False
|
||||
while True:
|
||||
widget.update() # drain pending Map/Configure events
|
||||
if widget.winfo_ismapped():
|
||||
if full_size:
|
||||
w_ok = widget.winfo_width() >= widget.winfo_reqwidth() > 1
|
||||
h_ok = widget.winfo_height() >= widget.winfo_reqheight() > 1
|
||||
else:
|
||||
w_ok = widget.winfo_width() > 1
|
||||
h_ok = widget.winfo_height() > 1
|
||||
if w_ok and h_ok:
|
||||
return True
|
||||
if full_size and not reset:
|
||||
# Tk no longer resizes the toplevel to fit its content if
|
||||
# the window manager has resized it. Undo this.
|
||||
widget.winfo_toplevel().wm_geometry('')
|
||||
reset = True
|
||||
if time.monotonic() >= deadline:
|
||||
return False
|
||||
time.sleep(0.01)
|
||||
|
||||
|
||||
def simulate_mouse_click(widget, x, y):
|
||||
"""Generate proper events to click at the x, y position (tries to act
|
||||
like an X server)."""
|
||||
widget.event_generate('<Enter>', x=0, y=0)
|
||||
widget.event_generate('<Motion>', x=x, y=y)
|
||||
widget.event_generate('<ButtonPress-1>', x=x, y=y)
|
||||
widget.event_generate('<ButtonRelease-1>', x=x, y=y)
|
||||
|
||||
|
||||
import _tkinter
|
||||
tcl_version = tuple(map(int, _tkinter.TCL_VERSION.split('.')))
|
||||
tk_version = tuple(map(int, _tkinter.TK_VERSION.split('.')))
|
||||
|
||||
def requires_tk(*version):
|
||||
if len(version) <= 2 and tk_version >= version:
|
||||
return lambda test: test
|
||||
|
||||
def deco(test):
|
||||
@functools.wraps(test)
|
||||
def newtest(self):
|
||||
root = getattr(self, 'root', None)
|
||||
if get_tk_patchlevel(root) < version:
|
||||
self.skipTest('requires Tk version >= ' +
|
||||
'.'.join(map(str, version)))
|
||||
test(self)
|
||||
return newtest
|
||||
return deco
|
||||
|
||||
_tk_patchlevel = None
|
||||
def get_tk_patchlevel(root):
|
||||
global _tk_patchlevel
|
||||
if _tk_patchlevel is None:
|
||||
_tk_patchlevel = tkinter._parse_version(root.tk.globalgetvar('tk_patchLevel'))
|
||||
return _tk_patchlevel
|
||||
|
||||
units = {
|
||||
'c': 72 / 2.54, # centimeters
|
||||
'i': 72, # inches
|
||||
'm': 72 / 25.4, # millimeters
|
||||
'p': 1, # points
|
||||
}
|
||||
|
||||
def pixels_conv(value):
|
||||
return float(value[:-1]) * units[value[-1:]]
|
||||
|
||||
def tcl_obj_eq(actual, expected):
|
||||
if actual == expected:
|
||||
return True
|
||||
if isinstance(actual, _tkinter.Tcl_Obj):
|
||||
if isinstance(expected, str):
|
||||
return str(actual) == expected
|
||||
if isinstance(actual, tuple):
|
||||
if isinstance(expected, tuple):
|
||||
return (len(actual) == len(expected) and
|
||||
all(tcl_obj_eq(act, exp)
|
||||
for act, exp in zip(actual, expected)))
|
||||
return False
|
||||
|
||||
def widget_eq(actual, expected):
|
||||
if actual == expected:
|
||||
return True
|
||||
if isinstance(actual, (str, tkinter.Widget)):
|
||||
if isinstance(expected, (str, tkinter.Widget)):
|
||||
return str(actual) == str(expected)
|
||||
return False
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import unittest
|
||||
import tkinter
|
||||
from test.support import requires, swap_attr
|
||||
from test.test_tkinter.support import setUpModule # noqa: F401
|
||||
from test.test_tkinter.support import AbstractDefaultRootTest, AbstractTkTest
|
||||
from tkinter import colorchooser
|
||||
from tkinter.colorchooser import askcolor
|
||||
from tkinter.commondialog import Dialog
|
||||
|
||||
requires('gui')
|
||||
|
||||
|
||||
class ChooserTest(AbstractTkTest, unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
AbstractTkTest.setUpClass.__func__(cls)
|
||||
cls.cc = colorchooser.Chooser(initialcolor='dark blue slate')
|
||||
|
||||
def test_fixoptions(self):
|
||||
cc = self.cc
|
||||
cc._fixoptions()
|
||||
self.assertEqual(cc.options['initialcolor'], 'dark blue slate')
|
||||
|
||||
cc.options['initialcolor'] = '#D2D269691E1E'
|
||||
cc._fixoptions()
|
||||
self.assertEqual(cc.options['initialcolor'], '#D2D269691E1E')
|
||||
|
||||
cc.options['initialcolor'] = (210, 105, 30)
|
||||
cc._fixoptions()
|
||||
self.assertEqual(cc.options['initialcolor'], '#d2691e')
|
||||
|
||||
def test_fixresult(self):
|
||||
cc = self.cc
|
||||
self.assertEqual(cc._fixresult(self.root, ()), (None, None))
|
||||
self.assertEqual(cc._fixresult(self.root, ''), (None, None))
|
||||
self.assertEqual(cc._fixresult(self.root, 'chocolate'),
|
||||
((210, 105, 30), 'chocolate'))
|
||||
self.assertEqual(cc._fixresult(self.root, '#4a3c8c'),
|
||||
((74, 60, 140), '#4a3c8c'))
|
||||
|
||||
|
||||
class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
|
||||
|
||||
def test_askcolor(self):
|
||||
def test_callback(dialog, master):
|
||||
nonlocal ismapped
|
||||
master.update()
|
||||
ismapped = master.winfo_ismapped()
|
||||
raise ZeroDivisionError
|
||||
|
||||
with swap_attr(Dialog, '_test_callback', test_callback):
|
||||
ismapped = None
|
||||
self.assertRaises(ZeroDivisionError, askcolor)
|
||||
#askcolor()
|
||||
self.assertEqual(ismapped, False)
|
||||
|
||||
root = tkinter.Tk()
|
||||
ismapped = None
|
||||
self.assertRaises(ZeroDivisionError, askcolor)
|
||||
self.assertEqual(ismapped, True)
|
||||
root.destroy()
|
||||
|
||||
tkinter.NoDefaultRoot()
|
||||
self.assertRaises(RuntimeError, askcolor)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Vendored
+171
@@ -0,0 +1,171 @@
|
||||
import unittest
|
||||
import tkinter
|
||||
from tkinter import dnd
|
||||
from test.support import requires
|
||||
from test.test_tkinter.support import setUpModule # noqa: F401
|
||||
from test.test_tkinter.support import AbstractTkTest
|
||||
|
||||
requires('gui')
|
||||
|
||||
|
||||
class Target:
|
||||
def __init__(self, widget, log):
|
||||
self.widget = widget
|
||||
self.log = log
|
||||
widget.dnd_accept = self.dnd_accept
|
||||
|
||||
def dnd_accept(self, source, event):
|
||||
self.log.append('accept')
|
||||
return self
|
||||
|
||||
def dnd_enter(self, source, event):
|
||||
self.log.append('enter')
|
||||
|
||||
def dnd_motion(self, source, event):
|
||||
self.log.append('motion')
|
||||
|
||||
def dnd_leave(self, source, event):
|
||||
self.log.append('leave')
|
||||
|
||||
def dnd_commit(self, source, event):
|
||||
self.log.append('commit')
|
||||
|
||||
|
||||
class Source:
|
||||
def __init__(self, log):
|
||||
self.log = log
|
||||
|
||||
def dnd_end(self, target, event):
|
||||
self.log.append('end')
|
||||
|
||||
|
||||
class FakeEvent:
|
||||
def __init__(self, widget, num=1):
|
||||
self.num = num
|
||||
self.widget = widget
|
||||
self.x = self.y = self.x_root = self.y_root = 0
|
||||
|
||||
|
||||
class DndTest(AbstractTkTest, unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.canvas = tkinter.Canvas(self.root)
|
||||
self.canvas.pack()
|
||||
# on_motion() locates the target with winfo_containing(). Bypass that
|
||||
# real screen lookup, which depends on the window being visible and
|
||||
# unobscured, so the test does not hinge on the window manager.
|
||||
self.canvas.winfo_containing = lambda x, y: self.canvas
|
||||
self.log = []
|
||||
self.source = Source(self.log)
|
||||
self.target = Target(self.canvas, self.log)
|
||||
|
||||
def tearDown(self):
|
||||
# Make sure no drag-and-drop is left active between tests: the
|
||||
# recursion guard is a name-mangled attribute on the root.
|
||||
try:
|
||||
del self.root._DndHandler__dnd
|
||||
except AttributeError:
|
||||
pass
|
||||
super().tearDown()
|
||||
|
||||
def test_drag_and_drop(self):
|
||||
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
|
||||
self.assertIsNotNone(handler)
|
||||
handler.on_motion(FakeEvent(self.canvas)) # Enter the target.
|
||||
handler.on_motion(FakeEvent(self.canvas)) # Move within the target.
|
||||
handler.on_release(FakeEvent(self.canvas)) # Drop on the target.
|
||||
self.assertEqual(self.log,
|
||||
['accept', 'enter', 'accept', 'motion', 'commit', 'end'])
|
||||
|
||||
def test_cancel(self):
|
||||
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
|
||||
handler.on_motion(FakeEvent(self.canvas)) # Enter the target.
|
||||
handler.cancel() # Leaves the target without committing.
|
||||
self.assertEqual(self.log, ['accept', 'enter', 'leave', 'end'])
|
||||
|
||||
def test_no_target(self):
|
||||
# Nothing under the pointer accepts the drag.
|
||||
self.canvas.winfo_containing = lambda x, y: None
|
||||
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
|
||||
handler.on_motion(FakeEvent(self.canvas))
|
||||
handler.on_release(FakeEvent(self.canvas))
|
||||
self.assertEqual(self.log, ['end'])
|
||||
|
||||
def test_no_recursive_start(self):
|
||||
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
|
||||
self.assertIsNotNone(handler)
|
||||
# A drag is already in progress, so a second start is ignored.
|
||||
self.assertIsNone(dnd.dnd_start(self.source, FakeEvent(self.canvas)))
|
||||
handler.cancel()
|
||||
|
||||
def test_high_button_number_ignored(self):
|
||||
self.assertIsNone(dnd.dnd_start(self.source, FakeEvent(self.canvas, num=6)))
|
||||
|
||||
def test_restart_after_finish(self):
|
||||
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
|
||||
handler.cancel()
|
||||
# Once a drag has finished a new one can start.
|
||||
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
|
||||
self.assertIsNotNone(handler)
|
||||
handler.cancel()
|
||||
|
||||
def test_drag_cursor(self):
|
||||
self.canvas['cursor'] = 'watch'
|
||||
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
|
||||
# The drag cursor is shown while dragging, the original restored after.
|
||||
self.assertEqual(handler.save_cursor, 'watch')
|
||||
self.assertEqual(str(self.canvas['cursor']), 'hand2')
|
||||
handler.cancel()
|
||||
self.assertEqual(str(self.canvas['cursor']), 'watch')
|
||||
|
||||
def test_bindings_added_and_removed(self):
|
||||
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
|
||||
self.assertIn('<Motion>', self.canvas.bind())
|
||||
self.assertIn('<B1-ButtonRelease-1>', self.canvas.bind())
|
||||
handler.cancel()
|
||||
self.assertNotIn('<Motion>', self.canvas.bind())
|
||||
self.assertNotIn('<B1-ButtonRelease-1>', self.canvas.bind())
|
||||
|
||||
def test_switch_target(self):
|
||||
log1, log2 = [], []
|
||||
w1, w2 = tkinter.Frame(self.root), tkinter.Frame(self.root)
|
||||
target1, target2 = Target(w1, log1), Target(w2, log2)
|
||||
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
|
||||
self.canvas.winfo_containing = lambda x, y: w1
|
||||
handler.on_motion(FakeEvent(self.canvas)) # Enter target1.
|
||||
self.canvas.winfo_containing = lambda x, y: w2
|
||||
handler.on_motion(FakeEvent(self.canvas)) # Leave target1, enter target2.
|
||||
self.assertIs(handler.target, target2)
|
||||
self.assertEqual(log1, ['accept', 'enter', 'leave'])
|
||||
self.assertEqual(log2, ['accept', 'enter'])
|
||||
handler.cancel()
|
||||
|
||||
def test_target_in_ancestor(self):
|
||||
# The widget under the pointer has no dnd_accept, but an ancestor
|
||||
# does: the search walks up the master chain to find it.
|
||||
parent = tkinter.Frame(self.root)
|
||||
target = Target(parent, self.log)
|
||||
child = tkinter.Frame(parent)
|
||||
self.canvas.winfo_containing = lambda x, y: child
|
||||
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
|
||||
handler.on_motion(FakeEvent(self.canvas))
|
||||
self.assertIs(handler.target, target)
|
||||
self.assertEqual(self.log, ['accept', 'enter'])
|
||||
handler.cancel()
|
||||
|
||||
def test_accept_returning_none_continues(self):
|
||||
# dnd_accept() returning None means "not me, keep looking up".
|
||||
parent = tkinter.Frame(self.root)
|
||||
target = Target(parent, self.log)
|
||||
child = tkinter.Frame(parent)
|
||||
child.dnd_accept = lambda source, event: None
|
||||
self.canvas.winfo_containing = lambda x, y: child
|
||||
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
|
||||
handler.on_motion(FakeEvent(self.canvas))
|
||||
self.assertIs(handler.target, target)
|
||||
handler.cancel()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
import os
|
||||
import unittest
|
||||
from tkinter import filedialog
|
||||
from tkinter.commondialog import Dialog
|
||||
from test.support import requires, swap_attr
|
||||
from test.test_tkinter.support import setUpModule # noqa: F401
|
||||
from test.test_tkinter.support import AbstractDialogTest, AbstractTkTest
|
||||
|
||||
requires('gui')
|
||||
|
||||
|
||||
class NativeDialogTest(AbstractTkTest, unittest.TestCase):
|
||||
# Open, SaveAs and Directory wrap modal native dialogs. The _test_callback
|
||||
# seam is called by show() just before the dialog would open; replacing it
|
||||
# with a function that raises exercises show() without blocking on the
|
||||
# dialog.
|
||||
|
||||
def check(self, dialog_class, command):
|
||||
self.assertEqual(dialog_class.command, command)
|
||||
master = None
|
||||
def test_callback(dialog, parent):
|
||||
nonlocal master
|
||||
master = parent
|
||||
raise ZeroDivisionError
|
||||
with swap_attr(Dialog, '_test_callback', test_callback):
|
||||
d = dialog_class(self.root, title='Test')
|
||||
self.assertRaises(ZeroDivisionError, d.show)
|
||||
self.assertIs(master, self.root)
|
||||
|
||||
def test_open(self):
|
||||
self.check(filedialog.Open, 'tk_getOpenFile')
|
||||
|
||||
def test_saveas(self):
|
||||
self.check(filedialog.SaveAs, 'tk_getSaveFile')
|
||||
|
||||
def test_directory(self):
|
||||
self.check(filedialog.Directory, 'tk_chooseDirectory')
|
||||
|
||||
|
||||
class FileDialogTest(AbstractDialogTest, unittest.TestCase):
|
||||
# The pure-Python FileDialog runs its own modal loop in go(); its logic is
|
||||
# exercised here without entering the loop.
|
||||
|
||||
def test_selection(self):
|
||||
d = filedialog.FileDialog(self.root)
|
||||
d.directory = os.path.abspath(os.sep)
|
||||
d.set_selection('spam.txt')
|
||||
self.assertEqual(os.path.basename(d.get_selection()), 'spam.txt')
|
||||
|
||||
def test_filter(self):
|
||||
d = filedialog.FileDialog(self.root)
|
||||
d.set_filter(os.getcwd(), '*.py')
|
||||
self.assertEqual(d.get_filter(), (os.getcwd(), '*.py'))
|
||||
|
||||
def test_ok_cancel(self):
|
||||
d = filedialog.FileDialog(self.root)
|
||||
d.directory = os.getcwd()
|
||||
d.set_selection('spam.txt')
|
||||
d.ok_command() # Accepts the current selection.
|
||||
self.assertEqual(os.path.basename(d.how), 'spam.txt')
|
||||
d.cancel_command() # Returns no selection.
|
||||
self.assertIsNone(d.how)
|
||||
|
||||
def test_subclasses(self):
|
||||
for cls in filedialog.LoadFileDialog, filedialog.SaveFileDialog:
|
||||
with self.subTest(cls=cls.__name__):
|
||||
d = cls(self.root)
|
||||
self.assertIsInstance(d, filedialog.FileDialog)
|
||||
self.assertEqual(d.top.title(), cls.title)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Vendored
+250
@@ -0,0 +1,250 @@
|
||||
import collections.abc
|
||||
import unittest
|
||||
import tkinter
|
||||
from tkinter import font
|
||||
from test.support import requires, gc_collect, ALWAYS_EQ
|
||||
from test.test_tkinter.support import setUpModule # noqa: F401
|
||||
from test.test_tkinter.support import AbstractTkTest, AbstractDefaultRootTest
|
||||
|
||||
requires('gui')
|
||||
|
||||
fontname = "TkDefaultFont"
|
||||
|
||||
class FontTest(AbstractTkTest, unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
AbstractTkTest.setUpClass.__func__(cls)
|
||||
try:
|
||||
cls.font = font.Font(root=cls.root, name=fontname, exists=True)
|
||||
except tkinter.TclError:
|
||||
cls.font = font.Font(root=cls.root, name=fontname, exists=False)
|
||||
|
||||
def test_configure(self):
|
||||
self.assertEqual(self.font.config, self.font.configure)
|
||||
options = self.font.configure()
|
||||
self.assertGreaterEqual(set(options),
|
||||
{'family', 'size', 'weight', 'slant', 'underline', 'overstrike'})
|
||||
for key in options:
|
||||
self.assertEqual(self.font.cget(key), options[key])
|
||||
self.assertEqual(self.font[key], options[key])
|
||||
for key in 'family', 'weight', 'slant':
|
||||
self.assertIsInstance(options[key], str)
|
||||
self.assertIsInstance(self.font.cget(key), str)
|
||||
self.assertIsInstance(self.font[key], str)
|
||||
sizetype = int if self.wantobjects else str
|
||||
for key in 'size', 'underline', 'overstrike':
|
||||
self.assertIsInstance(options[key], sizetype)
|
||||
self.assertIsInstance(self.font.cget(key), sizetype)
|
||||
self.assertIsInstance(self.font[key], sizetype)
|
||||
self.assertRaisesRegex(tkinter.TclError, 'bad option "-spam"',
|
||||
self.font.cget, 'spam')
|
||||
self.assertRaisesRegex(tkinter.TclError, 'bad option "-spam"',
|
||||
self.font.configure, spam='x')
|
||||
self.assertRaises(TypeError, self.font.cget)
|
||||
self.assertRaises(TypeError, self.font.cget, 'size', 'weight')
|
||||
|
||||
def test_create_from_named_font(self):
|
||||
# gh-143990: a font created from a named font copies its configured
|
||||
# options, preserving a size specified in pixels (a negative size).
|
||||
sizetype = int if self.wantobjects else str
|
||||
named = font.Font(root=self.root, name='my named font', # name with spaces
|
||||
family='Times', size=-20, weight='bold')
|
||||
# The source is the name of a named font or a Font representing one.
|
||||
for source in ['my named font', named]:
|
||||
with self.subTest(source=source):
|
||||
f = font.Font(root=self.root, font=source)
|
||||
self.assertEqual(f.cget('size'), sizetype(-20))
|
||||
self.assertEqual(f.actual('family'), named.actual('family'))
|
||||
self.assertEqual(f.actual('weight'), 'bold')
|
||||
|
||||
def test_create_from_description(self):
|
||||
# gh-143990: a font created from a font description is resolved via
|
||||
# "font actual", so a size in pixels (negative) becomes a size in points.
|
||||
descriptions = [
|
||||
('Times', -20), # tuple
|
||||
('Times', -20, 'bold'), # tuple with a style
|
||||
'Times -20', # string
|
||||
'Times -20 bold', # string with a style
|
||||
'{Times New Roman} -20', # string, family with spaces
|
||||
]
|
||||
for desc in descriptions:
|
||||
with self.subTest(font=desc):
|
||||
f = font.Font(root=self.root, font=desc)
|
||||
self.assertGreater(int(f.cget('size')), 0) # pixels -> points
|
||||
|
||||
def test_copy(self):
|
||||
# size=-20 (pixels): copy() copies the configured options, so the
|
||||
# size is preserved rather than resolved (gh-143990).
|
||||
f = font.Font(root=self.root, family='Times', size=-20, weight='bold')
|
||||
copied = f.copy()
|
||||
self.assertIsInstance(copied, font.Font)
|
||||
self.assertIsNot(copied, f)
|
||||
self.assertNotEqual(copied.name, f.name)
|
||||
self.assertEqual(copied.actual(), f.actual())
|
||||
sizetype = int if self.wantobjects else str
|
||||
self.assertEqual(copied.cget('size'), sizetype(-20))
|
||||
# The copy is independent of the original.
|
||||
copied.configure(size=20)
|
||||
self.assertEqual(f.cget('size'), sizetype(-20))
|
||||
self.assertEqual(copied.cget('size'), sizetype(20))
|
||||
self.assertRaises(TypeError, f.copy, 'x')
|
||||
|
||||
def test_unicode_family(self):
|
||||
family = 'MS \u30b4\u30b7\u30c3\u30af'
|
||||
try:
|
||||
f = font.Font(root=self.root, family=family, exists=True)
|
||||
except tkinter.TclError:
|
||||
f = font.Font(root=self.root, family=family, exists=False)
|
||||
self.assertEqual(f.cget('family'), family)
|
||||
del f
|
||||
gc_collect()
|
||||
|
||||
def test_actual(self):
|
||||
options = self.font.actual()
|
||||
self.assertGreaterEqual(set(options),
|
||||
{'family', 'size', 'weight', 'slant', 'underline', 'overstrike'})
|
||||
for key in options:
|
||||
self.assertEqual(self.font.actual(key), options[key])
|
||||
for key in 'family', 'weight', 'slant':
|
||||
self.assertIsInstance(options[key], str)
|
||||
self.assertIsInstance(self.font.actual(key), str)
|
||||
sizetype = int if self.wantobjects else str
|
||||
for key in 'size', 'underline', 'overstrike':
|
||||
self.assertIsInstance(options[key], sizetype)
|
||||
self.assertIsInstance(self.font.actual(key), sizetype)
|
||||
self.assertRaisesRegex(tkinter.TclError, 'bad option "-spam"',
|
||||
self.font.actual, 'spam')
|
||||
self.assertRaises(TypeError, self.font.actual, 'size', 'weight', 'slant')
|
||||
|
||||
def test_name(self):
|
||||
self.assertEqual(self.font.name, fontname)
|
||||
self.assertEqual(str(self.font), fontname)
|
||||
|
||||
def test_equality(self):
|
||||
font1 = font.Font(root=self.root, name=fontname, exists=True)
|
||||
font2 = font.Font(root=self.root, name=fontname, exists=True)
|
||||
self.assertIsNot(font1, font2)
|
||||
self.assertEqual(font1, font2)
|
||||
self.assertNotEqual(font1, font1.copy())
|
||||
|
||||
self.assertNotEqual(font1, 0)
|
||||
self.assertEqual(font1, ALWAYS_EQ)
|
||||
|
||||
root2 = tkinter.Tk()
|
||||
self.addCleanup(root2.destroy)
|
||||
font3 = font.Font(root=root2, name=fontname, exists=True)
|
||||
self.assertEqual(str(font1), str(font3))
|
||||
self.assertNotEqual(font1, font3)
|
||||
|
||||
def test_measure(self):
|
||||
self.assertIsInstance(self.font.measure('abc'), int)
|
||||
self.assertEqual(self.font.measure(''), 0)
|
||||
self.assertIsInstance(
|
||||
self.font.measure('abc', displayof=self.root), int)
|
||||
self.assertRaises(TypeError, self.font.measure)
|
||||
self.assertRaises(TypeError, self.font.measure, 'a', 'b', 'c')
|
||||
|
||||
def test_metrics(self):
|
||||
metrics = self.font.metrics()
|
||||
self.assertGreaterEqual(set(metrics),
|
||||
{'ascent', 'descent', 'linespace', 'fixed'})
|
||||
for key in metrics:
|
||||
self.assertEqual(self.font.metrics(key), metrics[key])
|
||||
self.assertEqual(self.font.metrics(key, displayof=self.root),
|
||||
metrics[key])
|
||||
self.assertIsInstance(metrics[key], int)
|
||||
self.assertIsInstance(self.font.metrics(key), int)
|
||||
self.assertRaisesRegex(tkinter.TclError, 'bad metric "-spam"',
|
||||
self.font.metrics, 'spam')
|
||||
|
||||
def test_families(self):
|
||||
families = font.families(self.root)
|
||||
self.assertIsInstance(families, tuple)
|
||||
self.assertTrue(families)
|
||||
for family in families:
|
||||
self.assertIsInstance(family, str)
|
||||
self.assertTrue(family)
|
||||
|
||||
def test_names(self):
|
||||
names = font.names(self.root)
|
||||
self.assertIsInstance(names, tuple)
|
||||
self.assertTrue(names)
|
||||
for name in names:
|
||||
self.assertIsInstance(name, str)
|
||||
self.assertTrue(name)
|
||||
self.assertIn(fontname, names)
|
||||
|
||||
def test_nametofont(self):
|
||||
testfont = font.nametofont(fontname, root=self.root)
|
||||
self.assertIsInstance(testfont, font.Font)
|
||||
self.assertEqual(testfont.name, fontname)
|
||||
|
||||
def test_repr(self):
|
||||
self.assertEqual(
|
||||
repr(self.font), f'<tkinter.font.Font object {fontname!r}>'
|
||||
)
|
||||
|
||||
def test_iterable_protocol(self):
|
||||
self.assertNotIsSubclass(font.Font, collections.abc.Iterable)
|
||||
self.assertNotIsSubclass(font.Font, collections.abc.Container)
|
||||
self.assertNotIsInstance(self.font, collections.abc.Iterable)
|
||||
self.assertNotIsInstance(self.font, collections.abc.Container)
|
||||
with self.assertRaisesRegex(TypeError, 'is not iterable'):
|
||||
iter(self.font)
|
||||
with self.assertRaisesRegex(TypeError, 'is not a container or iterable'):
|
||||
self.font in self.font
|
||||
|
||||
|
||||
class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
|
||||
|
||||
def test_families(self):
|
||||
self.assertRaises(RuntimeError, font.families)
|
||||
root = tkinter.Tk()
|
||||
families = font.families()
|
||||
self.assertIsInstance(families, tuple)
|
||||
self.assertTrue(families)
|
||||
for family in families:
|
||||
self.assertIsInstance(family, str)
|
||||
self.assertTrue(family)
|
||||
root.destroy()
|
||||
tkinter.NoDefaultRoot()
|
||||
self.assertRaises(RuntimeError, font.families)
|
||||
|
||||
def test_names(self):
|
||||
self.assertRaises(RuntimeError, font.names)
|
||||
root = tkinter.Tk()
|
||||
names = font.names()
|
||||
self.assertIsInstance(names, tuple)
|
||||
self.assertTrue(names)
|
||||
for name in names:
|
||||
self.assertIsInstance(name, str)
|
||||
self.assertTrue(name)
|
||||
self.assertIn(fontname, names)
|
||||
root.destroy()
|
||||
tkinter.NoDefaultRoot()
|
||||
self.assertRaises(RuntimeError, font.names)
|
||||
|
||||
def test_nametofont(self):
|
||||
self.assertRaises(RuntimeError, font.nametofont, fontname)
|
||||
root = tkinter.Tk()
|
||||
testfont = font.nametofont(fontname)
|
||||
self.assertIsInstance(testfont, font.Font)
|
||||
self.assertEqual(testfont.name, fontname)
|
||||
root.destroy()
|
||||
tkinter.NoDefaultRoot()
|
||||
self.assertRaises(RuntimeError, font.nametofont, fontname)
|
||||
|
||||
|
||||
class TestModule(unittest.TestCase):
|
||||
def test_deprecated__version__(self):
|
||||
with self.assertWarnsRegex(
|
||||
DeprecationWarning,
|
||||
"'__version__' is deprecated and slated for removal in Python 3.20",
|
||||
) as cm:
|
||||
getattr(font, "__version__")
|
||||
self.assertEqual(cm.filename, __file__)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+1001
File diff suppressed because it is too large.
Load diff
Vendored
+675
@@ -0,0 +1,675 @@
|
||||
import collections.abc
|
||||
import unittest
|
||||
import tkinter
|
||||
from test import support
|
||||
from test.support import os_helper
|
||||
from test.test_tkinter.support import setUpModule # noqa: F401
|
||||
from test.test_tkinter.support import AbstractTkTest, AbstractDefaultRootTest, requires_tk
|
||||
|
||||
support.requires('gui')
|
||||
|
||||
|
||||
class MiscTest(AbstractTkTest, unittest.TestCase):
|
||||
|
||||
def test_image_types(self):
|
||||
image_types = self.root.image_types()
|
||||
self.assertIsInstance(image_types, tuple)
|
||||
self.assertIn('photo', image_types)
|
||||
self.assertIn('bitmap', image_types)
|
||||
|
||||
def test_image_names(self):
|
||||
image_names = self.root.image_names()
|
||||
self.assertIsInstance(image_names, tuple)
|
||||
|
||||
|
||||
class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
|
||||
|
||||
def test_image_types(self):
|
||||
self.assertRaises(RuntimeError, tkinter.image_types)
|
||||
root = tkinter.Tk()
|
||||
image_types = tkinter.image_types()
|
||||
self.assertIsInstance(image_types, tuple)
|
||||
self.assertIn('photo', image_types)
|
||||
self.assertIn('bitmap', image_types)
|
||||
root.destroy()
|
||||
tkinter.NoDefaultRoot()
|
||||
self.assertRaises(RuntimeError, tkinter.image_types)
|
||||
|
||||
def test_image_names(self):
|
||||
self.assertRaises(RuntimeError, tkinter.image_names)
|
||||
root = tkinter.Tk()
|
||||
image_names = tkinter.image_names()
|
||||
self.assertIsInstance(image_names, tuple)
|
||||
root.destroy()
|
||||
tkinter.NoDefaultRoot()
|
||||
self.assertRaises(RuntimeError, tkinter.image_names)
|
||||
|
||||
def test_image_create_bitmap(self):
|
||||
self.assertRaises(RuntimeError, tkinter.BitmapImage)
|
||||
root = tkinter.Tk()
|
||||
image = tkinter.BitmapImage()
|
||||
self.assertIn(image.name, tkinter.image_names())
|
||||
root.destroy()
|
||||
tkinter.NoDefaultRoot()
|
||||
self.assertRaises(RuntimeError, tkinter.BitmapImage)
|
||||
|
||||
def test_image_create_photo(self):
|
||||
self.assertRaises(RuntimeError, tkinter.PhotoImage)
|
||||
root = tkinter.Tk()
|
||||
image = tkinter.PhotoImage()
|
||||
self.assertIn(image.name, tkinter.image_names())
|
||||
root.destroy()
|
||||
tkinter.NoDefaultRoot()
|
||||
self.assertRaises(RuntimeError, tkinter.PhotoImage)
|
||||
|
||||
|
||||
class BaseImageTest:
|
||||
def create(self):
|
||||
return self.image_class('::img::test', master=self.root,
|
||||
file=self.testfile)
|
||||
|
||||
def test_bug_100814(self):
|
||||
# gh-100814: Passing a callable option value causes AttributeError.
|
||||
with self.assertRaises(tkinter.TclError):
|
||||
self.image_class('::img::test', master=self.root, spam=print)
|
||||
image = self.image_class('::img::test', master=self.root)
|
||||
with self.assertRaises(tkinter.TclError):
|
||||
image.configure(spam=print)
|
||||
|
||||
def test_iterable_protocol(self):
|
||||
image = self.create()
|
||||
self.assertNotIsSubclass(self.image_class, collections.abc.Iterable)
|
||||
self.assertNotIsSubclass(self.image_class, collections.abc.Container)
|
||||
self.assertNotIsInstance(image, collections.abc.Iterable)
|
||||
self.assertNotIsInstance(image, collections.abc.Container)
|
||||
with self.assertRaisesRegex(TypeError, 'is not iterable'):
|
||||
iter(image)
|
||||
with self.assertRaisesRegex(TypeError, 'is not a container or iterable'):
|
||||
image in image
|
||||
|
||||
|
||||
class BitmapImageTest(BaseImageTest, AbstractTkTest, unittest.TestCase):
|
||||
image_class = tkinter.BitmapImage
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
AbstractTkTest.setUpClass.__func__(cls)
|
||||
cls.testfile = support.findfile('python.xbm', subdir='tkinterdata')
|
||||
|
||||
def test_create_from_file(self):
|
||||
image = tkinter.BitmapImage('::img::test', master=self.root,
|
||||
foreground='yellow', background='blue',
|
||||
file=self.testfile)
|
||||
self.assertEqual(str(image), '::img::test')
|
||||
self.assertEqual(image.type(), 'bitmap')
|
||||
self.assertEqual(image.width(), 16)
|
||||
self.assertEqual(image.height(), 16)
|
||||
self.assertIn('::img::test', self.root.image_names())
|
||||
del image
|
||||
support.gc_collect() # For PyPy or other GCs.
|
||||
self.assertNotIn('::img::test', self.root.image_names())
|
||||
|
||||
def test_create_from_data(self):
|
||||
with open(self.testfile, 'rb') as f:
|
||||
data = f.read()
|
||||
image = tkinter.BitmapImage('::img::test', master=self.root,
|
||||
foreground='yellow', background='blue',
|
||||
data=data)
|
||||
self.assertEqual(str(image), '::img::test')
|
||||
self.assertEqual(image.type(), 'bitmap')
|
||||
self.assertEqual(image.width(), 16)
|
||||
self.assertEqual(image.height(), 16)
|
||||
self.assertIn('::img::test', self.root.image_names())
|
||||
del image
|
||||
support.gc_collect() # For PyPy or other GCs.
|
||||
self.assertNotIn('::img::test', self.root.image_names())
|
||||
|
||||
def assertEqualStrList(self, actual, expected):
|
||||
self.assertIsInstance(actual, str)
|
||||
self.assertEqual(self.root.splitlist(actual), expected)
|
||||
|
||||
def test_configure_data(self):
|
||||
image = tkinter.BitmapImage('::img::test', master=self.root)
|
||||
self.assertEqual(image['data'], '-data {} {} {} {}')
|
||||
with open(self.testfile, 'rb') as f:
|
||||
data = f.read()
|
||||
image.configure(data=data)
|
||||
self.assertEqualStrList(image['data'],
|
||||
('-data', '', '', '', data.decode('ascii')))
|
||||
self.assertEqual(image.width(), 16)
|
||||
self.assertEqual(image.height(), 16)
|
||||
|
||||
self.assertEqual(image['maskdata'], '-maskdata {} {} {} {}')
|
||||
image.configure(maskdata=data)
|
||||
self.assertEqualStrList(image['maskdata'],
|
||||
('-maskdata', '', '', '', data.decode('ascii')))
|
||||
|
||||
def test_configure_file(self):
|
||||
image = tkinter.BitmapImage('::img::test', master=self.root)
|
||||
self.assertEqual(image['file'], '-file {} {} {} {}')
|
||||
image.configure(file=self.testfile)
|
||||
self.assertEqualStrList(image['file'],
|
||||
('-file', '', '', '',self.testfile))
|
||||
self.assertEqual(image.width(), 16)
|
||||
self.assertEqual(image.height(), 16)
|
||||
|
||||
self.assertEqual(image['maskfile'], '-maskfile {} {} {} {}')
|
||||
image.configure(maskfile=self.testfile)
|
||||
self.assertEqualStrList(image['maskfile'],
|
||||
('-maskfile', '', '', '', self.testfile))
|
||||
|
||||
def test_configure_background(self):
|
||||
image = tkinter.BitmapImage('::img::test', master=self.root)
|
||||
self.assertEqual(image['background'], '-background {} {} {} {}')
|
||||
image.configure(background='blue')
|
||||
self.assertEqual(image['background'], '-background {} {} {} blue')
|
||||
|
||||
def test_configure_foreground(self):
|
||||
image = tkinter.BitmapImage('::img::test', master=self.root)
|
||||
self.assertEqual(image['foreground'],
|
||||
'-foreground {} {} #000000 #000000')
|
||||
image.configure(foreground='yellow')
|
||||
self.assertEqual(image['foreground'],
|
||||
'-foreground {} {} #000000 yellow')
|
||||
|
||||
|
||||
class PhotoImageTest(BaseImageTest, AbstractTkTest, unittest.TestCase):
|
||||
image_class = tkinter.PhotoImage
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
AbstractTkTest.setUpClass.__func__(cls)
|
||||
cls.testfile = support.findfile('python.gif', subdir='tkinterdata')
|
||||
|
||||
def colorlist(self, *args):
|
||||
if tkinter.TkVersion >= 8.6 and self.wantobjects:
|
||||
return args
|
||||
else:
|
||||
return tkinter._join(args)
|
||||
|
||||
def check_create_from_file(self, ext):
|
||||
testfile = support.findfile('python.' + ext, subdir='tkinterdata')
|
||||
image = tkinter.PhotoImage('::img::test', master=self.root,
|
||||
file=testfile)
|
||||
self.assertEqual(str(image), '::img::test')
|
||||
self.assertEqual(image.type(), 'photo')
|
||||
self.assertEqual(image.width(), 16)
|
||||
self.assertEqual(image.height(), 16)
|
||||
self.assertEqual(image['data'], '')
|
||||
self.assertEqual(image['file'], testfile)
|
||||
self.assertIn('::img::test', self.root.image_names())
|
||||
del image
|
||||
support.gc_collect() # For PyPy or other GCs.
|
||||
self.assertNotIn('::img::test', self.root.image_names())
|
||||
|
||||
def check_create_from_data(self, ext):
|
||||
testfile = support.findfile('python.' + ext, subdir='tkinterdata')
|
||||
with open(testfile, 'rb') as f:
|
||||
data = f.read()
|
||||
image = tkinter.PhotoImage('::img::test', master=self.root,
|
||||
data=data)
|
||||
self.assertEqual(str(image), '::img::test')
|
||||
self.assertEqual(image.type(), 'photo')
|
||||
self.assertEqual(image.width(), 16)
|
||||
self.assertEqual(image.height(), 16)
|
||||
self.assertEqual(image['data'], data if self.wantobjects
|
||||
else data.decode('latin1'))
|
||||
self.assertEqual(image['file'], '')
|
||||
self.assertIn('::img::test', self.root.image_names())
|
||||
del image
|
||||
support.gc_collect() # For PyPy or other GCs.
|
||||
self.assertNotIn('::img::test', self.root.image_names())
|
||||
|
||||
def test_create_from_ppm_file(self):
|
||||
self.check_create_from_file('ppm')
|
||||
|
||||
def test_create_from_ppm_data(self):
|
||||
self.check_create_from_data('ppm')
|
||||
|
||||
def test_create_from_pgm_file(self):
|
||||
self.check_create_from_file('pgm')
|
||||
|
||||
def test_create_from_pgm_data(self):
|
||||
self.check_create_from_data('pgm')
|
||||
|
||||
def test_create_from_gif_file(self):
|
||||
self.check_create_from_file('gif')
|
||||
|
||||
def test_create_from_gif_data(self):
|
||||
self.check_create_from_data('gif')
|
||||
|
||||
@requires_tk(8, 6)
|
||||
def test_create_from_png_file(self):
|
||||
self.check_create_from_file('png')
|
||||
|
||||
@requires_tk(8, 6)
|
||||
def test_create_from_png_data(self):
|
||||
self.check_create_from_data('png')
|
||||
|
||||
def test_configure_data(self):
|
||||
image = tkinter.PhotoImage('::img::test', master=self.root)
|
||||
self.assertEqual(image['data'], '')
|
||||
with open(self.testfile, 'rb') as f:
|
||||
data = f.read()
|
||||
image.configure(data=data)
|
||||
self.assertEqual(image['data'], data if self.wantobjects
|
||||
else data.decode('latin1'))
|
||||
self.assertEqual(image.width(), 16)
|
||||
self.assertEqual(image.height(), 16)
|
||||
|
||||
def test_configure_format(self):
|
||||
image = tkinter.PhotoImage('::img::test', master=self.root)
|
||||
self.assertEqual(image['format'], '')
|
||||
image.configure(file=self.testfile, format='gif')
|
||||
self.assertEqual(image['format'], ('gif',) if self.wantobjects
|
||||
else 'gif')
|
||||
self.assertEqual(image.width(), 16)
|
||||
self.assertEqual(image.height(), 16)
|
||||
|
||||
def test_configure_file(self):
|
||||
image = tkinter.PhotoImage('::img::test', master=self.root)
|
||||
self.assertEqual(image['file'], '')
|
||||
image.configure(file=self.testfile)
|
||||
self.assertEqual(image['file'], self.testfile)
|
||||
self.assertEqual(image.width(), 16)
|
||||
self.assertEqual(image.height(), 16)
|
||||
|
||||
def test_configure_gamma(self):
|
||||
image = tkinter.PhotoImage('::img::test', master=self.root)
|
||||
self.assertEqual(image['gamma'], '1.0')
|
||||
image.configure(gamma=2.0)
|
||||
self.assertEqual(image['gamma'], '2.0')
|
||||
|
||||
def test_configure_width_height(self):
|
||||
image = tkinter.PhotoImage('::img::test', master=self.root)
|
||||
self.assertEqual(image['width'], '0')
|
||||
self.assertEqual(image['height'], '0')
|
||||
image.configure(width=20)
|
||||
image.configure(height=10)
|
||||
self.assertEqual(image['width'], '20')
|
||||
self.assertEqual(image['height'], '10')
|
||||
self.assertEqual(image.cget('width'), image['width'])
|
||||
self.assertEqual(image.cget('height'), image['height'])
|
||||
self.assertRaises(TypeError, image.cget)
|
||||
self.assertRaises(TypeError, image.cget, 'width', 'height')
|
||||
self.assertEqual(image.config, image.configure)
|
||||
self.assertEqual(image.width(), 20)
|
||||
self.assertEqual(image.height(), 10)
|
||||
|
||||
def test_configure_palette(self):
|
||||
image = tkinter.PhotoImage('::img::test', master=self.root)
|
||||
self.assertEqual(image['palette'], '')
|
||||
image.configure(palette=256)
|
||||
self.assertEqual(image['palette'], '256')
|
||||
image.configure(palette='3/4/2')
|
||||
self.assertEqual(image['palette'], '3/4/2')
|
||||
|
||||
def test_blank(self):
|
||||
image = self.create()
|
||||
image.blank()
|
||||
self.assertEqual(image.width(), 16)
|
||||
self.assertEqual(image.height(), 16)
|
||||
self.assertEqual(image.get(4, 6), self.colorlist(0, 0, 0))
|
||||
|
||||
def test_copy(self):
|
||||
image = self.create()
|
||||
image2 = image.copy()
|
||||
self.assertEqual(image2.width(), 16)
|
||||
self.assertEqual(image2.height(), 16)
|
||||
self.assertEqual(image2.get(4, 6), image.get(4, 6))
|
||||
|
||||
image2 = image.copy(from_coords=(2, 3, 14, 11))
|
||||
self.assertEqual(image2.width(), 12)
|
||||
self.assertEqual(image2.height(), 8)
|
||||
self.assertEqual(image2.get(0, 0), image.get(2, 3))
|
||||
self.assertEqual(image2.get(11, 7), image.get(13, 10))
|
||||
self.assertEqual(image2.get(2, 4), image.get(2+2, 4+3))
|
||||
|
||||
image2 = image.copy(from_coords=(2, 3, 14, 11), zoom=2)
|
||||
self.assertEqual(image2.width(), 24)
|
||||
self.assertEqual(image2.height(), 16)
|
||||
self.assertEqual(image2.get(0, 0), image.get(2, 3))
|
||||
self.assertEqual(image2.get(23, 15), image.get(13, 10))
|
||||
self.assertEqual(image2.get(2*2, 4*2), image.get(2+2, 4+3))
|
||||
self.assertEqual(image2.get(2*2+1, 4*2+1), image.get(6+2, 2+3))
|
||||
|
||||
image2 = image.copy(from_coords=(2, 3, 14, 11), subsample=2)
|
||||
self.assertEqual(image2.width(), 6)
|
||||
self.assertEqual(image2.height(), 4)
|
||||
self.assertEqual(image2.get(0, 0), image.get(2, 3))
|
||||
self.assertEqual(image2.get(5, 3), image.get(12, 9))
|
||||
self.assertEqual(image2.get(3, 2), image.get(3*2+2, 2*2+3))
|
||||
|
||||
image2 = image.copy(from_coords=(2, 3, 14, 11), subsample=2, zoom=3)
|
||||
self.assertEqual(image2.width(), 18)
|
||||
self.assertEqual(image2.height(), 12)
|
||||
self.assertEqual(image2.get(0, 0), image.get(2, 3))
|
||||
self.assertEqual(image2.get(17, 11), image.get(12, 9))
|
||||
self.assertEqual(image2.get(1*3, 2*3), image.get(1*2+2, 2*2+3))
|
||||
self.assertEqual(image2.get(1*3+2, 2*3+2), image.get(1*2+2, 2*2+3))
|
||||
|
||||
def test_subsample(self):
|
||||
image = self.create()
|
||||
image2 = image.subsample(2, 3)
|
||||
self.assertEqual(image2.width(), 8)
|
||||
self.assertEqual(image2.height(), 6)
|
||||
self.assertEqual(image2.get(2, 2), image.get(4, 6))
|
||||
|
||||
image2 = image.subsample(2)
|
||||
self.assertEqual(image2.width(), 8)
|
||||
self.assertEqual(image2.height(), 8)
|
||||
self.assertEqual(image2.get(2, 3), image.get(4, 6))
|
||||
|
||||
image2 = image.subsample(2, from_coords=(2, 3, 14, 11))
|
||||
self.assertEqual(image2.width(), 6)
|
||||
self.assertEqual(image2.height(), 4)
|
||||
self.assertEqual(image2.get(0, 0), image.get(2, 3))
|
||||
self.assertEqual(image2.get(5, 3), image.get(12, 9))
|
||||
self.assertEqual(image2.get(1, 2), image.get(1*2+2, 2*2+3))
|
||||
|
||||
def test_zoom(self):
|
||||
image = self.create()
|
||||
image2 = image.zoom(2, 3)
|
||||
self.assertEqual(image2.width(), 32)
|
||||
self.assertEqual(image2.height(), 48)
|
||||
self.assertEqual(image2.get(8, 18), image.get(4, 6))
|
||||
self.assertEqual(image2.get(9, 20), image.get(4, 6))
|
||||
|
||||
image2 = image.zoom(2)
|
||||
self.assertEqual(image2.width(), 32)
|
||||
self.assertEqual(image2.height(), 32)
|
||||
self.assertEqual(image2.get(8, 12), image.get(4, 6))
|
||||
self.assertEqual(image2.get(9, 13), image.get(4, 6))
|
||||
|
||||
image2 = image.zoom(2, from_coords=(2, 3, 14, 11))
|
||||
self.assertEqual(image2.width(), 24)
|
||||
self.assertEqual(image2.height(), 16)
|
||||
self.assertEqual(image2.get(0, 0), image.get(2, 3))
|
||||
self.assertEqual(image2.get(23, 15), image.get(13, 10))
|
||||
self.assertEqual(image2.get(2*2, 4*2), image.get(2+2, 4+3))
|
||||
self.assertEqual(image2.get(2*2+1, 4*2+1), image.get(6+2, 2+3))
|
||||
|
||||
def test_copy_replace(self):
|
||||
image = self.create()
|
||||
image2 = tkinter.PhotoImage(master=self.root)
|
||||
image2.copy_replace(image)
|
||||
self.assertEqual(image2.width(), 16)
|
||||
self.assertEqual(image2.height(), 16)
|
||||
self.assertEqual(image2.get(4, 6), image.get(4, 6))
|
||||
|
||||
image2 = tkinter.PhotoImage(master=self.root)
|
||||
image2.copy_replace(image, from_coords=(2, 3, 14, 11))
|
||||
self.assertEqual(image2.width(), 12)
|
||||
self.assertEqual(image2.height(), 8)
|
||||
self.assertEqual(image2.get(0, 0), image.get(2, 3))
|
||||
self.assertEqual(image2.get(11, 7), image.get(13, 10))
|
||||
self.assertEqual(image2.get(2, 4), image.get(2+2, 4+3))
|
||||
|
||||
image2 = tkinter.PhotoImage(master=self.root)
|
||||
image2.copy_replace(image)
|
||||
image2.copy_replace(image, from_coords=(2, 3, 14, 11), shrink=True)
|
||||
self.assertEqual(image2.width(), 12)
|
||||
self.assertEqual(image2.height(), 8)
|
||||
self.assertEqual(image2.get(0, 0), image.get(2, 3))
|
||||
self.assertEqual(image2.get(11, 7), image.get(13, 10))
|
||||
self.assertEqual(image2.get(2, 4), image.get(2+2, 4+3))
|
||||
|
||||
image2 = tkinter.PhotoImage(master=self.root)
|
||||
image2.copy_replace(image, from_coords=(2, 3, 14, 11), to=(3, 6))
|
||||
self.assertEqual(image2.width(), 15)
|
||||
self.assertEqual(image2.height(), 14)
|
||||
self.assertEqual(image2.get(0+3, 0+6), image.get(2, 3))
|
||||
self.assertEqual(image2.get(11+3, 7+6), image.get(13, 10))
|
||||
self.assertEqual(image2.get(2+3, 4+6), image.get(2+2, 4+3))
|
||||
|
||||
image2 = tkinter.PhotoImage(master=self.root)
|
||||
image2.copy_replace(image, from_coords=(2, 3, 14, 11), to=(0, 0, 100, 50))
|
||||
self.assertEqual(image2.width(), 100)
|
||||
self.assertEqual(image2.height(), 50)
|
||||
self.assertEqual(image2.get(0, 0), image.get(2, 3))
|
||||
self.assertEqual(image2.get(11, 7), image.get(13, 10))
|
||||
self.assertEqual(image2.get(2, 4), image.get(2+2, 4+3))
|
||||
self.assertEqual(image2.get(2+12, 4+8), image.get(2+2, 4+3))
|
||||
self.assertEqual(image2.get(2+12*2, 4), image.get(2+2, 4+3))
|
||||
self.assertEqual(image2.get(2, 4+8*3), image.get(2+2, 4+3))
|
||||
|
||||
image2 = tkinter.PhotoImage(master=self.root)
|
||||
image2.copy_replace(image, from_coords=(2, 3, 14, 11), zoom=2)
|
||||
self.assertEqual(image2.width(), 24)
|
||||
self.assertEqual(image2.height(), 16)
|
||||
self.assertEqual(image2.get(0, 0), image.get(2, 3))
|
||||
self.assertEqual(image2.get(23, 15), image.get(13, 10))
|
||||
self.assertEqual(image2.get(2*2, 4*2), image.get(2+2, 4+3))
|
||||
self.assertEqual(image2.get(2*2+1, 4*2+1), image.get(6+2, 2+3))
|
||||
|
||||
image2 = tkinter.PhotoImage(master=self.root)
|
||||
image2.copy_replace(image, from_coords=(2, 3, 14, 11), subsample=2)
|
||||
self.assertEqual(image2.width(), 6)
|
||||
self.assertEqual(image2.height(), 4)
|
||||
self.assertEqual(image2.get(0, 0), image.get(2, 3))
|
||||
self.assertEqual(image2.get(5, 3), image.get(12, 9))
|
||||
self.assertEqual(image2.get(1, 2), image.get(1*2+2, 2*2+3))
|
||||
|
||||
image2 = tkinter.PhotoImage(master=self.root)
|
||||
image2.copy_replace(image, from_coords=(2, 3, 14, 11), subsample=2, zoom=3)
|
||||
self.assertEqual(image2.width(), 18)
|
||||
self.assertEqual(image2.height(), 12)
|
||||
self.assertEqual(image2.get(0, 0), image.get(2, 3))
|
||||
self.assertEqual(image2.get(17, 11), image.get(12, 9))
|
||||
self.assertEqual(image2.get(3*3, 2*3), image.get(3*2+2, 2*2+3))
|
||||
self.assertEqual(image2.get(3*3+2, 2*3+2), image.get(3*2+2, 2*2+3))
|
||||
self.assertEqual(image2.get(1*3, 2*3), image.get(1*2+2, 2*2+3))
|
||||
self.assertEqual(image2.get(1*3+2, 2*3+2), image.get(1*2+2, 2*2+3))
|
||||
|
||||
def checkImgTrans(self, image, expected):
|
||||
actual = {(x, y)
|
||||
for x in range(image.width())
|
||||
for y in range(image.height())
|
||||
if image.transparency_get(x, y)}
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_copy_replace_compositingrule(self):
|
||||
image1 = tkinter.PhotoImage(master=self.root, width=2, height=2)
|
||||
image1.blank()
|
||||
image1.put('black', to=(0, 0, 2, 2))
|
||||
image1.transparency_set(0, 0, True)
|
||||
|
||||
# default compositingrule
|
||||
image2 = tkinter.PhotoImage(master=self.root, width=3, height=3)
|
||||
image2.blank()
|
||||
image2.put('white', to=(0, 0, 2, 2))
|
||||
image2.copy_replace(image1, to=(1, 1))
|
||||
self.checkImgTrans(image2, {(0, 2), (2, 0)})
|
||||
|
||||
image3 = tkinter.PhotoImage(master=self.root, width=3, height=3)
|
||||
image3.blank()
|
||||
image3.put('white', to=(0, 0, 2, 2))
|
||||
image3.copy_replace(image1, to=(1, 1), compositingrule='overlay')
|
||||
self.checkImgTrans(image3, {(0, 2), (2, 0)})
|
||||
|
||||
image4 = tkinter.PhotoImage(master=self.root, width=3, height=3)
|
||||
image4.blank()
|
||||
image4.put('white', to=(0, 0, 2, 2))
|
||||
image4.copy_replace(image1, to=(1, 1), compositingrule='set')
|
||||
self.checkImgTrans(image4, {(0, 2), (1, 1), (2, 0)})
|
||||
|
||||
def test_put(self):
|
||||
image = self.create()
|
||||
image.put('{red green} {blue yellow}', to=(4, 6))
|
||||
self.assertEqual(image.get(4, 6), self.colorlist(255, 0, 0))
|
||||
self.assertEqual(image.get(5, 6),
|
||||
self.colorlist(0, 128 if tkinter.TkVersion >= 8.6
|
||||
else 255, 0))
|
||||
self.assertEqual(image.get(4, 7), self.colorlist(0, 0, 255))
|
||||
self.assertEqual(image.get(5, 7), self.colorlist(255, 255, 0))
|
||||
|
||||
image.put((('#f00', '#00ff00'), ('#000000fff', '#ffffffff0000')))
|
||||
self.assertEqual(image.get(0, 0), self.colorlist(255, 0, 0))
|
||||
self.assertEqual(image.get(1, 0), self.colorlist(0, 255, 0))
|
||||
self.assertEqual(image.get(0, 1), self.colorlist(0, 0, 255))
|
||||
self.assertEqual(image.get(1, 1), self.colorlist(255, 255, 0))
|
||||
|
||||
def test_get(self):
|
||||
image = self.create()
|
||||
self.assertEqual(image.get(4, 6), self.colorlist(62, 116, 162))
|
||||
self.assertEqual(image.get(0, 0), self.colorlist(0, 0, 0))
|
||||
self.assertEqual(image.get(15, 15), self.colorlist(0, 0, 0))
|
||||
self.assertRaises(tkinter.TclError, image.get, -1, 0)
|
||||
self.assertRaises(tkinter.TclError, image.get, 0, -1)
|
||||
self.assertRaises(tkinter.TclError, image.get, 16, 15)
|
||||
self.assertRaises(tkinter.TclError, image.get, 15, 16)
|
||||
|
||||
def test_read(self):
|
||||
# Due to the Tk bug https://core.tcl-lang.org/tk/tktview/1576528
|
||||
# the -from option does not work correctly for GIF and PNG files.
|
||||
# Use the PPM file for this test.
|
||||
testfile = support.findfile('python.ppm', subdir='tkinterdata')
|
||||
image = tkinter.PhotoImage(master=self.root, file=testfile)
|
||||
|
||||
image2 = tkinter.PhotoImage(master=self.root)
|
||||
image2.read(testfile)
|
||||
self.assertEqual(image2.type(), 'photo')
|
||||
self.assertEqual(image2.width(), 16)
|
||||
self.assertEqual(image2.height(), 16)
|
||||
self.assertEqual(image2.get(0, 0), image.get(0, 0))
|
||||
self.assertEqual(image2.get(4, 6), image.get(4, 6))
|
||||
|
||||
self.assertRaises(tkinter.TclError, image2.read, self.testfile, 'ppm')
|
||||
|
||||
image2 = tkinter.PhotoImage(master=self.root)
|
||||
image2.read(testfile, from_coords=(2, 3, 14, 11))
|
||||
self.assertEqual(image2.width(), 12)
|
||||
self.assertEqual(image2.height(), 8)
|
||||
self.assertEqual(image2.get(0, 0), image.get(2, 3))
|
||||
self.assertEqual(image2.get(11, 7), image.get(13, 10))
|
||||
self.assertEqual(image2.get(2, 4), image.get(2+2, 4+3))
|
||||
|
||||
image2 = tkinter.PhotoImage(master=self.root, file=testfile)
|
||||
self.assertEqual(image2.width(), 16)
|
||||
self.assertEqual(image2.height(), 16)
|
||||
image2.read(testfile, from_coords=(2, 3, 14, 11), shrink=True)
|
||||
self.assertEqual(image2.width(), 12)
|
||||
self.assertEqual(image2.height(), 8)
|
||||
self.assertEqual(image2.get(0, 0), image.get(2, 3))
|
||||
self.assertEqual(image2.get(11, 7), image.get(13, 10))
|
||||
self.assertEqual(image2.get(2, 4), image.get(2+2, 4+3))
|
||||
|
||||
image2 = tkinter.PhotoImage(master=self.root)
|
||||
image2.read(testfile, from_coords=(2, 3, 14, 11), to=(3, 6))
|
||||
self.assertEqual(image2.type(), 'photo')
|
||||
self.assertEqual(image2.width(), 15)
|
||||
self.assertEqual(image2.height(), 14)
|
||||
self.assertEqual(image2.get(0+3, 0+6), image.get(2, 3))
|
||||
self.assertEqual(image2.get(11+3, 7+6), image.get(13, 10))
|
||||
self.assertEqual(image2.get(2+3, 4+6), image.get(2+2, 4+3))
|
||||
|
||||
def test_write(self):
|
||||
filename = os_helper.TESTFN
|
||||
import locale
|
||||
if locale.getlocale()[0] is None:
|
||||
# Tcl uses Latin1 in the C locale
|
||||
filename = os_helper.TESTFN_ASCII
|
||||
image = self.create()
|
||||
self.addCleanup(os_helper.unlink, filename)
|
||||
|
||||
image.write(filename)
|
||||
image2 = tkinter.PhotoImage('::img::test2', master=self.root,
|
||||
format='ppm', file=filename)
|
||||
self.assertEqual(str(image2), '::img::test2')
|
||||
self.assertEqual(image2.type(), 'photo')
|
||||
self.assertEqual(image2.width(), 16)
|
||||
self.assertEqual(image2.height(), 16)
|
||||
self.assertEqual(image2.get(0, 0), image.get(0, 0))
|
||||
self.assertEqual(image2.get(4, 6), image.get(4, 6))
|
||||
|
||||
image.write(filename, format='gif', from_coords=(4, 6, 6, 9))
|
||||
image3 = tkinter.PhotoImage('::img::test3', master=self.root,
|
||||
format='gif', file=filename)
|
||||
self.assertEqual(str(image3), '::img::test3')
|
||||
self.assertEqual(image3.type(), 'photo')
|
||||
self.assertEqual(image3.width(), 2)
|
||||
self.assertEqual(image3.height(), 3)
|
||||
self.assertEqual(image3.get(0, 0), image.get(4, 6))
|
||||
self.assertEqual(image3.get(1, 2), image.get(5, 8))
|
||||
|
||||
image.write(filename, background='#ff0000')
|
||||
image4 = tkinter.PhotoImage('::img::test4', master=self.root,
|
||||
format='ppm', file=filename)
|
||||
self.assertEqual(image4.get(0, 0), (255, 0, 0) if self.wantobjects else '255 0 0')
|
||||
self.assertEqual(image4.get(4, 6), image.get(4, 6))
|
||||
|
||||
image.write(filename, grayscale=True)
|
||||
image5 = tkinter.PhotoImage('::img::test5', master=self.root,
|
||||
format='ppm', file=filename)
|
||||
c = image5.get(4, 6)
|
||||
if not self.wantobjects:
|
||||
c = c.split()
|
||||
self.assertTrue(c[0] == c[1] == c[2], c)
|
||||
|
||||
def test_data(self):
|
||||
image = self.create()
|
||||
|
||||
data = image.data()
|
||||
self.assertIsInstance(data, tuple)
|
||||
for row in data:
|
||||
self.assertIsInstance(row, str)
|
||||
c = image.get(4, 6)
|
||||
if not self.wantobjects:
|
||||
c = tuple(map(int, c.split()))
|
||||
self.assertEqual(data[6].split()[4], '#%02x%02x%02x' % c)
|
||||
|
||||
data = image.data('ppm')
|
||||
image2 = tkinter.PhotoImage('::img::test2', master=self.root,
|
||||
format='ppm', data=data)
|
||||
self.assertEqual(str(image2), '::img::test2')
|
||||
self.assertEqual(image2.type(), 'photo')
|
||||
self.assertEqual(image2.width(), 16)
|
||||
self.assertEqual(image2.height(), 16)
|
||||
self.assertEqual(image2.get(0, 0), image.get(0, 0))
|
||||
self.assertEqual(image2.get(4, 6), image.get(4, 6))
|
||||
|
||||
data = image.data(format='gif', from_coords=(4, 6, 6, 9))
|
||||
image3 = tkinter.PhotoImage('::img::test3', master=self.root,
|
||||
format='gif', data=data)
|
||||
self.assertEqual(str(image3), '::img::test3')
|
||||
self.assertEqual(image3.type(), 'photo')
|
||||
self.assertEqual(image3.width(), 2)
|
||||
self.assertEqual(image3.height(), 3)
|
||||
self.assertEqual(image3.get(0, 0), image.get(4, 6))
|
||||
self.assertEqual(image3.get(1, 2), image.get(5, 8))
|
||||
|
||||
data = image.data('ppm', background='#ff0000')
|
||||
image4 = tkinter.PhotoImage('::img::test4', master=self.root,
|
||||
format='ppm', data=data)
|
||||
self.assertEqual(image4.get(0, 0), (255, 0, 0) if self.wantobjects else '255 0 0')
|
||||
self.assertEqual(image4.get(4, 6), image.get(4, 6))
|
||||
|
||||
data = image.data('ppm', grayscale=True)
|
||||
image5 = tkinter.PhotoImage('::img::test5', master=self.root,
|
||||
format='ppm', data=data)
|
||||
c = image5.get(4, 6)
|
||||
if not self.wantobjects:
|
||||
c = c.split()
|
||||
self.assertTrue(c[0] == c[1] == c[2], c)
|
||||
|
||||
|
||||
def test_transparency(self):
|
||||
image = self.create()
|
||||
self.assertIs(image.transparency_get(0, 0), True)
|
||||
self.assertIs(image.transparency_get(4, 6), False)
|
||||
image.transparency_set(4, 6, True)
|
||||
self.assertIs(image.transparency_get(4, 6), True)
|
||||
image.transparency_set(4, 6, False)
|
||||
self.assertIs(image.transparency_get(4, 6), False)
|
||||
self.assertRaises(tkinter.TclError, image.transparency_get, -1, 0)
|
||||
self.assertRaises(tkinter.TclError, image.transparency_get, 16, 0)
|
||||
self.assertRaises(tkinter.TclError, image.transparency_set, -1, 0, True)
|
||||
self.assertRaises(tkinter.TclError, image.transparency_set, 16, 0, True)
|
||||
self.assertRaises(TypeError, image.transparency_get, 0)
|
||||
self.assertRaises(TypeError, image.transparency_get, 0, 0, 0)
|
||||
self.assertRaises(TypeError, image.transparency_set, 0, 0)
|
||||
self.assertRaises(TypeError, image.transparency_set, 0, 0, True, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import test.support as test_support
|
||||
from test.support import os_helper
|
||||
from test.test_tkinter.support import setUpModule # noqa: F401
|
||||
from tkinter import Tcl, TclError
|
||||
|
||||
test_support.requires('gui')
|
||||
|
||||
class TkLoadTest(unittest.TestCase):
|
||||
|
||||
@unittest.skipIf('DISPLAY' not in os.environ, 'No $DISPLAY set.')
|
||||
def testLoadTk(self):
|
||||
tcl = Tcl()
|
||||
self.assertRaises(TclError,tcl.winfo_geometry)
|
||||
tcl.loadtk()
|
||||
self.assertEqual('1x1+0+0', tcl.winfo_geometry())
|
||||
tcl.destroy()
|
||||
|
||||
def testLoadTkFailure(self):
|
||||
old_display = None
|
||||
if sys.platform.startswith(('win', 'darwin', 'cygwin')):
|
||||
# no failure possible on windows?
|
||||
|
||||
# XXX Maybe on tk older than 8.4.13 it would be possible,
|
||||
# see tkinter.h.
|
||||
return
|
||||
with os_helper.EnvironmentVarGuard() as env:
|
||||
if 'DISPLAY' in os.environ:
|
||||
del env['DISPLAY']
|
||||
# on some platforms, deleting environment variables
|
||||
# doesn't actually carry through to the process level
|
||||
# because they don't support unsetenv
|
||||
# If that's the case, abort.
|
||||
with os.popen('echo $DISPLAY') as pipe:
|
||||
display = pipe.read().strip()
|
||||
if display:
|
||||
return
|
||||
|
||||
tcl = Tcl()
|
||||
self.assertRaises(TclError, tcl.winfo_geometry)
|
||||
self.assertRaises(TclError, tcl.loadtk)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import unittest
|
||||
import tkinter
|
||||
from test.support import requires, swap_attr
|
||||
from test.test_tkinter.support import setUpModule # noqa: F401
|
||||
from test.test_tkinter.support import AbstractDefaultRootTest
|
||||
from tkinter.commondialog import Dialog
|
||||
from tkinter.messagebox import showinfo
|
||||
|
||||
requires('gui')
|
||||
|
||||
|
||||
class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
|
||||
|
||||
def test_showinfo(self):
|
||||
def test_callback(dialog, master):
|
||||
nonlocal ismapped
|
||||
master.update()
|
||||
ismapped = master.winfo_ismapped()
|
||||
raise ZeroDivisionError
|
||||
|
||||
with swap_attr(Dialog, '_test_callback', test_callback):
|
||||
ismapped = None
|
||||
self.assertRaises(ZeroDivisionError, showinfo, "Spam", "Egg Information")
|
||||
self.assertEqual(ismapped, False)
|
||||
|
||||
root = tkinter.Tk()
|
||||
ismapped = None
|
||||
self.assertRaises(ZeroDivisionError, showinfo, "Spam", "Egg Information")
|
||||
self.assertEqual(ismapped, True)
|
||||
root.destroy()
|
||||
|
||||
tkinter.NoDefaultRoot()
|
||||
self.assertRaises(RuntimeError, showinfo, "Spam", "Egg Information")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Vendored
+1982
File diff suppressed because it is too large.
Load diff
+65
@@ -0,0 +1,65 @@
|
||||
import unittest
|
||||
import tkinter
|
||||
from tkinter.scrolledtext import ScrolledText
|
||||
from test.support import requires
|
||||
from test.test_tkinter.support import setUpModule # noqa: F401
|
||||
from test.test_tkinter.support import AbstractTkTest
|
||||
|
||||
requires('gui')
|
||||
|
||||
|
||||
class ScrolledTextTest(AbstractTkTest, unittest.TestCase):
|
||||
|
||||
def create(self, **kwargs):
|
||||
st = ScrolledText(self.root, **kwargs)
|
||||
self.addCleanup(st.destroy)
|
||||
return st
|
||||
|
||||
def test_create(self):
|
||||
st = self.create(background='red', height=5)
|
||||
# It is a Text widget held in a Frame together with a Scrollbar.
|
||||
self.assertIsInstance(st, tkinter.Text)
|
||||
self.assertIsInstance(st.frame, tkinter.Frame)
|
||||
self.assertIsInstance(st.vbar, tkinter.Scrollbar)
|
||||
self.assertEqual(st.winfo_parent(), str(st.frame))
|
||||
# str() returns the frame, so that geometry managers manage it.
|
||||
self.assertEqual(str(st), str(st.frame))
|
||||
# Keyword options configure the Text.
|
||||
self.assertEqual(str(st['background']), 'red')
|
||||
self.assertEqual(st['height'], 5 if self.wantobjects else '5')
|
||||
|
||||
def test_text_methods(self):
|
||||
st = self.create()
|
||||
st.insert('1.0', 'hello\nworld')
|
||||
self.assertEqual(st.get('1.0', 'end-1c'), 'hello\nworld')
|
||||
self.assertEqual(st.index('end-1c'), '2.5')
|
||||
st.delete('1.0', 'end')
|
||||
self.assertEqual(st.get('1.0', 'end-1c'), '')
|
||||
|
||||
def test_geometry_methods(self):
|
||||
st = self.create()
|
||||
# configure is not redirected; it configures the Text.
|
||||
st.configure(height=8)
|
||||
self.assertEqual(st['height'], 8 if self.wantobjects else '8')
|
||||
# Pack, Grid and Place methods are redirected to the frame.
|
||||
st.pack()
|
||||
self.root.update()
|
||||
self.assertEqual(st.frame.winfo_manager(), 'pack')
|
||||
self.assertEqual(st.pack_info(), st.frame.pack_info())
|
||||
st.pack_forget()
|
||||
self.assertEqual(st.frame.winfo_manager(), '')
|
||||
|
||||
def test_scrollbar(self):
|
||||
st = self.create(height=5)
|
||||
st.pack()
|
||||
st.insert('1.0', '\n'.join(map(str, range(100))))
|
||||
self.root.update()
|
||||
# The scrollbar tracks the text view.
|
||||
self.assertEqual(st.vbar.get(), st.yview())
|
||||
st.yview_moveto(1.0)
|
||||
self.root.update()
|
||||
self.assertEqual(st.vbar.get()[1], 1.0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
import unittest
|
||||
import tkinter
|
||||
from tkinter import messagebox
|
||||
from test.support import requires, swap_attr
|
||||
from test.test_tkinter.support import setUpModule # noqa: F401
|
||||
from test.test_tkinter.support import (AbstractDefaultRootTest,
|
||||
AbstractDialogTest, AbstractTkTest)
|
||||
from tkinter.simpledialog import (Dialog, SimpleDialog,
|
||||
askinteger, askfloat, askstring,
|
||||
_QueryInteger, _QueryFloat, _QueryString)
|
||||
|
||||
requires('gui')
|
||||
|
||||
|
||||
class SimpleDialogTest(AbstractTkTest, unittest.TestCase):
|
||||
# The root window is not hidden here (cf. AbstractDialogTest): SimpleDialog
|
||||
# makes its window transient for the master unconditionally, and a
|
||||
# transient of a hidden window is never mapped on Windows, so that go()
|
||||
# would block in wait_visibility().
|
||||
# SimpleDialog's modal loop is in go(); its bindings are exercised here by
|
||||
# generating events on the constructed dialog, without entering the loop.
|
||||
|
||||
def create(self, **kw):
|
||||
kw.setdefault('text', 'Question?')
|
||||
kw.setdefault('buttons', ['Yes', 'No'])
|
||||
kw.setdefault('default', 0)
|
||||
kw.setdefault('cancel', 1)
|
||||
d = SimpleDialog(self.root, **kw)
|
||||
self.addCleanup(lambda: d.root.winfo_exists() and d.root.destroy())
|
||||
return d
|
||||
|
||||
def test_message(self):
|
||||
# The text is shown in a message widget.
|
||||
d = self.create(text='Hello?')
|
||||
self.assertEqual(d.message.winfo_class(), 'Message')
|
||||
self.assertEqual(str(d.message.cget('text')), 'Hello?')
|
||||
|
||||
def test_class_name(self):
|
||||
# class_ sets the Tk class of the dialog window.
|
||||
d = self.create(class_='MyDialog')
|
||||
self.assertEqual(d.root.winfo_class(), 'MyDialog')
|
||||
|
||||
def test_button(self):
|
||||
# Pressing a button records its index.
|
||||
d = self.create(buttons=['Yes', 'No'])
|
||||
d.frame.winfo_children()[1].invoke() # "No"
|
||||
self.assertEqual(d.num, 1)
|
||||
|
||||
def test_default_button(self):
|
||||
# The default button is drawn with a raised border.
|
||||
d = self.create(buttons=['Yes', 'No'], default=0)
|
||||
self.assertEqual(str(d.frame.winfo_children()[0].cget('relief')), 'ridge')
|
||||
|
||||
def test_return_activates_default(self):
|
||||
# <Return> invokes the default button.
|
||||
d = self.create() # default 0
|
||||
self.require_mapped(d.root)
|
||||
d.root.update()
|
||||
d.root.focus_force()
|
||||
d.root.event_generate('<Return>')
|
||||
d.root.update()
|
||||
self.assertEqual(d.num, 0)
|
||||
|
||||
def test_return_no_default(self):
|
||||
# With no default button, <Return> rings the bell and leaves the dialog
|
||||
# open instead of activating a button.
|
||||
d = self.create(default=None)
|
||||
self.require_mapped(d.root)
|
||||
bells = []
|
||||
with swap_attr(d.root, 'bell', lambda *a, **k: bells.append(True)):
|
||||
d.root.focus_force()
|
||||
d.root.event_generate('<Return>')
|
||||
d.root.update()
|
||||
self.assertTrue(bells) # rang the bell
|
||||
self.assertIsNone(d.num)
|
||||
self.assertTrue(d.root.winfo_exists())
|
||||
|
||||
def test_wm_delete_cancels(self):
|
||||
# Closing the window through the window manager records the cancel index.
|
||||
d = self.create() # cancel 1
|
||||
d.wm_delete_window()
|
||||
self.assertEqual(d.num, 1)
|
||||
|
||||
def test_wm_delete_no_cancel(self):
|
||||
# With no cancel index, closing the window through the window manager
|
||||
# rings the bell and leaves the dialog open instead of recording an
|
||||
# index.
|
||||
d = self.create(default=None, cancel=None)
|
||||
d.root.update()
|
||||
bells = []
|
||||
with swap_attr(d.root, 'bell', lambda *a, **k: bells.append(True)):
|
||||
d.wm_delete_window()
|
||||
d.root.update()
|
||||
self.assertTrue(bells) # rang the bell
|
||||
self.assertIsNone(d.num)
|
||||
self.assertTrue(d.root.winfo_exists())
|
||||
|
||||
def test_go(self):
|
||||
# go() runs the modal loop and returns the chosen button's index.
|
||||
d = self.create()
|
||||
d.root.after(1, lambda: d.done(0))
|
||||
self.assertEqual(d.go(), 0)
|
||||
|
||||
|
||||
class DialogTest(AbstractDialogTest, unittest.TestCase):
|
||||
# Dialog is a base class for custom dialogs; exercise it via _QueryInteger.
|
||||
|
||||
def open(self, **kw):
|
||||
with swap_attr(Dialog, 'wait_window', staticmethod(lambda w: None)):
|
||||
d = _QueryInteger('Title', 'Prompt', parent=self.root, **kw)
|
||||
self.addCleanup(lambda: d.winfo_exists() and d.destroy())
|
||||
return d
|
||||
|
||||
def buttons(self, d):
|
||||
# Map the button box's buttons by their label.
|
||||
return {str(b.cget('text')): b
|
||||
for frame in d.winfo_children()
|
||||
for b in frame.winfo_children()
|
||||
if b.winfo_class() == 'Button'}
|
||||
|
||||
def test_background(self):
|
||||
# The classic dialog keeps the default Toplevel background.
|
||||
d = self.open()
|
||||
ref = tkinter.Toplevel(self.root)
|
||||
self.addCleanup(ref.destroy)
|
||||
self.assertEqual(str(d.cget('background')), str(ref.cget('background')))
|
||||
|
||||
def test_buttons(self):
|
||||
# The button box has OK (the default) and Cancel buttons.
|
||||
buttons = self.buttons(self.open())
|
||||
self.assertEqual(set(buttons), {'OK', 'Cancel'})
|
||||
self.assertEqual(str(buttons['OK'].cget('default')), 'active')
|
||||
|
||||
def test_ok(self):
|
||||
# The OK button validates the entry and stores the result.
|
||||
d = self.open()
|
||||
d.entry.insert(0, '42')
|
||||
self.buttons(d)['OK'].invoke()
|
||||
self.assertEqual(d.result, 42)
|
||||
self.assertFalse(d.winfo_exists()) # The dialog closed.
|
||||
|
||||
def test_cancel(self):
|
||||
# The Cancel button closes the dialog without a result.
|
||||
d = self.open()
|
||||
self.buttons(d)['Cancel'].invoke()
|
||||
self.assertIsNone(d.result)
|
||||
self.assertFalse(d.winfo_exists())
|
||||
|
||||
|
||||
class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
|
||||
|
||||
def test_askinteger(self):
|
||||
@staticmethod
|
||||
def mock_wait_window(w):
|
||||
nonlocal ismapped
|
||||
ismapped = w.master.winfo_ismapped()
|
||||
w.destroy()
|
||||
|
||||
with swap_attr(Dialog, 'wait_window', mock_wait_window):
|
||||
ismapped = None
|
||||
askinteger("Go To Line", "Line number")
|
||||
self.assertEqual(ismapped, False)
|
||||
|
||||
root = tkinter.Tk()
|
||||
ismapped = None
|
||||
askinteger("Go To Line", "Line number")
|
||||
self.assertEqual(ismapped, True)
|
||||
root.destroy()
|
||||
|
||||
tkinter.NoDefaultRoot()
|
||||
self.assertRaises(RuntimeError, askinteger, "Go To Line", "Line number")
|
||||
|
||||
|
||||
class QueryDialogTest(AbstractDialogTest, unittest.TestCase):
|
||||
# The query dialogs are modal: their __init__ blocks in wait_window().
|
||||
# Mock that out so the dialog stays alive and can be driven with generated
|
||||
# events, exercising the <Return>/<Escape> bindings and the validation.
|
||||
|
||||
def open(self, query, **kw):
|
||||
with swap_attr(Dialog, 'wait_window', staticmethod(lambda w: None)):
|
||||
d = query("Title", "Prompt", parent=self.root, **kw)
|
||||
self.addCleanup(lambda: d.winfo_exists() and d.destroy())
|
||||
d.focus_force()
|
||||
d.update()
|
||||
return d
|
||||
|
||||
def enter(self, d, value, key='<Return>'):
|
||||
d.entry.delete(0, 'end')
|
||||
d.entry.insert(0, value)
|
||||
d.event_generate(key)
|
||||
d.update()
|
||||
|
||||
def test_initialvalue(self):
|
||||
# The entry is pre-filled with the initial value, which is accepted.
|
||||
d = self.open(_QueryInteger, initialvalue=42)
|
||||
self.assertEqual(d.entry.get(), '42')
|
||||
d.event_generate('<Return>')
|
||||
d.update()
|
||||
self.assertEqual(d.result, 42)
|
||||
|
||||
def test_show(self):
|
||||
# _QueryString hides the entered text when show is given.
|
||||
d = self.open(_QueryString, show='*')
|
||||
self.assertEqual(str(d.entry.cget('show')), '*')
|
||||
|
||||
def test_return_accepts(self):
|
||||
for query, value, expected in [
|
||||
(_QueryInteger, '42', 42),
|
||||
(_QueryFloat, '1.5', 1.5),
|
||||
(_QueryString, 'spam', 'spam'),
|
||||
]:
|
||||
with self.subTest(query=query.__name__):
|
||||
d = self.open(query)
|
||||
self.enter(d, value)
|
||||
self.assertEqual(d.result, expected)
|
||||
self.assertFalse(d.winfo_exists()) # The dialog closed.
|
||||
|
||||
def test_escape_cancels(self):
|
||||
d = self.open(_QueryString)
|
||||
self.enter(d, 'spam', '<Escape>')
|
||||
self.assertIsNone(d.result)
|
||||
self.assertFalse(d.winfo_exists())
|
||||
|
||||
def test_invalid_value(self):
|
||||
warnings = []
|
||||
d = self.open(_QueryInteger)
|
||||
with swap_attr(messagebox, 'showwarning',
|
||||
lambda *a, **k: warnings.append(a)):
|
||||
self.enter(d, 'not a number')
|
||||
self.assertIsNone(d.result)
|
||||
self.assertTrue(d.winfo_exists()) # The dialog stays open.
|
||||
self.assertTrue(warnings)
|
||||
|
||||
def test_out_of_range(self):
|
||||
warnings = []
|
||||
d = self.open(_QueryInteger, minvalue=10, maxvalue=20)
|
||||
with swap_attr(messagebox, 'showwarning',
|
||||
lambda *a, **k: warnings.append(a)):
|
||||
self.enter(d, '5') # Below the minimum.
|
||||
self.assertIsNone(d.result)
|
||||
self.enter(d, '25') # Above the maximum.
|
||||
self.assertIsNone(d.result)
|
||||
self.assertTrue(d.winfo_exists())
|
||||
self.assertEqual(len(warnings), 2)
|
||||
|
||||
def test_boundary_values_accepted(self):
|
||||
# The min/max checks are inclusive: a value equal to a bound passes.
|
||||
d = self.open(_QueryInteger, minvalue=10, maxvalue=20)
|
||||
self.enter(d, '10') # Exactly the minimum.
|
||||
self.assertEqual(d.result, 10)
|
||||
self.assertFalse(d.winfo_exists())
|
||||
|
||||
d = self.open(_QueryInteger, minvalue=10, maxvalue=20)
|
||||
self.enter(d, '20') # Exactly the maximum.
|
||||
self.assertEqual(d.result, 20)
|
||||
self.assertFalse(d.winfo_exists())
|
||||
|
||||
def run_ask(self, ask, value, **kw):
|
||||
# Drive a modal ask* function: enter a value and accept it.
|
||||
def accept(d):
|
||||
d.entry.delete(0, 'end')
|
||||
d.entry.insert(0, value)
|
||||
d.ok()
|
||||
with swap_attr(Dialog, 'wait_window', staticmethod(accept)):
|
||||
return ask('Title', 'Prompt', parent=self.root, **kw)
|
||||
|
||||
def test_ask_functions(self):
|
||||
self.assertEqual(self.run_ask(askinteger, '42'), 42)
|
||||
self.assertEqual(self.run_ask(askfloat, '1.5'), 1.5)
|
||||
self.assertEqual(self.run_ask(askstring, 'spam'), 'spam')
|
||||
|
||||
def test_ask_cancelled(self):
|
||||
# A cancelled ask* returns None.
|
||||
with swap_attr(Dialog, 'wait_window', staticmethod(lambda d: d.cancel())):
|
||||
self.assertIsNone(askstring('Title', 'Prompt', parent=self.root))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Vendored
+747
@@ -0,0 +1,747 @@
|
||||
import unittest
|
||||
import tkinter
|
||||
from tkinter import TclError
|
||||
from test.support import requires
|
||||
from test.test_tkinter.support import setUpModule # noqa: F401
|
||||
from test.test_tkinter.support import AbstractTkTest, requires_tk
|
||||
|
||||
requires('gui')
|
||||
|
||||
class TextTest(AbstractTkTest, unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.text = tkinter.Text(self.root)
|
||||
self.text.pack()
|
||||
|
||||
def test_debug(self):
|
||||
text = self.text
|
||||
olddebug = text.debug()
|
||||
try:
|
||||
text.debug(False)
|
||||
self.assertIs(text.debug(), False)
|
||||
text.debug(True)
|
||||
self.assertIs(text.debug(), True)
|
||||
finally:
|
||||
text.debug(olddebug)
|
||||
self.assertEqual(text.debug(), olddebug)
|
||||
|
||||
def test_index(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'Lorem ipsum\ndolor sit amet')
|
||||
self.assertEqual(text.index('1.0'), '1.0')
|
||||
self.assertEqual(text.index('1.end'), '1.11')
|
||||
self.assertEqual(text.index('end'), '3.0')
|
||||
self.assertEqual(text.index('2.5'), '2.5')
|
||||
# Index past the end of a line is clamped to its end.
|
||||
self.assertEqual(text.index('1.100'), '1.11')
|
||||
self.assertRaises(TclError, text.index, 'invalid')
|
||||
self.assertRaises(TypeError, text.index)
|
||||
self.assertRaises(TypeError, text.index, '1.0', '2.5')
|
||||
|
||||
def test_compare(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'Lorem ipsum\ndolor sit amet')
|
||||
self.assertIs(text.compare('1.0', '<', '2.0'), True)
|
||||
self.assertIs(text.compare('2.0', '<', '1.0'), False)
|
||||
self.assertIs(text.compare('1.5', '==', '1.5'), True)
|
||||
self.assertIs(text.compare('1.5', '!=', '1.5'), False)
|
||||
self.assertIs(text.compare('2.0', '>=', '2.0'), True)
|
||||
self.assertIs(text.compare('1.0', '<=', 'end'), True)
|
||||
self.assertRaises(TclError, text.compare, '1.0', 'invalid', '2.0')
|
||||
self.assertRaises(TypeError, text.compare, '1.0', '<')
|
||||
self.assertRaises(TypeError, text.compare, '1.0', '<', '2.0', '3.0')
|
||||
|
||||
def test_insert_get_delete(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'Lorem ipsum')
|
||||
self.assertEqual(text.get('1.0', 'end'), 'Lorem ipsum\n')
|
||||
self.assertEqual(text.get('1.0', '1.5'), 'Lorem')
|
||||
self.assertEqual(text.get('1.6'), 'i') # single character
|
||||
|
||||
# insert before an existing index
|
||||
text.insert('1.0', '*** ')
|
||||
self.assertEqual(text.get('1.0', 'end'), '*** Lorem ipsum\n')
|
||||
|
||||
text.delete('1.0', '1.4')
|
||||
self.assertEqual(text.get('1.0', 'end'), 'Lorem ipsum\n')
|
||||
text.delete('1.5') # delete a single character
|
||||
self.assertEqual(text.get('1.0', 'end'), 'Loremipsum\n')
|
||||
self.assertRaises(TypeError, text.get)
|
||||
self.assertRaises(TypeError, text.get, '1.0', '1.5', 'end')
|
||||
self.assertRaises(TypeError, text.delete, '1.0', '1.5', 'end')
|
||||
|
||||
def test_insert_with_tags(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'hello', 'a', ' ', ('a', 'b'), 'world', 'b')
|
||||
self.assertEqual(text.get('1.0', 'end'), 'hello world\n')
|
||||
self.assertEqual([str(i) for i in text.tag_ranges('a')],
|
||||
['1.0', '1.6'])
|
||||
self.assertEqual([str(i) for i in text.tag_ranges('b')],
|
||||
['1.5', '1.11'])
|
||||
|
||||
def test_replace(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'Lorem ipsum')
|
||||
text.replace('1.0', '1.5', 'Hello')
|
||||
self.assertEqual(text.get('1.0', 'end'), 'Hello ipsum\n')
|
||||
text.replace('1.6', 'end - 1 char', 'world')
|
||||
self.assertEqual(text.get('1.0', 'end'), 'Hello world\n')
|
||||
self.assertRaises(TclError, text.replace, 'invalid', '1.5', 'x')
|
||||
# The first index must not be after the second.
|
||||
self.assertRaises(TclError, text.replace, '1.5', '1.0', 'x')
|
||||
self.assertRaises(TypeError, text.replace, '1.0', '1.5')
|
||||
|
||||
def test_mark(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'Lorem ipsum\ndolor sit amet')
|
||||
# 'insert' and 'current' marks always exist.
|
||||
self.assertIn('insert', text.mark_names())
|
||||
self.assertIn('current', text.mark_names())
|
||||
|
||||
text.mark_set('here', '1.5')
|
||||
self.assertIn('here', text.mark_names())
|
||||
self.assertEqual(text.index('here'), '1.5')
|
||||
text.mark_set('here', '2.3')
|
||||
self.assertEqual(text.index('here'), '2.3')
|
||||
|
||||
text.mark_unset('here')
|
||||
self.assertNotIn('here', text.mark_names())
|
||||
|
||||
# Unsetting a non-existent mark is not an error.
|
||||
text.mark_unset('nonexistent')
|
||||
self.assertRaises(TclError, text.mark_set, 'm', 'invalid')
|
||||
self.assertRaises(TypeError, text.mark_set, 'm')
|
||||
self.assertRaises(TypeError, text.mark_set, 'm', '1.0', '2.0')
|
||||
|
||||
def test_mark_gravity(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'Lorem ipsum')
|
||||
text.mark_set('here', '1.5')
|
||||
# The default gravity is right.
|
||||
self.assertEqual(text.mark_gravity('here'), 'right')
|
||||
text.mark_gravity('here', 'left')
|
||||
self.assertEqual(text.mark_gravity('here'), 'left')
|
||||
# With left gravity the mark stays before inserted text.
|
||||
text.insert('here', 'XXX')
|
||||
self.assertEqual(text.index('here'), '1.5')
|
||||
# With right gravity the mark moves after inserted text.
|
||||
text.mark_gravity('here', 'right')
|
||||
text.insert('here', 'YYY')
|
||||
self.assertEqual(text.index('here'), '1.8')
|
||||
|
||||
self.assertRaises(TclError, text.mark_gravity, 'nonexistent')
|
||||
self.assertRaises(TclError, text.mark_gravity, 'here', 'invalid')
|
||||
self.assertRaises(TypeError, text.mark_gravity)
|
||||
self.assertRaises(TypeError, text.mark_gravity, 'here', 'left', 'extra')
|
||||
|
||||
def test_mark_next_previous(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'Lorem ipsum')
|
||||
# Keep the builtin 'insert' and 'current' marks at 1.0 so they do
|
||||
# not interfere with the queries below.
|
||||
text.mark_set('insert', '1.0')
|
||||
text.mark_set('current', '1.0')
|
||||
text.mark_set('m1', '1.3')
|
||||
text.mark_set('m2', '1.7')
|
||||
# mark_next finds a mark at a position at or after the index,
|
||||
# mark_previous finds one at a position strictly before it.
|
||||
self.assertEqual(text.mark_next('1.1'), 'm1')
|
||||
self.assertEqual(text.mark_next('1.3'), 'm1')
|
||||
self.assertEqual(text.mark_next('1.4'), 'm2')
|
||||
self.assertIsNone(text.mark_next('1.8'))
|
||||
self.assertEqual(text.mark_previous('1.4'), 'm1')
|
||||
self.assertEqual(text.mark_previous('1.7'), 'm1')
|
||||
self.assertEqual(text.mark_previous('end'), 'm2')
|
||||
self.assertIsNone(text.mark_previous('1.0'))
|
||||
|
||||
self.assertRaises(TclError, text.mark_next, 'invalid')
|
||||
self.assertRaises(TclError, text.mark_previous, 'invalid')
|
||||
self.assertRaises(TypeError, text.mark_next)
|
||||
self.assertRaises(TypeError, text.mark_previous)
|
||||
self.assertRaises(TypeError, text.mark_next, '1.0', '2.0')
|
||||
self.assertRaises(TypeError, text.mark_previous, '1.0', '2.0')
|
||||
|
||||
def test_tag_add_remove_ranges(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'Lorem ipsum\ndolor sit amet')
|
||||
self.assertEqual(text.tag_ranges('sel'), ())
|
||||
|
||||
text.tag_add('big', '1.0', '1.5')
|
||||
self.assertEqual([str(i) for i in text.tag_ranges('big')],
|
||||
['1.0', '1.5'])
|
||||
# Add a second, disjoint range.
|
||||
text.tag_add('big', '2.0', '2.5')
|
||||
self.assertEqual([str(i) for i in text.tag_ranges('big')],
|
||||
['1.0', '1.5', '2.0', '2.5'])
|
||||
|
||||
text.tag_remove('big', '1.0', '1.3')
|
||||
self.assertEqual([str(i) for i in text.tag_ranges('big')],
|
||||
['1.3', '1.5', '2.0', '2.5'])
|
||||
|
||||
# tag_ranges of an undefined tag is empty, not an error.
|
||||
self.assertEqual(text.tag_ranges('nonexistent'), ())
|
||||
self.assertRaises(TclError, text.tag_add, 'big', 'invalid')
|
||||
self.assertRaises(TypeError, text.tag_add, 'big')
|
||||
self.assertRaises(TclError, text.tag_remove, 'big', 'invalid')
|
||||
self.assertRaises(TypeError, text.tag_remove, 'big', '1.0', '2.0', '3.0')
|
||||
self.assertRaises(TypeError, text.tag_ranges, 'big', 'extra')
|
||||
|
||||
def test_tag_names(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'Lorem ipsum')
|
||||
text.tag_add('a', '1.0', '1.5')
|
||||
text.tag_add('b', '1.3', '1.8')
|
||||
# 'sel' always exists; order reflects priority (creation order).
|
||||
self.assertEqual(set(text.tag_names()), {'sel', 'a', 'b'})
|
||||
# Tags applied to the character at the given index.
|
||||
self.assertEqual(set(text.tag_names('1.4')), {'a', 'b'})
|
||||
self.assertEqual(set(text.tag_names('1.0')), {'a'})
|
||||
self.assertEqual(set(text.tag_names('1.10')), set())
|
||||
self.assertRaises(TclError, text.tag_names, 'invalid')
|
||||
self.assertRaises(TypeError, text.tag_names, '1.0', '2.0')
|
||||
|
||||
def test_tag_nextrange_prevrange(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'Lorem ipsum dolor')
|
||||
text.tag_add('a', '1.0', '1.5')
|
||||
text.tag_add('a', '1.12', '1.17')
|
||||
|
||||
self.assertEqual([str(i) for i in text.tag_nextrange('a', '1.0')],
|
||||
['1.0', '1.5'])
|
||||
self.assertEqual([str(i) for i in text.tag_nextrange('a', '1.5')],
|
||||
['1.12', '1.17'])
|
||||
self.assertEqual(text.tag_nextrange('a', '1.17'), ())
|
||||
|
||||
self.assertEqual([str(i) for i in text.tag_prevrange('a', 'end')],
|
||||
['1.12', '1.17'])
|
||||
self.assertEqual([str(i) for i in text.tag_prevrange('a', '1.12')],
|
||||
['1.0', '1.5'])
|
||||
self.assertEqual(text.tag_prevrange('a', '1.0'), ())
|
||||
|
||||
# An undefined tag has no ranges, but the index must be valid.
|
||||
self.assertEqual(text.tag_nextrange('nonexistent', '1.0'), ())
|
||||
self.assertRaises(TclError, text.tag_nextrange, 'a', 'invalid')
|
||||
self.assertRaises(TclError, text.tag_prevrange, 'a', 'invalid')
|
||||
self.assertRaises(TypeError, text.tag_nextrange, 'a')
|
||||
self.assertRaises(TypeError, text.tag_prevrange, 'a')
|
||||
self.assertRaises(TypeError, text.tag_nextrange, 'a', '1.0', '2.0', '3.0')
|
||||
self.assertRaises(TypeError, text.tag_prevrange, 'a', '1.0', '2.0', '3.0')
|
||||
|
||||
def test_tag_delete(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'Lorem ipsum')
|
||||
text.tag_add('a', '1.0', '1.5')
|
||||
text.tag_add('b', '1.6', '1.11')
|
||||
self.assertEqual(set(text.tag_names()), {'sel', 'a', 'b'})
|
||||
text.tag_delete('a', 'b')
|
||||
self.assertEqual(set(text.tag_names()), {'sel'})
|
||||
self.assertEqual(text.tag_ranges('a'), ())
|
||||
|
||||
def test_tag_raise_lower(self):
|
||||
text = self.text
|
||||
text.tag_configure('a', foreground='red')
|
||||
text.tag_configure('b', foreground='green')
|
||||
text.tag_configure('c', foreground='blue')
|
||||
# Creation order is lowest-to-highest priority; tag_names lists
|
||||
# them in increasing priority order.
|
||||
self.assertEqual(text.tag_names(), ('sel', 'a', 'b', 'c'))
|
||||
text.tag_raise('a')
|
||||
self.assertEqual(text.tag_names(), ('sel', 'b', 'c', 'a'))
|
||||
text.tag_lower('a')
|
||||
self.assertEqual(text.tag_names(), ('a', 'sel', 'b', 'c'))
|
||||
text.tag_raise('a', 'b')
|
||||
self.assertEqual(text.tag_names(), ('sel', 'b', 'a', 'c'))
|
||||
text.tag_lower('c', 'b')
|
||||
self.assertEqual(text.tag_names(), ('sel', 'c', 'b', 'a'))
|
||||
|
||||
self.assertRaises(TclError, text.tag_raise, 'nonexistent')
|
||||
self.assertRaises(TclError, text.tag_lower, 'nonexistent')
|
||||
self.assertRaises(TclError, text.tag_raise, 'a', 'nonexistent')
|
||||
self.assertRaises(TypeError, text.tag_raise)
|
||||
self.assertRaises(TypeError, text.tag_raise, 'a', 'b', 'c')
|
||||
self.assertRaises(TypeError, text.tag_lower, 'a', 'b', 'c')
|
||||
|
||||
def test_tag_cget_configure(self):
|
||||
text = self.text
|
||||
text.tag_configure('a', foreground='red', underline=True)
|
||||
self.assertEqual(str(text.tag_cget('a', 'foreground')), 'red')
|
||||
self.assertIn(text.tag_cget('a', 'underline'), (1, '1', True))
|
||||
# tag_cget normalizes the option name (no leading hyphen needed).
|
||||
self.assertEqual(str(text.tag_cget('a', '-foreground')), 'red')
|
||||
# configure() query returns the full option spec.
|
||||
self.assertEqual(text.tag_configure('a', 'foreground')[-1], 'red')
|
||||
text.tag_configure('a', foreground='blue')
|
||||
self.assertEqual(str(text.tag_cget('a', 'foreground')), 'blue')
|
||||
|
||||
self.assertRaises(TclError, text.tag_cget, 'nonexistent',
|
||||
'foreground')
|
||||
self.assertRaises(TypeError, text.tag_cget, 'a')
|
||||
self.assertRaises(TypeError, text.tag_cget, 'a', 'foreground', 'extra')
|
||||
|
||||
def test_edit_modified(self):
|
||||
text = self.text
|
||||
self.assertEqual(text.edit_modified(), 0)
|
||||
text.insert('1.0', 'spam')
|
||||
self.assertEqual(text.edit_modified(), 1)
|
||||
text.edit_modified(False)
|
||||
self.assertEqual(text.edit_modified(), 0)
|
||||
text.edit_modified(True)
|
||||
self.assertEqual(text.edit_modified(), 1)
|
||||
|
||||
def test_edit_undo_redo(self):
|
||||
text = self.text
|
||||
text.configure(undo=True)
|
||||
text.insert('1.0', 'spam')
|
||||
text.edit_separator()
|
||||
text.insert('end', ' eggs')
|
||||
self.assertEqual(text.get('1.0', 'end'), 'spam eggs\n')
|
||||
|
||||
text.edit_undo()
|
||||
self.assertEqual(text.get('1.0', 'end'), 'spam\n')
|
||||
text.edit_redo()
|
||||
self.assertEqual(text.get('1.0', 'end'), 'spam eggs\n')
|
||||
|
||||
# An empty undo stack raises.
|
||||
text.edit_reset()
|
||||
self.assertRaises(TclError, text.edit_undo)
|
||||
|
||||
def test_dump(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'hello')
|
||||
text.tag_add('a', '1.0', '1.3')
|
||||
text.mark_set('here', '1.2')
|
||||
|
||||
dump = text.dump('1.0', 'end')
|
||||
self.assertIsInstance(dump, list)
|
||||
for item in dump:
|
||||
self.assertIsInstance(item, tuple)
|
||||
self.assertEqual(len(item), 3)
|
||||
kinds = {item[0] for item in dump}
|
||||
self.assertIn('text', kinds)
|
||||
self.assertIn('tagon', kinds)
|
||||
self.assertIn('mark', kinds)
|
||||
|
||||
# Filtering by kind.
|
||||
text_only = text.dump('1.0', 'end', text=True)
|
||||
self.assertEqual({item[0] for item in text_only}, {'text'})
|
||||
self.assertEqual(''.join(item[1] for item in text_only), 'hello\n')
|
||||
|
||||
# The command callback receives each triple and suppresses the result.
|
||||
collected = []
|
||||
result = text.dump('1.0', 'end', text=True,
|
||||
command=lambda *a: collected.append(a))
|
||||
self.assertIsNone(result)
|
||||
self.assertEqual(''.join(key_value_index[1]
|
||||
for key_value_index in collected), 'hello\n')
|
||||
|
||||
self.assertRaises(TclError, text.dump, 'invalid')
|
||||
self.assertRaises(TypeError, text.dump)
|
||||
self.assertRaises(TypeError, text.dump, '1.0', 'end', None, 'extra')
|
||||
|
||||
def test_image(self):
|
||||
text = self.text
|
||||
image = tkinter.PhotoImage(master=self.root, width=10, height=10)
|
||||
text.insert('1.0', 'AB')
|
||||
name = text.image_create('1.1', image=image)
|
||||
self.assertIsInstance(name, str)
|
||||
self.assertIn(name, self.root.splitlist(text.image_names()))
|
||||
self.assertEqual(str(text.image_cget(name, 'image')), str(image))
|
||||
|
||||
text.image_configure(name, align='top')
|
||||
self.assertEqual(str(text.image_cget(name, 'align')), 'top')
|
||||
|
||||
# An embedded image occupies a single index position.
|
||||
self.assertEqual(text.index('end - 1 char'), '1.3')
|
||||
|
||||
# Either a name or an image is required, and the index must be valid.
|
||||
self.assertRaises(TclError, text.image_create, '1.0')
|
||||
self.assertRaises(TclError, text.image_create, 'invalid',
|
||||
image=image)
|
||||
self.assertRaises(TypeError, text.image_cget, name)
|
||||
self.assertRaises(TypeError, text.image_cget, name, 'image', 'extra')
|
||||
|
||||
def test_window(self):
|
||||
text = self.text
|
||||
button = tkinter.Button(text, text='ok')
|
||||
text.insert('1.0', 'AB')
|
||||
text.window_create('1.1', window=button)
|
||||
self.assertEqual(self.root.splitlist(text.window_names()),
|
||||
(str(button),))
|
||||
self.assertEqual(text.window_cget('1.1', 'window'), str(button))
|
||||
|
||||
text.window_configure('1.1', padx=5)
|
||||
self.assertEqual(text.window_cget('1.1', 'padx'), 5)
|
||||
|
||||
# No embedded window at this index, and the index must be valid.
|
||||
self.assertRaises(TclError, text.window_cget, '1.0', 'window')
|
||||
self.assertRaises(TclError, text.window_cget, 'invalid', 'window')
|
||||
self.assertRaises(TypeError, text.window_cget, '1.1')
|
||||
self.assertRaises(TypeError, text.window_cget, '1.1', 'window', 'extra')
|
||||
button.destroy()
|
||||
|
||||
def test_tag_configure(self):
|
||||
text = self.text
|
||||
tag = 'a'
|
||||
getint = text.tk.getint
|
||||
getboolean = text.tk.getboolean
|
||||
|
||||
# Color options.
|
||||
for opt in ('foreground', 'background'):
|
||||
text.tag_configure(tag, **{opt: '#ff0000'})
|
||||
self.assertEqual(str(text.tag_cget(tag, opt)), '#ff0000')
|
||||
|
||||
# Stipple (bitmap) options.
|
||||
for opt in ('bgstipple', 'fgstipple'):
|
||||
text.tag_configure(tag, **{opt: 'gray50'})
|
||||
self.assertEqual(str(text.tag_cget(tag, opt)), 'gray50')
|
||||
|
||||
# Enumerated options.
|
||||
for opt, value in (('justify', 'center'), ('wrap', 'word'),
|
||||
('relief', 'raised'), ('tabstyle', 'wordprocessor'),
|
||||
('offset', '5')):
|
||||
text.tag_configure(tag, **{opt: value})
|
||||
self.assertEqual(str(text.tag_cget(tag, 'justify')), 'center')
|
||||
self.assertEqual(str(text.tag_cget(tag, 'wrap')), 'word')
|
||||
self.assertEqual(str(text.tag_cget(tag, 'relief')), 'raised')
|
||||
self.assertEqual(str(text.tag_cget(tag, 'tabstyle')), 'wordprocessor')
|
||||
|
||||
# Boolean options.
|
||||
for opt in ('underline', 'overstrike', 'elide'):
|
||||
text.tag_configure(tag, **{opt: True})
|
||||
self.assertIs(getboolean(text.tag_cget(tag, opt)), True)
|
||||
text.tag_configure(tag, **{opt: False})
|
||||
self.assertIs(getboolean(text.tag_cget(tag, opt)), False)
|
||||
|
||||
# Screen-distance (pixel) options.
|
||||
for opt in ('borderwidth', 'lmargin1', 'lmargin2', 'rmargin',
|
||||
'spacing1', 'spacing2', 'spacing3'):
|
||||
text.tag_configure(tag, **{opt: 7})
|
||||
self.assertEqual(getint(text.tag_cget(tag, opt)), 7)
|
||||
|
||||
# Other options.
|
||||
text.tag_configure(tag, font='Helvetica 12')
|
||||
self.assertEqual(str(text.tag_cget(tag, 'font')), 'Helvetica 12')
|
||||
text.tag_configure(tag, tabs=(10.2, '1i'))
|
||||
self.assertEqual([str(x) for x in text.tag_ranges('sel')], [])
|
||||
|
||||
self.assertRaises(TclError, text.tag_cget, tag, 'spam')
|
||||
|
||||
@requires_tk(8, 6, 6)
|
||||
def test_tag_configure_colors(self):
|
||||
# Tag color options added in Tk 8.6.6.
|
||||
text = self.text
|
||||
tag = 'a'
|
||||
for opt in ('selectforeground', 'selectbackground',
|
||||
'lmargincolor', 'rmargincolor',
|
||||
'underlinefg', 'overstrikefg'):
|
||||
text.tag_configure(tag, **{opt: '#00ff00'})
|
||||
self.assertEqual(str(text.tag_cget(tag, opt)), '#00ff00')
|
||||
|
||||
def test_tag_configure_query(self):
|
||||
text = self.text
|
||||
tag = 'a'
|
||||
# Querying all options returns a dict keyed by option name.
|
||||
cnf = text.tag_configure(tag)
|
||||
self.assertIsInstance(cnf, dict)
|
||||
self.assertIn('foreground', cnf)
|
||||
# The value is the full 5-tuple option specification.
|
||||
self.assertEqual(len(cnf['foreground']), 5)
|
||||
|
||||
# Querying a single option returns its specification.
|
||||
spec = text.tag_configure(tag, 'foreground')
|
||||
self.assertEqual(spec[0], 'foreground')
|
||||
self.assertEqual(spec[-1], '') # unset by default
|
||||
|
||||
# Setting via keyword arguments and via a dict are equivalent.
|
||||
text.tag_configure(tag, foreground='red')
|
||||
self.assertEqual(str(text.tag_configure(tag, 'foreground')[-1]), 'red')
|
||||
text.tag_configure(tag, {'foreground': 'blue'})
|
||||
self.assertEqual(str(text.tag_cget(tag, 'foreground')), 'blue')
|
||||
|
||||
# tag_config is an alias of tag_configure.
|
||||
self.assertEqual(text.tag_config, text.tag_configure)
|
||||
|
||||
def test_image_configure(self):
|
||||
text = self.text
|
||||
image = tkinter.PhotoImage(master=self.root, width=10, height=10)
|
||||
text.insert('1.0', 'AB')
|
||||
name = text.image_create('1.1', image=image, name='img')
|
||||
self.assertEqual(name, 'img')
|
||||
|
||||
self.assertEqual(str(text.image_cget(name, 'name')), 'img')
|
||||
self.assertEqual(str(text.image_cget(name, 'image')), str(image))
|
||||
for value in ('top', 'center', 'bottom', 'baseline'):
|
||||
text.image_configure(name, align=value)
|
||||
self.assertEqual(str(text.image_cget(name, 'align')), value)
|
||||
text.image_configure(name, padx=3, pady=4)
|
||||
self.assertEqual(text.tk.getint(text.image_cget(name, 'padx')), 3)
|
||||
self.assertEqual(text.tk.getint(text.image_cget(name, 'pady')), 4)
|
||||
|
||||
# Querying returns the full option set.
|
||||
cnf = text.image_configure(name)
|
||||
self.assertIsInstance(cnf, dict)
|
||||
self.assertIn('align', cnf)
|
||||
self.assertRaises(TclError, text.image_cget, name, 'spam')
|
||||
|
||||
def test_window_configure(self):
|
||||
text = self.text
|
||||
button = tkinter.Button(text, text='ok')
|
||||
text.insert('1.0', 'AB')
|
||||
text.window_create('1.1', window=button)
|
||||
|
||||
self.assertEqual(text.window_cget('1.1', 'window'), str(button))
|
||||
for value in ('top', 'center', 'bottom', 'baseline'):
|
||||
text.window_configure('1.1', align=value)
|
||||
self.assertEqual(str(text.window_cget('1.1', 'align')), value)
|
||||
text.window_configure('1.1', padx=3, pady=4)
|
||||
self.assertEqual(text.tk.getint(text.window_cget('1.1', 'padx')), 3)
|
||||
self.assertEqual(text.tk.getint(text.window_cget('1.1', 'pady')), 4)
|
||||
text.window_configure('1.1', stretch=True)
|
||||
self.assertIs(text.tk.getboolean(text.window_cget('1.1', 'stretch')),
|
||||
True)
|
||||
|
||||
cnf = text.window_configure('1.1')
|
||||
self.assertIsInstance(cnf, dict)
|
||||
self.assertIn('stretch', cnf)
|
||||
self.assertRaises(TclError, text.window_cget, '1.1', 'spam')
|
||||
self.assertEqual(text.window_config, text.window_configure)
|
||||
button.destroy()
|
||||
|
||||
def test_peer(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'Lorem ipsum')
|
||||
self.assertEqual(text.peer_names(), ())
|
||||
|
||||
text.peer_create('.peer1')
|
||||
names = self.root.splitlist(text.tk.call('winfo', 'children', '.'))
|
||||
self.assertIn('.peer1', [str(n) for n in names])
|
||||
self.assertEqual([str(p) for p in text.peer_names()], ['.peer1'])
|
||||
# Peers share content.
|
||||
self.assertEqual(text.tk.call('.peer1', 'get', '1.0', 'end'),
|
||||
'Lorem ipsum\n')
|
||||
text.tk.call('destroy', '.peer1')
|
||||
|
||||
def test_bbox(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'hello')
|
||||
text.update()
|
||||
bbox = text.bbox('1.0')
|
||||
self.assertIsInstance(bbox, tuple)
|
||||
self.assertEqual(len(bbox), 4)
|
||||
for v in bbox:
|
||||
self.assertIsInstance(v, int)
|
||||
# A character that is not displayed has no bounding box.
|
||||
self.assertIsNone(text.bbox('end'))
|
||||
self.assertRaises(TclError, text.bbox, 'invalid')
|
||||
self.assertRaises(TypeError, text.bbox)
|
||||
self.assertRaises(TypeError, text.bbox, '1.0', '2.0')
|
||||
|
||||
def test_dlineinfo(self):
|
||||
text = self.text
|
||||
text.insert('1.0', 'hello\nworld')
|
||||
text.update()
|
||||
info = text.dlineinfo('1.0')
|
||||
self.assertIsInstance(info, tuple)
|
||||
self.assertEqual(len(info), 5)
|
||||
for v in info:
|
||||
self.assertIsInstance(v, int)
|
||||
self.assertRaises(TclError, text.dlineinfo, 'invalid')
|
||||
self.assertRaises(TypeError, text.dlineinfo)
|
||||
self.assertRaises(TypeError, text.dlineinfo, '1.0', '2.0')
|
||||
|
||||
def test_see(self):
|
||||
text = self.text
|
||||
text.insert('1.0', '\n'.join('line %d' % i for i in range(200)))
|
||||
text.update()
|
||||
# Initially the last line is not visible.
|
||||
self.assertIsNone(text.bbox('200.0'))
|
||||
text.see('200.0')
|
||||
text.update()
|
||||
self.assertIsNotNone(text.bbox('200.0'))
|
||||
self.assertRaises(TclError, text.see, 'invalid')
|
||||
self.assertRaises(TypeError, text.see)
|
||||
self.assertRaises(TypeError, text.see, '1.0', '2.0')
|
||||
|
||||
# yview_pickplace is a deprecated way to make an index visible.
|
||||
text.yview_pickplace('1.0')
|
||||
text.update()
|
||||
self.assertIsNotNone(text.bbox('1.0'))
|
||||
|
||||
def test_search(self):
|
||||
text = self.text
|
||||
|
||||
# pattern and index are obligatory arguments.
|
||||
self.assertRaises(TclError, text.search, None, '1.0')
|
||||
self.assertRaises(TclError, text.search, 'a', None)
|
||||
self.assertRaises(TclError, text.search, None, None)
|
||||
|
||||
# Invalid text index.
|
||||
self.assertRaises(TclError, text.search, '', 0)
|
||||
self.assertRaises(TclError, text.search, '', '')
|
||||
self.assertRaises(TclError, text.search, '', 'invalid')
|
||||
self.assertRaises(TclError, text.search, '', '1.0', 0)
|
||||
self.assertRaises(TclError, text.search, '', '1.0', '')
|
||||
self.assertRaises(TclError, text.search, '', '1.0', 'invalid')
|
||||
|
||||
text.insert('1.0',
|
||||
'This is a test. This is only a test.\n'
|
||||
'Another line.\n'
|
||||
'Yet another line.\n'
|
||||
'64-bit')
|
||||
|
||||
self.assertEqual(text.search('test', '1.0'), '1.10')
|
||||
self.assertEqual(text.search('test', '1.0', 'end'), '1.10')
|
||||
self.assertEqual(text.search('test', '1.0', '1.10'), '')
|
||||
self.assertEqual(text.search('test', '1.11'), '1.31')
|
||||
self.assertEqual(text.search('test', '1.32', 'end'), '')
|
||||
self.assertEqual(text.search('test', '1.32'), '1.10')
|
||||
|
||||
self.assertEqual(text.search('', '1.0'), '1.0') # empty pattern
|
||||
self.assertEqual(text.search('nonexistent', '1.0'), '')
|
||||
self.assertEqual(text.search('-bit', '1.0'), '4.2') # starts with a hyphen
|
||||
|
||||
self.assertEqual(text.search('line', '3.0'), '3.12')
|
||||
self.assertEqual(text.search('line', '3.0', forwards=True), '3.12')
|
||||
self.assertEqual(text.search('line', '3.0', backwards=True), '2.8')
|
||||
self.assertEqual(text.search('line', '3.0', forwards=True, backwards=True), '2.8')
|
||||
|
||||
self.assertEqual(text.search('t.', '1.0'), '1.13')
|
||||
self.assertEqual(text.search('t.', '1.0', exact=True), '1.13')
|
||||
self.assertEqual(text.search('t.', '1.0', regexp=True), '1.10')
|
||||
self.assertEqual(text.search('t.', '1.0', exact=True, regexp=True), '1.10')
|
||||
|
||||
self.assertEqual(text.search('TEST', '1.0'), '')
|
||||
self.assertEqual(text.search('TEST', '1.0', nocase=True), '1.10')
|
||||
|
||||
self.assertEqual(text.search('.*line', '1.0', regexp=True), '2.0')
|
||||
self.assertEqual(text.search('.*line', '1.0', regexp=True, nolinestop=True), '1.0')
|
||||
|
||||
self.assertEqual(text.search('test', '1.0', '1.13'), '1.10')
|
||||
self.assertEqual(text.search('test', '1.0', '1.13', strictlimits=True), '')
|
||||
self.assertEqual(text.search('test', '1.0', '1.14', strictlimits=True), '1.10')
|
||||
|
||||
var = tkinter.Variable(self.root)
|
||||
self.assertEqual(text.search('test', '1.0', count=var), '1.10')
|
||||
self.assertEqual(var.get(), 4 if self.wantobjects else '4')
|
||||
|
||||
# TODO: Add test for elide=True
|
||||
|
||||
def test_search_all(self):
|
||||
text = self.text
|
||||
|
||||
# pattern and index are obligatory arguments.
|
||||
self.assertRaises(TclError, text.search_all, None, '1.0')
|
||||
self.assertRaises(TclError, text.search_all, 'a', None)
|
||||
self.assertRaises(TclError, text.search_all, None, None)
|
||||
|
||||
# Keyword-only arguments
|
||||
self.assertRaises(TypeError, text.search_all, 'a', '1.0', 'end', None)
|
||||
|
||||
# Invalid text index.
|
||||
self.assertRaises(TclError, text.search_all, '', 0)
|
||||
self.assertRaises(TclError, text.search_all, '', '')
|
||||
self.assertRaises(TclError, text.search_all, '', 'invalid')
|
||||
self.assertRaises(TclError, text.search_all, '', '1.0', 0)
|
||||
self.assertRaises(TclError, text.search_all, '', '1.0', '')
|
||||
self.assertRaises(TclError, text.search_all, '', '1.0', 'invalid')
|
||||
|
||||
def eq(res, expected):
|
||||
self.assertIsInstance(res, tuple)
|
||||
self.assertEqual([str(i) for i in res], expected)
|
||||
|
||||
text.insert('1.0', 'ababa\naba\n64-bit')
|
||||
|
||||
eq(text.search_all('aba', '1.0'), ['1.0', '2.0'])
|
||||
eq(text.search_all('aba', '1.0', 'end'), ['1.0', '2.0'])
|
||||
eq(text.search_all('aba', '1.1', 'end'), ['1.2', '2.0'])
|
||||
eq(text.search_all('aba', '1.1'), ['1.2', '2.0', '1.0'])
|
||||
|
||||
res = text.search_all('', '1.0') # empty pattern
|
||||
eq(res[:5], ['1.0', '1.1', '1.2', '1.3', '1.4'])
|
||||
eq(res[-5:], ['3.2', '3.3', '3.4', '3.5', '3.6'])
|
||||
eq(text.search_all('nonexistent', '1.0'), [])
|
||||
eq(text.search_all('-bit', '1.0'), ['3.2']) # starts with a hyphen
|
||||
|
||||
eq(text.search_all('aba', '1.0', 'end', forwards=True), ['1.0', '2.0'])
|
||||
eq(text.search_all('aba', 'end', '1.0', backwards=True), ['2.0', '1.2'])
|
||||
|
||||
eq(text.search_all('aba', '1.0', overlap=True), ['1.0', '1.2', '2.0'])
|
||||
eq(text.search_all('aba', 'end', '1.0', overlap=True, backwards=True), ['2.0', '1.2', '1.0'])
|
||||
|
||||
eq(text.search_all('aba', '1.0', exact=True), ['1.0', '2.0'])
|
||||
eq(text.search_all('a.a', '1.0', exact=True), [])
|
||||
eq(text.search_all('a.a', '1.0', regexp=True), ['1.0', '2.0'])
|
||||
|
||||
eq(text.search_all('ABA', '1.0'), [])
|
||||
eq(text.search_all('ABA', '1.0', nocase=True), ['1.0', '2.0'])
|
||||
|
||||
eq(text.search_all('a.a', '1.0', regexp=True), ['1.0', '2.0'])
|
||||
eq(text.search_all('a.a', '1.0', regexp=True, nolinestop=True), ['1.0', '1.4'])
|
||||
|
||||
eq(text.search_all('aba', '1.0', '2.2'), ['1.0', '2.0'])
|
||||
eq(text.search_all('aba', '1.0', '2.2', strictlimits=True), ['1.0'])
|
||||
eq(text.search_all('aba', '1.0', '2.3', strictlimits=True), ['1.0', '2.0'])
|
||||
|
||||
var = tkinter.Variable(self.root)
|
||||
eq(text.search_all('aba', '1.0', count=var), ['1.0', '2.0'])
|
||||
self.assertEqual(var.get(), (3, 3) if self.wantobjects else '3 3')
|
||||
|
||||
# TODO: Add test for elide=True
|
||||
|
||||
def test_count(self):
|
||||
text = self.text
|
||||
text.insert('1.0',
|
||||
'Lorem ipsum dolor sit amet,\n'
|
||||
'consectetur adipiscing elit,\n'
|
||||
'sed do eiusmod tempor incididunt\n'
|
||||
'ut labore et dolore magna aliqua.')
|
||||
|
||||
options = ('chars', 'indices', 'lines',
|
||||
'displaychars', 'displayindices', 'displaylines',
|
||||
'xpixels', 'ypixels')
|
||||
self.assertEqual(len(text.count('1.0', 'end', *options, return_ints=True)), 8)
|
||||
self.assertEqual(len(text.count('1.0', 'end', *options)), 8)
|
||||
self.assertEqual(text.count('1.0', 'end', 'chars', 'lines', return_ints=True),
|
||||
(124, 4))
|
||||
self.assertEqual(text.count('1.3', '4.5', 'chars', 'lines'), (92, 3))
|
||||
self.assertEqual(text.count('4.5', '1.3', 'chars', 'lines', return_ints=True),
|
||||
(-92, -3))
|
||||
self.assertEqual(text.count('4.5', '1.3', 'chars', 'lines'), (-92, -3))
|
||||
self.assertEqual(text.count('1.3', '1.3', 'chars', 'lines', return_ints=True),
|
||||
(0, 0))
|
||||
self.assertEqual(text.count('1.3', '1.3', 'chars', 'lines'), (0, 0))
|
||||
self.assertEqual(text.count('1.0', 'end', 'lines', return_ints=True), 4)
|
||||
self.assertEqual(text.count('1.0', 'end', 'lines'), (4,))
|
||||
self.assertEqual(text.count('end', '1.0', 'lines', return_ints=True), -4)
|
||||
self.assertEqual(text.count('end', '1.0', 'lines'), (-4,))
|
||||
self.assertEqual(text.count('1.3', '1.5', 'lines', return_ints=True), 0)
|
||||
self.assertEqual(text.count('1.3', '1.5', 'lines'), None)
|
||||
self.assertEqual(text.count('1.3', '1.3', 'lines', return_ints=True), 0)
|
||||
self.assertEqual(text.count('1.3', '1.3', 'lines'), None)
|
||||
# Count 'indices' by default.
|
||||
self.assertEqual(text.count('1.0', 'end', return_ints=True), 124)
|
||||
self.assertEqual(text.count('1.0', 'end'), (124,))
|
||||
self.assertEqual(text.count('1.0', 'end', 'indices', return_ints=True), 124)
|
||||
self.assertEqual(text.count('1.0', 'end', 'indices'), (124,))
|
||||
self.assertRaises(TclError, text.count, '1.0', 'end', 'spam')
|
||||
self.assertRaises(TclError, text.count, '1.0', 'end', '-lines')
|
||||
|
||||
self.assertIsInstance(text.count('1.3', '1.5', 'ypixels', return_ints=True), int)
|
||||
self.assertIsInstance(text.count('1.3', '1.5', 'ypixels'), tuple)
|
||||
self.assertIsInstance(text.count('1.3', '1.5', 'update', 'ypixels', return_ints=True), int)
|
||||
self.assertIsInstance(text.count('1.3', '1.5', 'update', 'ypixels'), int)
|
||||
self.assertEqual(text.count('1.3', '1.3', 'update', 'ypixels', return_ints=True), 0)
|
||||
self.assertEqual(text.count('1.3', '1.3', 'update', 'ypixels'), None)
|
||||
self.assertEqual(text.count('1.3', '1.5', 'update', 'indices', return_ints=True), 2)
|
||||
self.assertEqual(text.count('1.3', '1.5', 'update', 'indices'), 2)
|
||||
self.assertEqual(text.count('1.3', '1.3', 'update', 'indices', return_ints=True), 0)
|
||||
self.assertEqual(text.count('1.3', '1.3', 'update', 'indices'), None)
|
||||
self.assertEqual(text.count('1.3', '1.5', 'update', return_ints=True), 2)
|
||||
self.assertEqual(text.count('1.3', '1.5', 'update'), (2,))
|
||||
self.assertEqual(text.count('1.3', '1.3', 'update', return_ints=True), 0)
|
||||
self.assertEqual(text.count('1.3', '1.3', 'update'), None)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# test_tkinter_pipe.py
|
||||
import unittest
|
||||
import subprocess
|
||||
import sys
|
||||
from test import support
|
||||
|
||||
|
||||
@unittest.skipUnless(support.has_subprocess_support, "test requires subprocess")
|
||||
class TkinterPipeTest(unittest.TestCase):
|
||||
|
||||
def test_tkinter_pipe_buffered(self):
|
||||
args = [sys.executable, "-i"]
|
||||
proc = subprocess.Popen(args,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
proc.stdin.write(b"import tkinter\n")
|
||||
proc.stdin.write(b"interpreter = tkinter.Tcl()\n")
|
||||
proc.stdin.write(b"print('hello')\n")
|
||||
proc.stdin.write(b"print('goodbye')\n")
|
||||
proc.stdin.write(b"quit()\n")
|
||||
stdout, stderr = proc.communicate(timeout=support.SHORT_TIMEOUT)
|
||||
stdout = stdout.decode()
|
||||
self.assertEqual(stdout.split(), ['hello', 'goodbye'])
|
||||
|
||||
def test_tkinter_pipe_unbuffered(self):
|
||||
args = [sys.executable, "-i", "-u"]
|
||||
proc = subprocess.Popen(args,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
proc.stdin.write(b"import tkinter\n")
|
||||
proc.stdin.write(b"interpreter = tkinter.Tcl()\n")
|
||||
|
||||
proc.stdin.write(b"print('hello')\n")
|
||||
proc.stdin.flush()
|
||||
stdout = proc.stdout.readline()
|
||||
stdout = stdout.decode()
|
||||
self.assertEqual(stdout.strip(), 'hello')
|
||||
|
||||
proc.stdin.write(b"print('hello again')\n")
|
||||
proc.stdin.flush()
|
||||
stdout = proc.stdout.readline()
|
||||
stdout = stdout.decode()
|
||||
self.assertEqual(stdout.strip(), 'hello again')
|
||||
|
||||
proc.stdin.write(b"print('goodbye')\n")
|
||||
proc.stdin.write(b"quit()\n")
|
||||
stdout, stderr = proc.communicate(timeout=support.SHORT_TIMEOUT)
|
||||
stdout = stdout.decode()
|
||||
self.assertEqual(stdout.strip(), 'goodbye')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
import unittest
|
||||
from test import support
|
||||
|
||||
import gc
|
||||
import tkinter
|
||||
from tkinter import (Variable, StringVar, IntVar, DoubleVar, BooleanVar, Tcl,
|
||||
TclError)
|
||||
from test.support import ALWAYS_EQ
|
||||
from test.test_tkinter.support import setUpModule # noqa: F401
|
||||
from test.test_tkinter.support import AbstractDefaultRootTest, tcl_version
|
||||
|
||||
|
||||
class Var(Variable):
|
||||
|
||||
_default = "default"
|
||||
side_effect = False
|
||||
|
||||
def set(self, value):
|
||||
self.side_effect = True
|
||||
super().set(value)
|
||||
|
||||
|
||||
class TestBase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.root = Tcl()
|
||||
|
||||
def tearDown(self):
|
||||
del self.root
|
||||
|
||||
|
||||
class TestVariable(TestBase):
|
||||
|
||||
def info_exists(self, *args):
|
||||
return self.root.getboolean(self.root.call("info", "exists", *args))
|
||||
|
||||
def test_default(self):
|
||||
v = Variable(self.root)
|
||||
self.assertEqual("", v.get())
|
||||
self.assertRegex(str(v), r"^PY_VAR(\d+)$")
|
||||
|
||||
def test_name_and_value(self):
|
||||
v = Variable(self.root, "sample string", "varname")
|
||||
self.assertEqual("sample string", v.get())
|
||||
self.assertEqual("varname", str(v))
|
||||
|
||||
def test___del__(self):
|
||||
self.assertFalse(self.info_exists("varname"))
|
||||
v = Variable(self.root, "sample string", "varname")
|
||||
self.assertTrue(self.info_exists("varname"))
|
||||
del v
|
||||
support.gc_collect() # For PyPy or other GCs.
|
||||
self.assertFalse(self.info_exists("varname"))
|
||||
|
||||
def test_dont_unset_not_existing(self):
|
||||
self.assertFalse(self.info_exists("varname"))
|
||||
v1 = Variable(self.root, name="name")
|
||||
v2 = Variable(self.root, name="name")
|
||||
del v1
|
||||
support.gc_collect() # For PyPy or other GCs.
|
||||
self.assertFalse(self.info_exists("name"))
|
||||
# shouldn't raise exception
|
||||
del v2
|
||||
support.gc_collect() # For PyPy or other GCs.
|
||||
self.assertFalse(self.info_exists("name"))
|
||||
|
||||
def test_equality(self):
|
||||
# values doesn't matter, only class and name are checked
|
||||
v1 = Variable(self.root, name="abc")
|
||||
v2 = Variable(self.root, name="abc")
|
||||
self.assertIsNot(v1, v2)
|
||||
self.assertEqual(v1, v2)
|
||||
|
||||
v3 = Variable(self.root, name="cba")
|
||||
self.assertNotEqual(v1, v3)
|
||||
|
||||
v4 = StringVar(self.root, name="abc")
|
||||
self.assertEqual(str(v1), str(v4))
|
||||
self.assertNotEqual(v1, v4)
|
||||
|
||||
V = type('Variable', (), {})
|
||||
self.assertNotEqual(v1, V())
|
||||
|
||||
self.assertNotEqual(v1, object())
|
||||
self.assertEqual(v1, ALWAYS_EQ)
|
||||
|
||||
root2 = tkinter.Tk()
|
||||
self.addCleanup(root2.destroy)
|
||||
v5 = Variable(root2, name="abc")
|
||||
self.assertEqual(str(v1), str(v5))
|
||||
self.assertNotEqual(v1, v5)
|
||||
|
||||
def test_invalid_name(self):
|
||||
with self.assertRaises(TypeError):
|
||||
Variable(self.root, name=123)
|
||||
|
||||
def test_null_in_name(self):
|
||||
with self.assertRaises(ValueError):
|
||||
Variable(self.root, name='var\x00name')
|
||||
with self.assertRaises(ValueError):
|
||||
self.root.globalsetvar('var\x00name', "value")
|
||||
with self.assertRaises(ValueError):
|
||||
self.root.globalsetvar(b'var\x00name', "value")
|
||||
with self.assertRaises(ValueError):
|
||||
self.root.setvar('var\x00name', "value")
|
||||
with self.assertRaises(ValueError):
|
||||
self.root.setvar(b'var\x00name', "value")
|
||||
|
||||
def test_initialize(self):
|
||||
v = Var(self.root)
|
||||
self.assertFalse(v.side_effect)
|
||||
v.set("value")
|
||||
self.assertTrue(v.side_effect)
|
||||
self.assertEqual(Variable.initialize, Variable.set)
|
||||
|
||||
def test_trace_old(self):
|
||||
if tcl_version >= (9, 0):
|
||||
self.skipTest('requires Tcl version < 9.0')
|
||||
# Old interface
|
||||
v = Variable(self.root)
|
||||
vname = str(v)
|
||||
self.assertEqual(v.trace, v.trace_variable)
|
||||
trace = []
|
||||
def read_tracer(*args):
|
||||
trace.append(('read',) + args)
|
||||
def write_tracer(*args):
|
||||
trace.append(('write',) + args)
|
||||
with self.assertWarns(DeprecationWarning) as cm:
|
||||
cb1 = v.trace_variable('r', read_tracer)
|
||||
self.assertEqual(cm.filename, __file__)
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
cb2 = v.trace_variable('wu', write_tracer)
|
||||
with self.assertWarns(DeprecationWarning) as cm:
|
||||
self.assertEqual(sorted(v.trace_vinfo()), [('r', cb1), ('wu', cb2)])
|
||||
self.assertEqual(cm.filename, __file__)
|
||||
self.assertEqual(trace, [])
|
||||
|
||||
v.set('spam')
|
||||
self.assertEqual(trace, [('write', vname, '', 'w')])
|
||||
|
||||
trace = []
|
||||
v.get()
|
||||
self.assertEqual(trace, [('read', vname, '', 'r')])
|
||||
|
||||
trace = []
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
info = sorted(v.trace_vinfo())
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
v.trace_vdelete('w', cb1) # Wrong mode
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
self.assertEqual(sorted(v.trace_vinfo()), info)
|
||||
with self.assertRaises(TclError):
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
v.trace_vdelete('r', 'spam') # Wrong command name
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
self.assertEqual(sorted(v.trace_vinfo()), info)
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
v.trace_vdelete('r', (cb1, 43)) # Wrong arguments
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
self.assertEqual(sorted(v.trace_vinfo()), info)
|
||||
v.get()
|
||||
self.assertEqual(trace, [('read', vname, '', 'r')])
|
||||
|
||||
trace = []
|
||||
with self.assertWarns(DeprecationWarning) as cm:
|
||||
v.trace_vdelete('r', cb1)
|
||||
self.assertEqual(cm.filename, __file__)
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
self.assertEqual(v.trace_vinfo(), [('wu', cb2)])
|
||||
v.get()
|
||||
self.assertEqual(trace, [])
|
||||
|
||||
trace = []
|
||||
del write_tracer
|
||||
gc.collect()
|
||||
v.set('eggs')
|
||||
self.assertEqual(trace, [('write', vname, '', 'w')])
|
||||
|
||||
trace = []
|
||||
del v
|
||||
gc.collect()
|
||||
self.assertEqual(trace, [('write', vname, '', 'u')])
|
||||
|
||||
def test_trace(self):
|
||||
v = Variable(self.root)
|
||||
vname = str(v)
|
||||
trace = []
|
||||
def read_tracer(*args):
|
||||
trace.append(('read',) + args)
|
||||
def write_tracer(*args):
|
||||
trace.append(('write',) + args)
|
||||
tr1 = v.trace_add('read', read_tracer)
|
||||
tr2 = v.trace_add(['write', 'unset'], write_tracer)
|
||||
self.assertEqual(sorted(v.trace_info()), [
|
||||
(('read',), tr1),
|
||||
(('write', 'unset'), tr2)])
|
||||
self.assertEqual(trace, [])
|
||||
|
||||
v.set('spam')
|
||||
self.assertEqual(trace, [('write', vname, '', 'write')])
|
||||
|
||||
trace = []
|
||||
v.get()
|
||||
self.assertEqual(trace, [('read', vname, '', 'read')])
|
||||
|
||||
trace = []
|
||||
info = sorted(v.trace_info())
|
||||
v.trace_remove('write', tr1) # Wrong mode
|
||||
self.assertEqual(sorted(v.trace_info()), info)
|
||||
with self.assertRaises(TclError):
|
||||
v.trace_remove('read', 'spam') # Wrong command name
|
||||
self.assertEqual(sorted(v.trace_info()), info)
|
||||
v.get()
|
||||
self.assertEqual(trace, [('read', vname, '', 'read')])
|
||||
|
||||
trace = []
|
||||
v.trace_remove('read', tr1)
|
||||
self.assertEqual(v.trace_info(), [(('write', 'unset'), tr2)])
|
||||
v.get()
|
||||
self.assertEqual(trace, [])
|
||||
|
||||
trace = []
|
||||
del write_tracer
|
||||
gc.collect()
|
||||
v.set('eggs')
|
||||
self.assertEqual(trace, [('write', vname, '', 'write')])
|
||||
|
||||
trace = []
|
||||
del v
|
||||
gc.collect()
|
||||
self.assertEqual(trace, [('write', vname, '', 'unset')])
|
||||
|
||||
|
||||
class TestStringVar(TestBase):
|
||||
|
||||
def test_default(self):
|
||||
v = StringVar(self.root)
|
||||
self.assertEqual("", v.get())
|
||||
|
||||
def test_get(self):
|
||||
v = StringVar(self.root, "abc", "name")
|
||||
self.assertEqual("abc", v.get())
|
||||
self.root.globalsetvar("name", "value")
|
||||
self.assertEqual("value", v.get())
|
||||
|
||||
def test_get_null(self):
|
||||
v = StringVar(self.root, "abc\x00def", "name")
|
||||
self.assertEqual("abc\x00def", v.get())
|
||||
self.root.globalsetvar("name", "val\x00ue")
|
||||
self.assertEqual("val\x00ue", v.get())
|
||||
|
||||
|
||||
class TestIntVar(TestBase):
|
||||
|
||||
def test_default(self):
|
||||
v = IntVar(self.root)
|
||||
self.assertEqual(0, v.get())
|
||||
|
||||
def test_get(self):
|
||||
v = IntVar(self.root, 123, "name")
|
||||
self.assertEqual(123, v.get())
|
||||
self.root.globalsetvar("name", "345")
|
||||
self.assertEqual(345, v.get())
|
||||
self.root.globalsetvar("name", "876.5")
|
||||
self.assertEqual(876, v.get())
|
||||
|
||||
def test_invalid_value(self):
|
||||
v = IntVar(self.root, name="name")
|
||||
self.root.globalsetvar("name", "value")
|
||||
with self.assertRaises((ValueError, TclError)):
|
||||
v.get()
|
||||
|
||||
|
||||
class TestDoubleVar(TestBase):
|
||||
|
||||
def test_default(self):
|
||||
v = DoubleVar(self.root)
|
||||
self.assertEqual(0.0, v.get())
|
||||
|
||||
def test_get(self):
|
||||
v = DoubleVar(self.root, 1.23, "name")
|
||||
self.assertAlmostEqual(1.23, v.get())
|
||||
self.root.globalsetvar("name", "3.45")
|
||||
self.assertAlmostEqual(3.45, v.get())
|
||||
|
||||
def test_get_from_int(self):
|
||||
v = DoubleVar(self.root, 1.23, "name")
|
||||
self.assertAlmostEqual(1.23, v.get())
|
||||
self.root.globalsetvar("name", "3.45")
|
||||
self.assertAlmostEqual(3.45, v.get())
|
||||
self.root.globalsetvar("name", "456")
|
||||
self.assertAlmostEqual(456, v.get())
|
||||
|
||||
def test_invalid_value(self):
|
||||
v = DoubleVar(self.root, name="name")
|
||||
self.root.globalsetvar("name", "value")
|
||||
with self.assertRaises((ValueError, TclError)):
|
||||
v.get()
|
||||
|
||||
|
||||
class TestBooleanVar(TestBase):
|
||||
|
||||
def test_default(self):
|
||||
v = BooleanVar(self.root)
|
||||
self.assertIs(v.get(), False)
|
||||
|
||||
def test_get(self):
|
||||
v = BooleanVar(self.root, True, "name")
|
||||
self.assertIs(v.get(), True)
|
||||
self.root.globalsetvar("name", "0")
|
||||
self.assertIs(v.get(), False)
|
||||
self.root.globalsetvar("name", 42 if self.root.wantobjects() else 1)
|
||||
self.assertIs(v.get(), True)
|
||||
self.root.globalsetvar("name", 0)
|
||||
self.assertIs(v.get(), False)
|
||||
self.root.globalsetvar("name", "on")
|
||||
self.assertIs(v.get(), True)
|
||||
|
||||
def test_set(self):
|
||||
true = 1 if self.root.wantobjects() else "1"
|
||||
false = 0 if self.root.wantobjects() else "0"
|
||||
v = BooleanVar(self.root, name="name")
|
||||
v.set(True)
|
||||
self.assertEqual(self.root.globalgetvar("name"), true)
|
||||
v.set("0")
|
||||
self.assertEqual(self.root.globalgetvar("name"), false)
|
||||
v.set(42)
|
||||
self.assertEqual(self.root.globalgetvar("name"), true)
|
||||
v.set(0)
|
||||
self.assertEqual(self.root.globalgetvar("name"), false)
|
||||
v.set("on")
|
||||
self.assertEqual(self.root.globalgetvar("name"), true)
|
||||
self.assertEqual(BooleanVar.initialize, BooleanVar.set)
|
||||
|
||||
def test_invalid_value_domain(self):
|
||||
false = 0 if self.root.wantobjects() else "0"
|
||||
v = BooleanVar(self.root, name="name")
|
||||
with self.assertRaises(TclError):
|
||||
v.set("value")
|
||||
self.assertEqual(self.root.globalgetvar("name"), false)
|
||||
self.root.globalsetvar("name", "value")
|
||||
with self.assertRaises(ValueError):
|
||||
v.get()
|
||||
self.root.globalsetvar("name", "1.0")
|
||||
with self.assertRaises(ValueError):
|
||||
v.get()
|
||||
|
||||
|
||||
class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
|
||||
|
||||
def test_variable(self):
|
||||
self.assertRaises(RuntimeError, Variable)
|
||||
root = tkinter.Tk()
|
||||
v = Variable()
|
||||
v.set("value")
|
||||
self.assertEqual(v.get(), "value")
|
||||
root.destroy()
|
||||
tkinter.NoDefaultRoot()
|
||||
self.assertRaises(RuntimeError, Variable)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+2644
File diff suppressed because it is too large.
Load diff
+628
@@ -0,0 +1,628 @@
|
||||
# Common tests for test_tkinter/test_widgets.py and test_ttk/test_widgets.py
|
||||
|
||||
import re
|
||||
import tkinter
|
||||
from test.test_tkinter.support import (AbstractTkTest, requires_tk, tk_version,
|
||||
pixels_conv, tcl_obj_eq)
|
||||
import test.support
|
||||
|
||||
_sentinel = object()
|
||||
|
||||
# Abbreviated option names accepted by Tk in addition to the full names.
|
||||
_OPTION_ALIASES = {
|
||||
'bd': 'borderwidth',
|
||||
'bg': 'background',
|
||||
'bgimg': 'backgroundimage',
|
||||
'fg': 'foreground',
|
||||
'invcmd': 'invalidcommand',
|
||||
'vcmd': 'validatecommand',
|
||||
}
|
||||
|
||||
# Options which accept all values allowed by Tk_GetPixels
|
||||
# borderwidth = bd
|
||||
|
||||
class AbstractWidgetTest(AbstractTkTest):
|
||||
_default_pixels = '' if tk_version >= (9, 0) else -1 # Value for unset pixel options.
|
||||
_rounds_pixels = (tk_version < (9, 0)) # True if some pixel options are rounded.
|
||||
_no_round = set() # Pixel options which are not rounded nonetheless
|
||||
_stringify = False # Whether to convert tuples to strings
|
||||
_allow_empty_justify = False
|
||||
_clipped_to_default = set()
|
||||
|
||||
@property
|
||||
def scaling(self):
|
||||
try:
|
||||
return self._scaling
|
||||
except AttributeError:
|
||||
self._scaling = float(self.root.call('tk', 'scaling'))
|
||||
return self._scaling
|
||||
|
||||
def _str(self, value):
|
||||
if not self._stringify and self.wantobjects and tk_version >= (8, 6):
|
||||
return value
|
||||
if isinstance(value, tuple):
|
||||
return ' '.join(map(self._str, value))
|
||||
return str(value)
|
||||
|
||||
def assertEqual2(self, actual, expected, msg=None, eq=object.__eq__):
|
||||
if eq(actual, expected):
|
||||
return
|
||||
self.assertEqual(actual, expected, msg)
|
||||
|
||||
def checkParam(self, widget, name, value, *, expected=_sentinel,
|
||||
conv=False, eq=None):
|
||||
widget[name] = value
|
||||
if expected is _sentinel:
|
||||
expected = value
|
||||
if name in self._clipped:
|
||||
if not isinstance(expected, str) and expected < 0:
|
||||
if tk_version >= (8, 7) and name in self._clipped_to_default:
|
||||
expected = self._default_pixels
|
||||
else:
|
||||
expected = 0
|
||||
if conv:
|
||||
expected = conv(expected)
|
||||
if self._stringify or not self.wantobjects:
|
||||
if isinstance(expected, tuple):
|
||||
expected = tkinter._join(expected)
|
||||
else:
|
||||
expected = str(expected)
|
||||
if eq is None:
|
||||
eq = tcl_obj_eq
|
||||
self.assertEqual2(widget[name], expected, eq=eq)
|
||||
self.assertEqual2(widget.cget(name), expected, eq=eq)
|
||||
t = widget.configure(name)
|
||||
self.assertEqual(len(t), 5)
|
||||
self.assertEqual2(t[4], expected, eq=eq)
|
||||
|
||||
def checkInvalidParam(self, widget, name, value, errmsg=None):
|
||||
orig = widget[name]
|
||||
if errmsg is not None:
|
||||
errmsg = errmsg.format(re.escape(str(value)))
|
||||
errmsg = fr'\A{errmsg}\z'
|
||||
with self.assertRaisesRegex(tkinter.TclError, errmsg or ''):
|
||||
widget[name] = value
|
||||
self.assertEqual(widget[name], orig)
|
||||
with self.assertRaisesRegex(tkinter.TclError, errmsg or ''):
|
||||
widget.configure({name: value})
|
||||
self.assertEqual(widget[name], orig)
|
||||
|
||||
def checkParams(self, widget, name, *values, **kwargs):
|
||||
for value in values:
|
||||
self.checkParam(widget, name, value, **kwargs)
|
||||
|
||||
def checkIntegerParam(self, widget, name, *values, **kwargs):
|
||||
self.checkParams(widget, name, *values, **kwargs)
|
||||
errmsg = 'expected integer but got "{}"'
|
||||
self.checkInvalidParam(widget, name, '', errmsg=errmsg)
|
||||
self.checkInvalidParam(widget, name, '10p', errmsg=errmsg)
|
||||
self.checkInvalidParam(widget, name, 3.2, errmsg=errmsg)
|
||||
|
||||
def checkFloatParam(self, widget, name, *values, conv=float, **kwargs):
|
||||
for value in values:
|
||||
self.checkParam(widget, name, value, conv=conv, **kwargs)
|
||||
errmsg = 'expected floating-point number but got "{}"'
|
||||
self.checkInvalidParam(widget, name, '', errmsg=errmsg)
|
||||
self.checkInvalidParam(widget, name, 'spam', errmsg=errmsg)
|
||||
|
||||
def checkBooleanParam(self, widget, name):
|
||||
for value in (False, 0, 'false', 'no', 'off'):
|
||||
self.checkParam(widget, name, value, expected=0)
|
||||
for value in (True, 1, 'true', 'yes', 'on'):
|
||||
self.checkParam(widget, name, value, expected=1)
|
||||
errmsg = 'expected boolean value but got "{}"'
|
||||
self.checkInvalidParam(widget, name, '', errmsg=errmsg)
|
||||
self.checkInvalidParam(widget, name, 'spam', errmsg=errmsg)
|
||||
|
||||
def checkColorParam(self, widget, name, *, allow_empty=None, **kwargs):
|
||||
self.checkParams(widget, name,
|
||||
'#ff0000', '#00ff00', '#0000ff', '#123456',
|
||||
'red', 'green', 'blue', 'white', 'black', 'grey',
|
||||
**kwargs)
|
||||
self.checkInvalidParam(widget, name, 'spam',
|
||||
errmsg='unknown color name "spam"')
|
||||
|
||||
def checkCursorParam(self, widget, name, **kwargs):
|
||||
self.checkParams(widget, name, 'arrow', 'watch', 'cross', '',**kwargs)
|
||||
self.checkParam(widget, name, 'none')
|
||||
self.checkInvalidParam(widget, name, 'spam',
|
||||
errmsg='bad cursor spec "spam"')
|
||||
|
||||
def checkCommandParam(self, widget, name):
|
||||
def command(*args):
|
||||
pass
|
||||
widget[name] = command
|
||||
self.assertTrue(widget[name])
|
||||
self.checkParams(widget, name, '')
|
||||
|
||||
def checkEnumParam(self, widget, name, *values,
|
||||
errmsg=None, allow_empty=False, fullname=None,
|
||||
sort=False, **kwargs):
|
||||
self.checkParams(widget, name, *values, **kwargs)
|
||||
if errmsg is None:
|
||||
if sort:
|
||||
if values[-1]:
|
||||
values = tuple(sorted(values))
|
||||
else:
|
||||
values = tuple(sorted(values[:-1])) + ('',)
|
||||
errmsg2 = ' %s "{}": must be %s%s or %s' % (
|
||||
fullname or name,
|
||||
', '.join(values[:-1]),
|
||||
',' if len(values) > 2 else '',
|
||||
values[-1] or '""')
|
||||
if '' not in values and not allow_empty:
|
||||
self.checkInvalidParam(widget, name, '',
|
||||
errmsg='ambiguous' + errmsg2)
|
||||
errmsg = 'bad' + errmsg2
|
||||
self.checkInvalidParam(widget, name, 'spam', errmsg=errmsg)
|
||||
|
||||
def checkPixelsParam(self, widget, name, *values, conv=None, **kwargs):
|
||||
if conv is None:
|
||||
if self._rounds_pixels and name not in self._no_round:
|
||||
conv = round
|
||||
alow_neg = tk_version < (9, 1)
|
||||
for value in values:
|
||||
expected = _sentinel
|
||||
conv1 = conv
|
||||
if isinstance(value, str):
|
||||
if not getattr(self, '_converts_pixels', True):
|
||||
conv1 = str
|
||||
if conv1 and conv1 is not str:
|
||||
expected = pixels_conv(value) * self.scaling
|
||||
conv1 = round
|
||||
elif not alow_neg and isinstance(value, (int, float)) and value < 0:
|
||||
self.checkInvalidParam(widget, name, value)
|
||||
continue
|
||||
self.checkParam(widget, name, value, expected=expected,
|
||||
conv=conv1, **kwargs)
|
||||
errmsg = '(bad|expected) screen distance ((or "" )?but got )?"{}"'
|
||||
self.checkInvalidParam(widget, name, '6x', errmsg=errmsg)
|
||||
self.checkInvalidParam(widget, name, 'spam', errmsg=errmsg)
|
||||
|
||||
def checkReliefParam(self, widget, name, *, allow_empty=False):
|
||||
values = ('flat', 'groove', 'raised', 'ridge', 'solid', 'sunken')
|
||||
if allow_empty:
|
||||
values += ('',)
|
||||
self.checkParams(widget, name, *values)
|
||||
errmsg = 'bad relief "{}": must be %s, or %s' % (
|
||||
', '.join(values[:-1]),
|
||||
values[-1] or '""')
|
||||
if tk_version < (8, 6):
|
||||
errmsg = None
|
||||
self.checkInvalidParam(widget, name, 'spam', errmsg=errmsg)
|
||||
|
||||
def checkImageParam(self, widget, name):
|
||||
image = tkinter.PhotoImage(master=self.root, name='image1')
|
||||
self.checkParam(widget, name, image, conv=str)
|
||||
if tk_version < (8, 7):
|
||||
errmsg = 'image "spam" doesn\'t exist'
|
||||
else:
|
||||
errmsg = 'image "spam" does not exist'
|
||||
self.checkInvalidParam(widget, name, 'spam',
|
||||
errmsg=errmsg)
|
||||
widget[name] = ''
|
||||
|
||||
def checkVariableParam(self, widget, name, var):
|
||||
self.checkParam(widget, name, var, conv=str)
|
||||
|
||||
def assertIsBoundingBox(self, bbox):
|
||||
self.assertIsNotNone(bbox)
|
||||
self.assertIsInstance(bbox, tuple)
|
||||
if len(bbox) != 4:
|
||||
self.fail('Invalid bounding box: %r' % (bbox,))
|
||||
for item in bbox:
|
||||
if not isinstance(item, int):
|
||||
self.fail('Invalid bounding box: %r' % (bbox,))
|
||||
break
|
||||
|
||||
|
||||
def test_keys(self):
|
||||
widget = self.create()
|
||||
keys = widget.keys()
|
||||
self.assertEqual(sorted(keys), sorted(widget.configure()))
|
||||
for k in keys:
|
||||
widget[k]
|
||||
# Test if OPTIONS contains all keys
|
||||
if test.support.verbose:
|
||||
keys = set(keys)
|
||||
expected = set(self.OPTIONS)
|
||||
for k in sorted(keys - expected):
|
||||
if not (k in _OPTION_ALIASES and
|
||||
_OPTION_ALIASES[k] in keys and
|
||||
_OPTION_ALIASES[k] in expected):
|
||||
print('%s.OPTIONS doesn\'t contain "%s"' %
|
||||
(self.__class__.__name__, k))
|
||||
|
||||
def test_options_in_docstring(self):
|
||||
# Every option in OPTIONS must be listed in the docstring of the
|
||||
# __init__ method (see gh-78335). Options reported by keys() but
|
||||
# missing from the docstring are only printed in verbose mode, as
|
||||
# some of them depend on the Tk version.
|
||||
widget = self.create()
|
||||
doc = type(widget).__init__.__doc__
|
||||
if doc is None:
|
||||
self.skipTest('docstrings are not available (run with -OO)')
|
||||
# Look at the option list only, not the leading description.
|
||||
start = doc.find('Valid option names')
|
||||
if start < 0:
|
||||
start = doc.find('OPTIONS')
|
||||
if start < 0:
|
||||
self.skipTest('the __init__ docstring does not list options')
|
||||
documented = set(re.findall(r'[a-z][a-z0-9]+', doc[start:]))
|
||||
def is_documented(option):
|
||||
return (option in documented or
|
||||
_OPTION_ALIASES.get(option) in documented)
|
||||
missing = sorted(o for o in self.OPTIONS if not is_documented(o))
|
||||
self.assertEqual(missing, [],
|
||||
'%s options missing from the __init__ docstring: %s'
|
||||
% (type(widget).__name__, missing))
|
||||
if test.support.verbose:
|
||||
for key in sorted(set(widget.keys())):
|
||||
if not is_documented(key):
|
||||
print('%s.__init__ docstring doesn\'t contain "%s"'
|
||||
% (type(widget).__name__, key))
|
||||
|
||||
class PixelOptionsTests:
|
||||
"""Standard options that accept all formats acceptable to Tk_GetPixels.
|
||||
|
||||
In addition to numbers, these options can be set with distances
|
||||
specified as a string consisting of a number followed by a single
|
||||
character giving the unit of distance. The allowed units are:
|
||||
millimeters ('m'), centimeters ('c'), inches ('i') or points ('p').
|
||||
In Tk 9 a cget call for one of these options returns a Tcl_Obj of
|
||||
type "pixels", whose string representation is the distance string
|
||||
passed to configure.
|
||||
"""
|
||||
PIXEL_OPTIONS = ('activeborderwidth', 'borderwidth', 'highlightthickness',
|
||||
'insertborderwidth', 'insertwidth', 'padx', 'pady', 'selectborderwidth')
|
||||
|
||||
def test_configure_activeborderwidth(self):
|
||||
widget = self.create()
|
||||
self.checkPixelsParam(widget, 'activeborderwidth',
|
||||
0, 1.3, 2.9, 6, -2, '10p')
|
||||
|
||||
def test_configure_borderwidth(self):
|
||||
widget = self.create()
|
||||
self.checkPixelsParam(widget, 'borderwidth',
|
||||
0, 1.3, 2.6, 6, -2, '10p')
|
||||
|
||||
if 'bd' in self.OPTIONS:
|
||||
self.checkPixelsParam(widget, 'bd', 0, 1.3, 2.6, 6, '10p')
|
||||
self.checkParam(widget, 'bd', -2, expected=expected)
|
||||
|
||||
def test_configure_highlightthickness(self):
|
||||
widget = self.create()
|
||||
self.checkPixelsParam(widget, 'highlightthickness',
|
||||
0, 1.3, 2.6, 6, -2, '10p')
|
||||
|
||||
def test_configure_insertborderwidth(self):
|
||||
widget = self.create()
|
||||
self.checkPixelsParam(widget, 'insertborderwidth', 0, 1.3, 2.6, 6, -2, '10p')
|
||||
|
||||
def test_configure_insertwidth(self):
|
||||
widget = self.create()
|
||||
self.checkPixelsParam(widget, 'insertwidth', 1.3, 2.6, -2, '10p')
|
||||
|
||||
def test_configure_padx(self):
|
||||
widget = self.create()
|
||||
self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, -2, '12m')
|
||||
|
||||
def test_configure_pady(self):
|
||||
widget = self.create()
|
||||
self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, -2, '12m')
|
||||
|
||||
def test_configure_selectborderwidth(self):
|
||||
widget = self.create()
|
||||
self.checkPixelsParam(widget, 'selectborderwidth', 1.3, 2.6, -2, '10p')
|
||||
|
||||
|
||||
class StandardOptionsTests(PixelOptionsTests):
|
||||
|
||||
STANDARD_OPTIONS = ( 'activebackground', 'activeforeground',
|
||||
'anchor', 'background', 'bitmap', 'compound', 'cursor',
|
||||
'disabledforeground', 'exportselection', 'font', 'foreground',
|
||||
'highlightbackground', 'highlightcolor', 'image',
|
||||
'insertbackground', 'insertofftime', 'insertontime', 'jump',
|
||||
'justify', 'orient', 'relief', 'repeatdelay', 'repeatinterval',
|
||||
'selectbackground', 'selectforeground', 'setgrid', 'takefocus',
|
||||
'text', 'textvariable', 'troughcolor', 'underline', 'wraplength',
|
||||
'xscrollcommand', 'yscrollcommand', ) + PixelOptionsTests.PIXEL_OPTIONS
|
||||
|
||||
def test_configure_activebackground(self):
|
||||
widget = self.create()
|
||||
self.checkColorParam(widget, 'activebackground')
|
||||
|
||||
def test_configure_activeforeground(self):
|
||||
widget = self.create()
|
||||
self.checkColorParam(widget, 'activeforeground')
|
||||
|
||||
def test_configure_activerelief(self):
|
||||
widget = self.create()
|
||||
self.checkReliefParam(widget, 'activerelief')
|
||||
|
||||
def test_configure_anchor(self):
|
||||
widget = self.create()
|
||||
self.checkEnumParam(widget, 'anchor',
|
||||
'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'center')
|
||||
|
||||
def test_configure_background(self):
|
||||
widget = self.create()
|
||||
self.checkColorParam(widget, 'background')
|
||||
if 'bg' in self.OPTIONS:
|
||||
self.checkColorParam(widget, 'bg')
|
||||
|
||||
@requires_tk(8, 7)
|
||||
def test_configure_backgroundimage(self):
|
||||
widget = self.create()
|
||||
self.checkImageParam(widget, 'backgroundimage')
|
||||
|
||||
def test_configure_bitmap(self):
|
||||
widget = self.create()
|
||||
self.checkParam(widget, 'bitmap', 'questhead')
|
||||
self.checkParam(widget, 'bitmap', 'gray50')
|
||||
filename = test.support.findfile('python.xbm', subdir='tkinterdata')
|
||||
self.checkParam(widget, 'bitmap', '@' + filename)
|
||||
# Cocoa Tk widgets don't detect invalid -bitmap values
|
||||
# See https://core.tcl.tk/tk/info/31cd33dbf0
|
||||
if not ('aqua' in self.root.tk.call('tk', 'windowingsystem') and
|
||||
'AppKit' in self.root.winfo_server()):
|
||||
self.checkInvalidParam(widget, 'bitmap', 'spam',
|
||||
errmsg='bitmap "spam" not defined')
|
||||
|
||||
def test_configure_compound(self):
|
||||
widget = self.create()
|
||||
self.checkEnumParam(widget, 'compound',
|
||||
'bottom', 'center', 'left', 'none', 'right', 'top')
|
||||
|
||||
def test_configure_cursor(self):
|
||||
widget = self.create()
|
||||
self.checkCursorParam(widget, 'cursor')
|
||||
|
||||
def test_configure_disabledforeground(self):
|
||||
widget = self.create()
|
||||
self.checkColorParam(widget, 'disabledforeground')
|
||||
|
||||
def test_configure_exportselection(self):
|
||||
widget = self.create()
|
||||
self.checkBooleanParam(widget, 'exportselection')
|
||||
|
||||
def test_configure_font(self):
|
||||
widget = self.create()
|
||||
self.checkParam(widget, 'font',
|
||||
'-Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-*')
|
||||
is_ttk = widget.__class__.__module__ == 'tkinter.ttk'
|
||||
if not is_ttk:
|
||||
errmsg = 'font "" does ?n[o\']t exist'
|
||||
self.checkInvalidParam(widget, 'font', '', errmsg=errmsg)
|
||||
|
||||
def test_configure_foreground(self):
|
||||
widget = self.create()
|
||||
self.checkColorParam(widget, 'foreground')
|
||||
if 'fg' in self.OPTIONS:
|
||||
self.checkColorParam(widget, 'fg')
|
||||
|
||||
def test_configure_highlightbackground(self):
|
||||
widget = self.create()
|
||||
self.checkColorParam(widget, 'highlightbackground')
|
||||
|
||||
def test_configure_highlightcolor(self):
|
||||
widget = self.create()
|
||||
self.checkColorParam(widget, 'highlightcolor')
|
||||
|
||||
def test_configure_image(self):
|
||||
widget = self.create()
|
||||
self.checkImageParam(widget, 'image')
|
||||
|
||||
def test_configure_insertbackground(self):
|
||||
widget = self.create()
|
||||
self.checkColorParam(widget, 'insertbackground')
|
||||
|
||||
def test_configure_insertofftime(self):
|
||||
widget = self.create()
|
||||
self.checkIntegerParam(widget, 'insertofftime', 100)
|
||||
|
||||
def test_configure_insertontime(self):
|
||||
widget = self.create()
|
||||
self.checkIntegerParam(widget, 'insertontime', 100)
|
||||
|
||||
def test_configure_jump(self):
|
||||
widget = self.create()
|
||||
self.checkBooleanParam(widget, 'jump')
|
||||
|
||||
def test_configure_justify(self):
|
||||
widget = self.create()
|
||||
values = ('left', 'right', 'center')
|
||||
if self._allow_empty_justify:
|
||||
values += ('',)
|
||||
self.checkEnumParam(widget, 'justify', *values,
|
||||
fullname='justification')
|
||||
|
||||
def test_configure_orient(self):
|
||||
widget = self.create()
|
||||
self.assertEqual(str(widget['orient']), self.default_orient)
|
||||
self.checkEnumParam(widget, 'orient', 'horizontal', 'vertical')
|
||||
|
||||
@requires_tk(8, 7)
|
||||
def test_configure_placeholder(self):
|
||||
widget = self.create()
|
||||
self.checkParam(widget, 'placeholder', 'xxx')
|
||||
|
||||
@requires_tk(8, 7)
|
||||
def test_configure_placeholderforeground(self):
|
||||
widget = self.create()
|
||||
self.checkColorParam(widget, 'placeholderforeground')
|
||||
|
||||
def test_configure_relief(self):
|
||||
widget = self.create()
|
||||
self.checkReliefParam(widget, 'relief')
|
||||
|
||||
def test_configure_repeatdelay(self):
|
||||
widget = self.create()
|
||||
self.checkIntegerParam(widget, 'repeatdelay', -500, 500)
|
||||
|
||||
def test_configure_repeatinterval(self):
|
||||
widget = self.create()
|
||||
self.checkIntegerParam(widget, 'repeatinterval', -500, 500)
|
||||
|
||||
def test_configure_selectbackground(self):
|
||||
widget = self.create()
|
||||
self.checkColorParam(widget, 'selectbackground')
|
||||
|
||||
def test_configure_selectforeground(self):
|
||||
widget = self.create()
|
||||
self.checkColorParam(widget, 'selectforeground')
|
||||
|
||||
def test_configure_setgrid(self):
|
||||
widget = self.create()
|
||||
self.checkBooleanParam(widget, 'setgrid')
|
||||
|
||||
def test_configure_state(self):
|
||||
widget = self.create()
|
||||
self.checkEnumParam(widget, 'state', 'active', 'disabled', 'normal')
|
||||
|
||||
def test_configure_takefocus(self):
|
||||
widget = self.create()
|
||||
self.checkParams(widget, 'takefocus', '0', '1', '')
|
||||
|
||||
def test_configure_text(self):
|
||||
widget = self.create()
|
||||
self.checkParams(widget, 'text', '', 'any string')
|
||||
|
||||
def test_configure_textvariable(self):
|
||||
widget = self.create()
|
||||
var = tkinter.StringVar(self.root)
|
||||
self.checkVariableParam(widget, 'textvariable', var)
|
||||
|
||||
@requires_tk(8, 7)
|
||||
def test_configure_tile(self):
|
||||
widget = self.create()
|
||||
self.checkBooleanParam(widget, 'tile')
|
||||
|
||||
def test_configure_troughcolor(self):
|
||||
widget = self.create()
|
||||
self.checkColorParam(widget, 'troughcolor')
|
||||
|
||||
def test_configure_underline(self):
|
||||
widget = self.create()
|
||||
self.checkParams(widget, 'underline', 0, 1, 10)
|
||||
if tk_version >= (8, 7):
|
||||
is_ttk = widget.__class__.__module__ == 'tkinter.ttk'
|
||||
self.checkParam(widget, 'underline', '',
|
||||
expected='' if is_ttk else self._default_pixels)
|
||||
self.checkParam(widget, 'underline', '5+2',
|
||||
expected='5+2' if is_ttk else 7)
|
||||
self.checkParam(widget, 'underline', '5-2',
|
||||
expected='5-2' if is_ttk else 3)
|
||||
self.checkParam(widget, 'underline', 'end', expected='end')
|
||||
self.checkParam(widget, 'underline', 'end-2', expected='end-2')
|
||||
errmsg = (r'bad index "{}": must be integer\?\[\+-\]integer\?, '
|
||||
r'end\?\[\+-\]integer\?, or ""')
|
||||
else:
|
||||
errmsg = 'expected integer but got "{}"'
|
||||
self.checkInvalidParam(widget, 'underline', '', errmsg=errmsg)
|
||||
self.checkInvalidParam(widget, 'underline', '10p', errmsg=errmsg)
|
||||
self.checkInvalidParam(widget, 'underline', 3.2, errmsg=errmsg)
|
||||
|
||||
def test_configure_wraplength(self):
|
||||
widget = self.create()
|
||||
self.checkPixelsParam(widget, 'wraplength', 100)
|
||||
|
||||
def test_configure_xscrollcommand(self):
|
||||
widget = self.create()
|
||||
self.checkCommandParam(widget, 'xscrollcommand')
|
||||
|
||||
def test_configure_yscrollcommand(self):
|
||||
widget = self.create()
|
||||
self.checkCommandParam(widget, 'yscrollcommand')
|
||||
|
||||
# non-standard but common options
|
||||
|
||||
def test_configure_command(self):
|
||||
widget = self.create()
|
||||
self.checkCommandParam(widget, 'command')
|
||||
|
||||
def test_configure_indicatoron(self):
|
||||
widget = self.create()
|
||||
self.checkBooleanParam(widget, 'indicatoron')
|
||||
|
||||
def test_configure_offrelief(self):
|
||||
widget = self.create()
|
||||
self.checkReliefParam(widget, 'offrelief')
|
||||
|
||||
def test_configure_overrelief(self):
|
||||
widget = self.create()
|
||||
self.checkReliefParam(widget, 'overrelief',
|
||||
allow_empty=(tk_version >= (8, 7)))
|
||||
|
||||
def test_configure_selectcolor(self):
|
||||
widget = self.create()
|
||||
self.checkColorParam(widget, 'selectcolor')
|
||||
|
||||
def test_configure_selectimage(self):
|
||||
widget = self.create()
|
||||
self.checkImageParam(widget, 'selectimage')
|
||||
|
||||
def test_configure_tristateimage(self):
|
||||
widget = self.create()
|
||||
self.checkImageParam(widget, 'tristateimage')
|
||||
|
||||
def test_configure_tristatevalue(self):
|
||||
widget = self.create()
|
||||
self.checkParam(widget, 'tristatevalue', 'unknowable')
|
||||
|
||||
def test_configure_variable(self):
|
||||
widget = self.create()
|
||||
var = tkinter.DoubleVar(self.root)
|
||||
self.checkVariableParam(widget, 'variable', var)
|
||||
|
||||
|
||||
class IntegerSizeTests:
|
||||
""" Tests widgets which only accept integral width and height."""
|
||||
def test_configure_height(self):
|
||||
widget = self.create()
|
||||
self.checkIntegerParam(widget, 'height', 100, -100, 0)
|
||||
|
||||
def test_configure_width(self):
|
||||
widget = self.create()
|
||||
self.checkIntegerParam(widget, 'width', 402, -402, 0)
|
||||
|
||||
|
||||
class PixelSizeTests:
|
||||
""" Tests widgets which accept screen distances for width and height."""
|
||||
def test_configure_height(self):
|
||||
widget = self.create()
|
||||
self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '3c')
|
||||
|
||||
def test_configure_width(self):
|
||||
widget = self.create()
|
||||
self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i')
|
||||
|
||||
|
||||
def add_configure_tests(*source_classes):
|
||||
# This decorator adds test_configure_xxx methods from source classes for
|
||||
# every xxx option in the OPTIONS class attribute if they are not defined
|
||||
# explicitly.
|
||||
def decorator(cls):
|
||||
for option in cls.OPTIONS:
|
||||
methodname = 'test_configure_' + option
|
||||
if not hasattr(cls, methodname):
|
||||
for source_class in source_classes:
|
||||
if hasattr(source_class, methodname):
|
||||
setattr(cls, methodname,
|
||||
getattr(source_class, methodname))
|
||||
break
|
||||
else:
|
||||
def test(self, option=option):
|
||||
widget = self.create()
|
||||
widget[option]
|
||||
raise AssertionError('Option "%s" is not tested in %s' %
|
||||
(option, cls.__name__))
|
||||
test.__name__ = methodname
|
||||
setattr(cls, methodname, test)
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
def setUpModule():
|
||||
if test.support.verbose:
|
||||
tcl = tkinter.Tcl()
|
||||
print('patchlevel =', tcl.call('info', 'patchlevel'), flush=True)
|
||||
Vendored
+53
@@ -0,0 +1,53 @@
|
||||
import os.path
|
||||
import unittest
|
||||
from test import support
|
||||
from test.support import import_helper
|
||||
|
||||
|
||||
if support.check_sanitizer(address=True, memory=True):
|
||||
raise unittest.SkipTest("Tests involving libX11 can SEGFAULT on ASAN/MSAN builds")
|
||||
|
||||
# Skip this test if _tkinter wasn't built.
|
||||
import_helper.import_module('_tkinter')
|
||||
|
||||
# Skip test if tk cannot be initialized.
|
||||
support.requires('gui')
|
||||
|
||||
|
||||
import tkinter
|
||||
from _tkinter import TclError
|
||||
from tkinter import ttk
|
||||
|
||||
|
||||
class TestModule(unittest.TestCase):
|
||||
def test_deprecated__version__(self):
|
||||
with self.assertWarnsRegex(
|
||||
DeprecationWarning,
|
||||
"'__version__' is deprecated and slated for removal in Python 3.20",
|
||||
) as cm:
|
||||
getattr(ttk, "__version__")
|
||||
self.assertEqual(cm.filename, __file__)
|
||||
|
||||
|
||||
def setUpModule():
|
||||
wantobjects = support.get_resource_value('wantobjects')
|
||||
if wantobjects is not None:
|
||||
unittest.enterModuleContext(
|
||||
support.swap_attr(tkinter, 'wantobjects', int(wantobjects)))
|
||||
root = None
|
||||
try:
|
||||
root = tkinter.Tk()
|
||||
button = ttk.Button(root)
|
||||
button.destroy()
|
||||
del button
|
||||
except TclError as msg:
|
||||
# assuming ttk is not available
|
||||
raise unittest.SkipTest("ttk not available: %s" % msg)
|
||||
finally:
|
||||
if root is not None:
|
||||
root.destroy()
|
||||
del root
|
||||
|
||||
|
||||
def load_tests(*args):
|
||||
return support.load_package_tests(os.path.dirname(__file__), *args)
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
from . import load_tests
|
||||
import unittest
|
||||
|
||||
unittest.main()
|
||||
Vendored
+404
@@ -0,0 +1,404 @@
|
||||
import sys
|
||||
import unittest
|
||||
import tkinter
|
||||
from tkinter import ttk
|
||||
from test.support import requires, gc_collect
|
||||
from test.test_tkinter.support import setUpModule # noqa: F401
|
||||
from test.test_tkinter.support import (AbstractTkTest, AbstractDefaultRootTest,
|
||||
get_tk_patchlevel, widget_eq)
|
||||
|
||||
requires('gui')
|
||||
|
||||
from test.test_ttk import test_widgets
|
||||
|
||||
|
||||
class LabeledScaleTest(AbstractTkTest, unittest.TestCase):
|
||||
|
||||
def tearDown(self):
|
||||
self.root.update_idletasks()
|
||||
super().tearDown()
|
||||
|
||||
def test_widget_destroy(self):
|
||||
# automatically created variable
|
||||
x = ttk.LabeledScale(self.root)
|
||||
var = x._variable._name
|
||||
x.destroy()
|
||||
gc_collect() # For PyPy or other GCs.
|
||||
self.assertRaises(tkinter.TclError, x.tk.globalgetvar, var)
|
||||
|
||||
# manually created variable
|
||||
myvar = tkinter.DoubleVar(self.root)
|
||||
name = myvar._name
|
||||
x = ttk.LabeledScale(self.root, variable=myvar)
|
||||
x.destroy()
|
||||
if self.wantobjects:
|
||||
self.assertEqual(x.tk.globalgetvar(name), myvar.get())
|
||||
else:
|
||||
self.assertEqual(float(x.tk.globalgetvar(name)), myvar.get())
|
||||
del myvar
|
||||
gc_collect() # For PyPy or other GCs.
|
||||
self.assertRaises(tkinter.TclError, x.tk.globalgetvar, name)
|
||||
|
||||
# checking that the tracing callback is properly removed
|
||||
myvar = tkinter.IntVar(self.root)
|
||||
# LabeledScale will start tracing myvar
|
||||
x = ttk.LabeledScale(self.root, variable=myvar)
|
||||
x.destroy()
|
||||
# Unless the tracing callback was removed, creating a new
|
||||
# LabeledScale with the same var will cause an error now. This
|
||||
# happens because the variable will be set to (possibly) a new
|
||||
# value which causes the tracing callback to be called and then
|
||||
# it tries calling instance attributes not yet defined.
|
||||
ttk.LabeledScale(self.root, variable=myvar)
|
||||
if hasattr(sys, 'last_exc'):
|
||||
self.assertNotEqual(type(sys.last_exc), tkinter.TclError)
|
||||
elif hasattr(sys, 'last_type'):
|
||||
self.assertNotEqual(sys.last_type, tkinter.TclError)
|
||||
|
||||
def test_initialization(self):
|
||||
# master passing
|
||||
master = tkinter.Frame(self.root)
|
||||
x = ttk.LabeledScale(master)
|
||||
self.assertEqual(x.master, master)
|
||||
x.destroy()
|
||||
|
||||
# variable initialization/passing
|
||||
passed_expected = (('0', 0), (0, 0), (10, 10),
|
||||
(-1, -1), (sys.maxsize + 1, sys.maxsize + 1),
|
||||
(2.5, 2), ('2.5', 2))
|
||||
for pair in passed_expected:
|
||||
x = ttk.LabeledScale(self.root, from_=pair[0])
|
||||
self.assertEqual(x.value, pair[1])
|
||||
x.destroy()
|
||||
x = ttk.LabeledScale(self.root, from_=None)
|
||||
self.assertRaises((ValueError, tkinter.TclError), x._variable.get)
|
||||
x.destroy()
|
||||
# variable should have its default value set to the from_ value
|
||||
myvar = tkinter.DoubleVar(self.root, value=20)
|
||||
x = ttk.LabeledScale(self.root, variable=myvar)
|
||||
self.assertEqual(x.value, 0)
|
||||
x.destroy()
|
||||
# check that it is really using a DoubleVar
|
||||
x = ttk.LabeledScale(self.root, variable=myvar, from_=0.5)
|
||||
self.assertEqual(x.value, 0.5)
|
||||
self.assertEqual(x._variable._name, myvar._name)
|
||||
x.destroy()
|
||||
|
||||
# widget positionment
|
||||
def check_positions(scale, scale_pos, label, label_pos):
|
||||
self.assertEqual(scale.pack_info()['side'], scale_pos)
|
||||
self.assertEqual(label.place_info()['anchor'], label_pos)
|
||||
x = ttk.LabeledScale(self.root, compound='top')
|
||||
check_positions(x.scale, 'bottom', x.label, 'n')
|
||||
x.destroy()
|
||||
x = ttk.LabeledScale(self.root, compound='bottom')
|
||||
check_positions(x.scale, 'top', x.label, 's')
|
||||
x.destroy()
|
||||
# invert default positions
|
||||
x = ttk.LabeledScale(self.root, compound='unknown')
|
||||
check_positions(x.scale, 'top', x.label, 's')
|
||||
x.destroy()
|
||||
x = ttk.LabeledScale(self.root) # take default positions
|
||||
check_positions(x.scale, 'bottom', x.label, 'n')
|
||||
x.destroy()
|
||||
|
||||
# extra, and invalid, kwargs
|
||||
self.assertRaises(tkinter.TclError, ttk.LabeledScale, master, a='b')
|
||||
|
||||
|
||||
def test_horizontal_range(self):
|
||||
lscale = ttk.LabeledScale(self.root, from_=0, to=10)
|
||||
lscale.pack()
|
||||
self.require_mapped(lscale)
|
||||
|
||||
linfo_1 = lscale.label.place_info()
|
||||
prev_xcoord = lscale.scale.coords()[0]
|
||||
self.assertEqual(prev_xcoord, int(linfo_1['x']))
|
||||
# change range to: from -5 to 5. This should change the x coord of
|
||||
# the scale widget, since 0 is at the middle of the new
|
||||
# range.
|
||||
lscale.scale.configure(from_=-5, to=5)
|
||||
# The following update is needed since the test doesn't use mainloop,
|
||||
# at the same time this shouldn't affect test outcome
|
||||
lscale.update()
|
||||
curr_xcoord = lscale.scale.coords()[0]
|
||||
self.assertNotEqual(prev_xcoord, curr_xcoord)
|
||||
# the label widget should have been repositioned too
|
||||
linfo_2 = lscale.label.place_info()
|
||||
self.assertEqual(lscale.label['text'], 0 if self.wantobjects else '0')
|
||||
self.assertEqual(curr_xcoord, int(linfo_2['x']))
|
||||
# change the range back
|
||||
lscale.scale.configure(from_=0, to=10)
|
||||
self.assertNotEqual(prev_xcoord, curr_xcoord)
|
||||
self.assertEqual(prev_xcoord, int(linfo_1['x']))
|
||||
|
||||
lscale.destroy()
|
||||
|
||||
|
||||
def test_variable_change(self):
|
||||
x = ttk.LabeledScale(self.root)
|
||||
x.pack()
|
||||
self.require_mapped(x)
|
||||
|
||||
curr_xcoord = x.scale.coords()[0]
|
||||
newval = x.value + 1
|
||||
x.value = newval
|
||||
# The following update is needed since the test doesn't use mainloop,
|
||||
# at the same time this shouldn't affect test outcome
|
||||
x.update()
|
||||
self.assertEqual(x.value, newval)
|
||||
self.assertEqual(x.label['text'],
|
||||
newval if self.wantobjects else str(newval))
|
||||
self.assertEqual(float(x.scale.get()), newval)
|
||||
self.assertGreater(x.scale.coords()[0], curr_xcoord)
|
||||
self.assertEqual(x.scale.coords()[0],
|
||||
int(x.label.place_info()['x']))
|
||||
|
||||
# value outside range
|
||||
if self.wantobjects:
|
||||
conv = lambda x: x
|
||||
else:
|
||||
conv = int
|
||||
x.value = conv(x.scale['to']) + 1 # no changes shouldn't happen
|
||||
x.update()
|
||||
self.assertEqual(x.value, newval)
|
||||
self.assertEqual(conv(x.label['text']), newval)
|
||||
self.assertEqual(float(x.scale.get()), newval)
|
||||
self.assertEqual(x.scale.coords()[0],
|
||||
int(x.label.place_info()['x']))
|
||||
|
||||
# non-integer value
|
||||
x.value = newval = newval + 1.5
|
||||
x.update()
|
||||
self.assertEqual(x.value, int(newval))
|
||||
self.assertEqual(conv(x.label['text']), int(newval))
|
||||
self.assertEqual(float(x.scale.get()), newval)
|
||||
|
||||
x.destroy()
|
||||
|
||||
|
||||
def test_resize(self):
|
||||
x = ttk.LabeledScale(self.root)
|
||||
x.pack(expand=True, fill='both')
|
||||
gc_collect() # For PyPy or other GCs.
|
||||
self.require_mapped(x)
|
||||
|
||||
width, height = x.master.winfo_width(), x.master.winfo_height()
|
||||
width_new, height_new = width * 2, height * 2
|
||||
|
||||
x.value = 3
|
||||
x.update()
|
||||
x.master.wm_geometry("%dx%d" % (width_new, height_new))
|
||||
self.assertEqual(int(x.label.place_info()['x']),
|
||||
x.scale.coords()[0])
|
||||
|
||||
# Reset geometry
|
||||
x.master.wm_geometry("%dx%d" % (width, height))
|
||||
x.destroy()
|
||||
|
||||
|
||||
class OptionMenuTest(test_widgets.MenubuttonTest, unittest.TestCase):
|
||||
|
||||
# OptionMenu documents only its own options, not the inherited
|
||||
# Menubutton options (like the classic tkinter.OptionMenu).
|
||||
test_options_in_docstring = None
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.textvar = tkinter.StringVar(self.root)
|
||||
|
||||
def tearDown(self):
|
||||
del self.textvar
|
||||
super().tearDown()
|
||||
|
||||
def create(self, default='b', values=('a', 'b', 'c'), **kwargs):
|
||||
return ttk.OptionMenu(self.root, self.textvar, default, *values, **kwargs)
|
||||
|
||||
def test_bad_kwarg(self):
|
||||
with self.assertRaisesRegex(tkinter.TclError, r"^unknown option -image$"):
|
||||
ttk.OptionMenu(self.root, self.textvar, 'b', image='')
|
||||
|
||||
def test_configure_class(self):
|
||||
# Unlike a plain Menubutton, OptionMenu does not accept a class at
|
||||
# construction, so only the read-only nature of the option is tested.
|
||||
widget = self.create()
|
||||
self.assertEqual(widget['class'], '')
|
||||
errmsg = 'attempt to change read-only option'
|
||||
if get_tk_patchlevel(self.root) < (8, 6, 0, 'beta', 3):
|
||||
errmsg = 'Attempt to change read-only option'
|
||||
self.checkInvalidParam(widget, 'class', 'Foo', errmsg=errmsg)
|
||||
|
||||
def test_configure_style(self):
|
||||
# Like Menubutton, but OptionMenu does not accept a class at
|
||||
# construction, so the custom-class part of the standard test is omitted.
|
||||
widget = self.create()
|
||||
self.assertEqual(widget['style'], '')
|
||||
self.checkInvalidParam(widget, 'style', 'Foo',
|
||||
errmsg='Layout Foo not found')
|
||||
style = ttk.Style(self.root)
|
||||
style.configure('Custom.TMenubutton')
|
||||
self.checkParam(widget, 'style', 'Custom.TMenubutton')
|
||||
|
||||
def test_configure_menu(self):
|
||||
# OptionMenu manages its own menu; ['menu'] returns that Menu widget.
|
||||
widget = self.create()
|
||||
self.assertIsInstance(widget['menu'], tkinter.Menu)
|
||||
self.checkParam(widget, 'menu', widget['menu'], eq=widget_eq)
|
||||
|
||||
def test_configure_text(self):
|
||||
# The displayed text is governed by the textvariable.
|
||||
widget = self.create()
|
||||
self.textvar.set('a')
|
||||
self.assertEqual(widget['text'], 'a')
|
||||
self.textvar.set('c')
|
||||
self.assertEqual(widget['text'], 'c')
|
||||
|
||||
def test_widget_destroy(self):
|
||||
var = tkinter.StringVar(self.root)
|
||||
optmenu = ttk.OptionMenu(self.root, var)
|
||||
name = var._name
|
||||
optmenu.update_idletasks()
|
||||
optmenu.destroy()
|
||||
self.assertEqual(optmenu.tk.globalgetvar(name), var.get())
|
||||
del var
|
||||
gc_collect() # For PyPy or other GCs.
|
||||
self.assertRaises(tkinter.TclError, optmenu.tk.globalgetvar, name)
|
||||
|
||||
|
||||
def test_initialization(self):
|
||||
self.assertRaises(tkinter.TclError,
|
||||
ttk.OptionMenu, self.root, self.textvar, invalid='thing')
|
||||
|
||||
optmenu = ttk.OptionMenu(self.root, self.textvar, 'b', 'a', 'b')
|
||||
self.assertEqual(optmenu._variable.get(), 'b')
|
||||
|
||||
self.assertTrue(optmenu['menu'])
|
||||
self.assertTrue(optmenu['textvariable'])
|
||||
|
||||
optmenu.destroy()
|
||||
|
||||
|
||||
def test_menu(self):
|
||||
items = ('a', 'b', 'c')
|
||||
default = 'a'
|
||||
optmenu = ttk.OptionMenu(self.root, self.textvar, default, *items)
|
||||
found_default = False
|
||||
for i in range(len(items)):
|
||||
value = optmenu['menu'].entrycget(i, 'value')
|
||||
self.assertEqual(value, items[i])
|
||||
if value == default:
|
||||
found_default = True
|
||||
self.assertTrue(found_default)
|
||||
optmenu.destroy()
|
||||
|
||||
# default shouldn't be in menu if it is not part of values
|
||||
default = 'd'
|
||||
optmenu = ttk.OptionMenu(self.root, self.textvar, default, *items)
|
||||
curr = None
|
||||
i = 0
|
||||
while True:
|
||||
last, curr = curr, optmenu['menu'].entryconfigure(i, 'value')
|
||||
if last == curr:
|
||||
# no more menu entries
|
||||
break
|
||||
self.assertNotEqual(curr, default)
|
||||
i += 1
|
||||
self.assertEqual(i, len(items))
|
||||
|
||||
# check that variable is updated correctly
|
||||
optmenu.pack()
|
||||
gc_collect() # For PyPy or other GCs.
|
||||
optmenu['menu'].invoke(0)
|
||||
self.assertEqual(optmenu._variable.get(), items[0])
|
||||
|
||||
# changing to an invalid index shouldn't change the variable
|
||||
self.assertRaises(tkinter.TclError, optmenu['menu'].invoke, -1)
|
||||
self.assertEqual(optmenu._variable.get(), items[0])
|
||||
|
||||
optmenu.destroy()
|
||||
|
||||
# specifying a callback
|
||||
success = []
|
||||
def cb_test(item):
|
||||
self.assertEqual(item, items[1])
|
||||
success.append(True)
|
||||
optmenu = ttk.OptionMenu(self.root, self.textvar, 'a', command=cb_test,
|
||||
*items)
|
||||
optmenu['menu'].invoke(1)
|
||||
if not success:
|
||||
self.fail("Menu callback not invoked")
|
||||
|
||||
optmenu.destroy()
|
||||
|
||||
def test_set_menu(self):
|
||||
optmenu = ttk.OptionMenu(self.root, self.textvar, 'a', 'a', 'b', 'c')
|
||||
menu = optmenu['menu']
|
||||
self.assertEqual(menu.index('end'), 2)
|
||||
|
||||
# set_menu rebuilds the menu with new values and an optional default.
|
||||
optmenu.set_menu('y', 'x', 'y', 'z')
|
||||
self.assertEqual(self.textvar.get(), 'y')
|
||||
self.assertEqual([menu.entrycget(i, 'label') for i in range(3)],
|
||||
['x', 'y', 'z'])
|
||||
|
||||
# Without a default the variable is left unchanged.
|
||||
optmenu.set_menu(None, 'p', 'q')
|
||||
self.assertEqual(self.textvar.get(), 'y')
|
||||
self.assertEqual([menu.entrycget(i, 'label') for i in range(2)],
|
||||
['p', 'q'])
|
||||
optmenu.destroy()
|
||||
|
||||
def test_unique_radiobuttons(self):
|
||||
# check that radiobuttons are unique across instances (bpo25684)
|
||||
items = ('a', 'b', 'c')
|
||||
default = 'a'
|
||||
optmenu = ttk.OptionMenu(self.root, self.textvar, default, *items)
|
||||
textvar2 = tkinter.StringVar(self.root)
|
||||
optmenu2 = ttk.OptionMenu(self.root, textvar2, default, *items)
|
||||
optmenu.pack()
|
||||
optmenu2.pack()
|
||||
optmenu['menu'].invoke(1)
|
||||
optmenu2['menu'].invoke(2)
|
||||
optmenu_stringvar_name = optmenu['menu'].entrycget(0, 'variable')
|
||||
optmenu2_stringvar_name = optmenu2['menu'].entrycget(0, 'variable')
|
||||
self.assertNotEqual(optmenu_stringvar_name,
|
||||
optmenu2_stringvar_name)
|
||||
self.assertEqual(self.root.tk.globalgetvar(optmenu_stringvar_name),
|
||||
items[1])
|
||||
self.assertEqual(self.root.tk.globalgetvar(optmenu2_stringvar_name),
|
||||
items[2])
|
||||
|
||||
optmenu.destroy()
|
||||
optmenu2.destroy()
|
||||
|
||||
def test_trace_variable(self):
|
||||
# prior to bpo45160, tracing a variable would cause the callback to be made twice
|
||||
success = []
|
||||
items = ('a', 'b', 'c')
|
||||
textvar = tkinter.StringVar(self.root)
|
||||
def cb_test(*args):
|
||||
success.append(textvar.get())
|
||||
optmenu = ttk.OptionMenu(self.root, textvar, "a", *items)
|
||||
optmenu.pack()
|
||||
cb_name = textvar.trace_add("write", cb_test)
|
||||
optmenu['menu'].invoke(1)
|
||||
self.assertEqual(success, ['b'])
|
||||
self.assertEqual(textvar.get(), 'b')
|
||||
textvar.trace_remove("write", cb_name)
|
||||
optmenu.destroy()
|
||||
|
||||
def test_specify_name(self):
|
||||
textvar = tkinter.StringVar(self.root)
|
||||
widget = ttk.OptionMenu(self.root, textvar, ":)", name="option_menu_ex")
|
||||
self.assertEqual(str(widget), ".option_menu_ex")
|
||||
self.assertIs(self.root.children["option_menu_ex"], widget)
|
||||
|
||||
|
||||
class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
|
||||
|
||||
def test_labeledscale(self):
|
||||
self._test_widget(ttk.LabeledScale)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Vendored
+464
@@ -0,0 +1,464 @@
|
||||
import unittest
|
||||
import sys
|
||||
import tkinter
|
||||
from tkinter import ttk
|
||||
from tkinter import TclError
|
||||
from test import support
|
||||
from test.support import requires
|
||||
from test.test_tkinter.support import setUpModule # noqa: F401
|
||||
from test.test_tkinter.support import AbstractTkTest, get_tk_patchlevel
|
||||
|
||||
requires('gui')
|
||||
|
||||
CLASS_NAMES = [
|
||||
'.', 'ComboboxPopdownFrame', 'Heading',
|
||||
'Horizontal.TProgressbar', 'Horizontal.TScale', 'Item', 'Sash',
|
||||
'TButton', 'TCheckbutton', 'TCombobox', 'TEntry',
|
||||
'TLabelframe', 'TLabelframe.Label', 'TMenubutton',
|
||||
'TNotebook', 'TNotebook.Tab', 'Toolbutton', 'TProgressbar',
|
||||
'TRadiobutton', 'Treeview', 'TScale', 'TScrollbar', 'TSpinbox',
|
||||
'Vertical.TProgressbar', 'Vertical.TScale'
|
||||
]
|
||||
|
||||
class StyleTest(AbstractTkTest, unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.style = ttk.Style(self.root)
|
||||
|
||||
|
||||
def test_configure(self):
|
||||
style = self.style
|
||||
style.configure('TButton', background='yellow')
|
||||
self.assertEqual(style.configure('TButton', 'background'),
|
||||
'yellow')
|
||||
self.assertIsInstance(style.configure('TButton'), dict)
|
||||
|
||||
|
||||
def test_map(self):
|
||||
style = self.style
|
||||
|
||||
# Single state
|
||||
for states in ['active'], [('active',)]:
|
||||
with self.subTest(states=states):
|
||||
style.map('TButton', background=[(*states, 'white')])
|
||||
expected = [('active', 'white')]
|
||||
self.assertEqual(style.map('TButton', 'background'), expected)
|
||||
m = style.map('TButton')
|
||||
self.assertIsInstance(m, dict)
|
||||
self.assertEqual(m['background'], expected)
|
||||
|
||||
# Multiple states
|
||||
for states in ['pressed', '!disabled'], ['pressed !disabled'], [('pressed', '!disabled')]:
|
||||
with self.subTest(states=states):
|
||||
style.map('TButton', background=[(*states, 'black')])
|
||||
expected = [('pressed', '!disabled', 'black')]
|
||||
self.assertEqual(style.map('TButton', 'background'), expected)
|
||||
m = style.map('TButton')
|
||||
self.assertIsInstance(m, dict)
|
||||
self.assertEqual(m['background'], expected)
|
||||
|
||||
# Default state
|
||||
for states in [], [''], [()]:
|
||||
with self.subTest(states=states):
|
||||
style.map('TButton', background=[(*states, 'grey')])
|
||||
expected = [('grey',)]
|
||||
self.assertEqual(style.map('TButton', 'background'), expected)
|
||||
m = style.map('TButton')
|
||||
self.assertIsInstance(m, dict)
|
||||
self.assertEqual(m['background'], expected)
|
||||
|
||||
|
||||
def test_lookup(self):
|
||||
style = self.style
|
||||
style.configure('TButton', background='yellow')
|
||||
style.map('TButton', background=[('active', 'background', 'blue')])
|
||||
|
||||
self.assertEqual(style.lookup('TButton', 'background'), 'yellow')
|
||||
self.assertEqual(style.lookup('TButton', 'background',
|
||||
['active', 'background']), 'blue')
|
||||
self.assertEqual(style.lookup('TButton', 'optionnotdefined',
|
||||
default='iknewit'), 'iknewit')
|
||||
|
||||
|
||||
def test_layout(self):
|
||||
style = self.style
|
||||
self.assertRaises(tkinter.TclError, style.layout, 'NotALayout')
|
||||
tv_style = style.layout('Treeview')
|
||||
|
||||
# "erase" Treeview layout
|
||||
style.layout('Treeview', '')
|
||||
self.assertEqual(style.layout('Treeview'),
|
||||
[('null', {'sticky': 'nswe'})]
|
||||
)
|
||||
|
||||
# restore layout
|
||||
style.layout('Treeview', tv_style)
|
||||
self.assertEqual(style.layout('Treeview'), tv_style)
|
||||
|
||||
# should return a list
|
||||
self.assertIsInstance(style.layout('TButton'), list)
|
||||
|
||||
# correct layout, but "option" doesn't exist as option
|
||||
self.assertRaises(tkinter.TclError, style.layout, 'Treeview',
|
||||
[('name', {'option': 'inexistent'})])
|
||||
|
||||
|
||||
def test_theme_use(self):
|
||||
self.assertRaises(tkinter.TclError, self.style.theme_use,
|
||||
'nonexistingname')
|
||||
|
||||
curr_theme = self.style.theme_use()
|
||||
new_theme = None
|
||||
for theme in self.style.theme_names():
|
||||
if theme != curr_theme:
|
||||
new_theme = theme
|
||||
self.style.theme_use(theme)
|
||||
break
|
||||
else:
|
||||
# just one theme available, can't go on with tests
|
||||
return
|
||||
|
||||
self.assertFalse(curr_theme == new_theme)
|
||||
self.assertFalse(new_theme != self.style.theme_use())
|
||||
|
||||
self.style.theme_use(curr_theme)
|
||||
|
||||
def test_theme_settings(self):
|
||||
style = self.style
|
||||
theme = style.theme_use()
|
||||
style.theme_settings(theme, {
|
||||
'Test.TLabel': {
|
||||
'configure': {'foreground': 'red', 'background': 'blue'},
|
||||
'map': {'foreground': [('active', 'green')]},
|
||||
},
|
||||
})
|
||||
self.assertEqual(style.lookup('Test.TLabel', 'foreground'), 'red')
|
||||
self.assertEqual(style.lookup('Test.TLabel', 'background'), 'blue')
|
||||
self.assertEqual(style.map('Test.TLabel', 'foreground'),
|
||||
[('active', 'green')])
|
||||
self.assertRaises(tkinter.TclError, style.theme_settings,
|
||||
'nonexistingname', {})
|
||||
|
||||
def test_configure_custom_copy(self):
|
||||
style = self.style
|
||||
|
||||
curr_theme = self.style.theme_use()
|
||||
self.addCleanup(self.style.theme_use, curr_theme)
|
||||
for theme in self.style.theme_names():
|
||||
self.style.theme_use(theme)
|
||||
for name in CLASS_NAMES:
|
||||
default = style.configure(name)
|
||||
if not default:
|
||||
continue
|
||||
with self.subTest(theme=theme, name=name):
|
||||
if support.verbose >= 2:
|
||||
print('configure', theme, name, default)
|
||||
if (theme in ('vista', 'xpnative')
|
||||
and sys.getwindowsversion()[:2] == (6, 1)):
|
||||
# Fails on the Windows 7 buildbot
|
||||
continue
|
||||
newname = f'C.{name}'
|
||||
self.assertEqual(style.configure(newname), None)
|
||||
style.configure(newname, **default)
|
||||
self.assertEqual(style.configure(newname), default)
|
||||
for key, value in default.items():
|
||||
self.assertEqual(style.configure(newname, key), value)
|
||||
|
||||
|
||||
def test_map_custom_copy(self):
|
||||
style = self.style
|
||||
|
||||
curr_theme = self.style.theme_use()
|
||||
self.addCleanup(self.style.theme_use, curr_theme)
|
||||
for theme in self.style.theme_names():
|
||||
self.style.theme_use(theme)
|
||||
for name in CLASS_NAMES:
|
||||
default = style.map(name)
|
||||
if not default:
|
||||
continue
|
||||
with self.subTest(theme=theme, name=name):
|
||||
if support.verbose >= 2:
|
||||
print('map', theme, name, default)
|
||||
if (theme in ('vista', 'xpnative')
|
||||
and sys.getwindowsversion()[:2] == (6, 1)):
|
||||
# Fails on the Windows 7 buildbot
|
||||
continue
|
||||
newname = f'C.{name}'
|
||||
self.assertEqual(style.map(newname), {})
|
||||
style.map(newname, **default)
|
||||
if theme == 'alt' and name == '.' and get_tk_patchlevel(self.root) < (8, 6, 1):
|
||||
default['embossed'] = [('disabled', '1')]
|
||||
self.assertEqual(style.map(newname), default)
|
||||
for key, value in default.items():
|
||||
self.assertEqual(style.map(newname, key), value)
|
||||
|
||||
def test_element_options(self):
|
||||
style = self.style
|
||||
element_names = style.element_names()
|
||||
self.assertNotIsInstance(element_names, str)
|
||||
for name in element_names:
|
||||
self.assertIsInstance(name, str)
|
||||
element_options = style.element_options(name)
|
||||
self.assertNotIsInstance(element_options, str)
|
||||
for optname in element_options:
|
||||
self.assertIsInstance(optname, str)
|
||||
|
||||
def test_element_create_errors(self):
|
||||
style = self.style
|
||||
with self.assertRaises(TypeError):
|
||||
style.element_create('plain.newelem')
|
||||
with self.assertRaisesRegex(TclError, 'No such element type spam'):
|
||||
style.element_create('plain.newelem', 'spam')
|
||||
|
||||
def test_element_create_from(self):
|
||||
style = self.style
|
||||
style.element_create('plain.background', 'from', 'default')
|
||||
self.assertIn('plain.background', style.element_names())
|
||||
style.element_create('plain.arrow', 'from', 'default', 'rightarrow')
|
||||
self.assertIn('plain.arrow', style.element_names())
|
||||
|
||||
def test_element_create_from_errors(self):
|
||||
style = self.style
|
||||
with self.assertRaises(IndexError):
|
||||
style.element_create('plain.newelem', 'from')
|
||||
with self.assertRaisesRegex(TclError,
|
||||
'theme "spam" (does not|doesn\'t) exist'):
|
||||
style.element_create('plain.newelem', 'from', 'spam')
|
||||
|
||||
def test_element_create_image(self):
|
||||
style = self.style
|
||||
image = tkinter.PhotoImage(master=self.root, width=12, height=10)
|
||||
style.element_create('block', 'image', image)
|
||||
self.assertIn('block', style.element_names())
|
||||
|
||||
style.layout('TestLabel1', [('block', {'sticky': 'news'})])
|
||||
a = ttk.Label(self.root, style='TestLabel1')
|
||||
a.pack(expand=True, fill='both')
|
||||
self.assertEqual(a.winfo_reqwidth(), 12)
|
||||
self.assertEqual(a.winfo_reqheight(), 10)
|
||||
|
||||
imgfile = support.findfile('python.xbm', subdir='tkinterdata')
|
||||
img1 = tkinter.BitmapImage(master=self.root, file=imgfile,
|
||||
foreground='yellow', background='blue')
|
||||
img2 = tkinter.BitmapImage(master=self.root, file=imgfile,
|
||||
foreground='blue', background='yellow')
|
||||
img3 = tkinter.BitmapImage(master=self.root, file=imgfile,
|
||||
foreground='white', background='black')
|
||||
style.element_create('TestButton.button', 'image',
|
||||
img1, ('pressed', img2), ('active', img3),
|
||||
border=(2, 4), sticky='we')
|
||||
self.assertIn('TestButton.button', style.element_names())
|
||||
|
||||
style.layout('TestButton', [('TestButton.button', {'sticky': 'news'})])
|
||||
b = ttk.Button(self.root, style='TestButton')
|
||||
b.pack(expand=True, fill='both')
|
||||
self.assertEqual(b.winfo_reqwidth(), 16)
|
||||
self.assertEqual(b.winfo_reqheight(), 16)
|
||||
|
||||
def test_element_create_image_errors(self):
|
||||
style = self.style
|
||||
image = tkinter.PhotoImage(master=self.root, width=10, height=10)
|
||||
with self.assertRaises(IndexError):
|
||||
style.element_create('block2', 'image')
|
||||
with self.assertRaises(TypeError):
|
||||
style.element_create('block2', 'image', image, 1)
|
||||
with self.assertRaises(ValueError):
|
||||
style.element_create('block2', 'image', image, ())
|
||||
with self.assertRaisesRegex(TclError, 'Invalid state name'):
|
||||
style.element_create('block2', 'image', image, ('spam', image))
|
||||
with self.assertRaisesRegex(TclError, 'Invalid state name'):
|
||||
style.element_create('block2', 'image', image, (1, image))
|
||||
with self.assertRaises(TypeError):
|
||||
style.element_create('block2', 'image', image, ('pressed', 1, image))
|
||||
with self.assertRaises(TypeError):
|
||||
style.element_create('block2', 'image', image, (1, 'selected', image))
|
||||
with self.assertRaisesRegex(TclError, 'bad option'):
|
||||
style.element_create('block2', 'image', image, spam=1)
|
||||
|
||||
def test_element_create_vsapi_1(self):
|
||||
style = self.style
|
||||
if 'xpnative' not in style.theme_names():
|
||||
self.skipTest("requires 'xpnative' theme")
|
||||
style.element_create('smallclose', 'vsapi', 'WINDOW', 19, [
|
||||
('disabled', 4),
|
||||
('pressed', 3),
|
||||
('active', 2),
|
||||
('', 1)])
|
||||
style.layout('CloseButton',
|
||||
[('CloseButton.smallclose', {'sticky': 'news'})])
|
||||
b = ttk.Button(self.root, style='CloseButton')
|
||||
b.pack(expand=True, fill='both')
|
||||
self.assertEqual(b.winfo_reqwidth(), 13)
|
||||
self.assertEqual(b.winfo_reqheight(), 13)
|
||||
|
||||
def test_element_create_vsapi_2(self):
|
||||
style = self.style
|
||||
if 'xpnative' not in style.theme_names():
|
||||
self.skipTest("requires 'xpnative' theme")
|
||||
style.element_create('pin', 'vsapi', 'EXPLORERBAR', 3, [
|
||||
('pressed', '!selected', 3),
|
||||
('active', '!selected', 2),
|
||||
('pressed', 'selected', 6),
|
||||
('active', 'selected', 5),
|
||||
('selected', 4),
|
||||
('', 1)])
|
||||
style.layout('Explorer.Pin',
|
||||
[('Explorer.Pin.pin', {'sticky': 'news'})])
|
||||
pin = ttk.Checkbutton(self.root, style='Explorer.Pin')
|
||||
pin.pack(expand=True, fill='both')
|
||||
self.assertEqual(pin.winfo_reqwidth(), 16)
|
||||
self.assertEqual(pin.winfo_reqheight(), 16)
|
||||
|
||||
def test_element_create_vsapi_3(self):
|
||||
style = self.style
|
||||
if 'xpnative' not in style.theme_names():
|
||||
self.skipTest("requires 'xpnative' theme")
|
||||
style.element_create('headerclose', 'vsapi', 'EXPLORERBAR', 2, [
|
||||
('pressed', 3),
|
||||
('active', 2),
|
||||
('', 1)])
|
||||
style.layout('Explorer.CloseButton',
|
||||
[('Explorer.CloseButton.headerclose', {'sticky': 'news'})])
|
||||
b = ttk.Button(self.root, style='Explorer.CloseButton')
|
||||
b.pack(expand=True, fill='both')
|
||||
self.assertEqual(b.winfo_reqwidth(), 16)
|
||||
self.assertEqual(b.winfo_reqheight(), 16)
|
||||
|
||||
def test_theme_create(self):
|
||||
style = self.style
|
||||
curr_theme = style.theme_use()
|
||||
curr_layout = style.layout('TLabel')
|
||||
style.theme_create('testtheme1')
|
||||
self.assertIn('testtheme1', style.theme_names())
|
||||
|
||||
style.theme_create('testtheme2', settings={
|
||||
'elem' : {'element create': ['from', 'default'],},
|
||||
'TLabel' : {
|
||||
'configure': {'padding': 10},
|
||||
'layout': [('elem', {'sticky': 'we'})],
|
||||
},
|
||||
})
|
||||
self.assertIn('testtheme2', style.theme_names())
|
||||
|
||||
style.theme_create('testtheme3', 'testtheme2')
|
||||
self.assertIn('testtheme3', style.theme_names())
|
||||
|
||||
style.theme_use('testtheme1')
|
||||
self.assertEqual(style.element_names(), ())
|
||||
self.assertEqual(style.layout('TLabel'), curr_layout)
|
||||
|
||||
style.theme_use('testtheme2')
|
||||
self.assertEqual(style.element_names(), ('elem',))
|
||||
self.assertEqual(style.lookup('TLabel', 'padding'), '10')
|
||||
self.assertEqual(style.layout('TLabel'), [('elem', {'sticky': 'we'})])
|
||||
|
||||
style.theme_use('testtheme3')
|
||||
self.assertEqual(style.element_names(), ())
|
||||
self.assertEqual(style.lookup('TLabel', 'padding'), '')
|
||||
self.assertEqual(style.layout('TLabel'), [('elem', {'sticky': 'we'})])
|
||||
|
||||
style.theme_use(curr_theme)
|
||||
|
||||
def test_theme_create_image(self):
|
||||
style = self.style
|
||||
curr_theme = style.theme_use()
|
||||
image = tkinter.PhotoImage(master=self.root, width=10, height=10)
|
||||
new_theme = 'testtheme4'
|
||||
style.theme_create(new_theme, settings={
|
||||
'block' : {
|
||||
'element create': ['image', image, {'width': 120, 'height': 100}],
|
||||
},
|
||||
'TestWidget.block2' : {
|
||||
'element create': ['image', image],
|
||||
},
|
||||
'TestWidget' : {
|
||||
'configure': {
|
||||
'anchor': 'left',
|
||||
'padding': (3, 0, 0, 2),
|
||||
'foreground': 'yellow',
|
||||
},
|
||||
'map': {
|
||||
'foreground': [
|
||||
('pressed', 'red'),
|
||||
('active', 'disabled', 'blue'),
|
||||
],
|
||||
},
|
||||
'layout': [
|
||||
('TestWidget.block', {'sticky': 'we', 'side': 'left'}),
|
||||
('TestWidget.border', {
|
||||
'sticky': 'nsw',
|
||||
'border': 1,
|
||||
'children': [
|
||||
('TestWidget.block2', {'sticky': 'nswe'})
|
||||
]
|
||||
})
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
style.theme_use(new_theme)
|
||||
self.assertIn('block', style.element_names())
|
||||
self.assertEqual(style.lookup('TestWidget', 'anchor'), 'left')
|
||||
self.assertEqual(style.lookup('TestWidget', 'padding'), '3 0 0 2')
|
||||
self.assertEqual(style.lookup('TestWidget', 'foreground'), 'yellow')
|
||||
self.assertEqual(style.lookup('TestWidget', 'foreground',
|
||||
['active']), 'yellow')
|
||||
self.assertEqual(style.lookup('TestWidget', 'foreground',
|
||||
['active', 'pressed']), 'red')
|
||||
self.assertEqual(style.lookup('TestWidget', 'foreground',
|
||||
['active', 'disabled']), 'blue')
|
||||
self.assertEqual(style.layout('TestWidget'),
|
||||
[
|
||||
('TestWidget.block', {'side': 'left', 'sticky': 'we'}),
|
||||
('TestWidget.border', {
|
||||
'sticky': 'nsw',
|
||||
'border': '1',
|
||||
'children': [('TestWidget.block2', {'sticky': 'nswe'})]
|
||||
})
|
||||
])
|
||||
|
||||
b = ttk.Label(self.root, style='TestWidget')
|
||||
b.pack(expand=True, fill='both')
|
||||
# The exact width varies with the Tk version and display scaling.
|
||||
self.assertGreater(b.winfo_reqwidth(), 130)
|
||||
self.assertEqual(b.winfo_reqheight(), 100)
|
||||
|
||||
style.theme_use(curr_theme)
|
||||
|
||||
def test_theme_create_vsapi(self):
|
||||
style = self.style
|
||||
if 'xpnative' not in style.theme_names():
|
||||
self.skipTest("requires 'xpnative' theme")
|
||||
curr_theme = style.theme_use()
|
||||
new_theme = 'testtheme5'
|
||||
style.theme_create(new_theme, settings={
|
||||
'pin' : {
|
||||
'element create': ['vsapi', 'EXPLORERBAR', 3, [
|
||||
('pressed', '!selected', 3),
|
||||
('active', '!selected', 2),
|
||||
('pressed', 'selected', 6),
|
||||
('active', 'selected', 5),
|
||||
('selected', 4),
|
||||
('', 1)]],
|
||||
},
|
||||
'Explorer.Pin' : {
|
||||
'layout': [('Explorer.Pin.pin', {'sticky': 'news'})],
|
||||
},
|
||||
})
|
||||
|
||||
style.theme_use(new_theme)
|
||||
self.assertIn('pin', style.element_names())
|
||||
self.assertEqual(style.layout('Explorer.Pin'),
|
||||
[('Explorer.Pin.pin', {'sticky': 'nswe'})])
|
||||
|
||||
pin = ttk.Checkbutton(self.root, style='Explorer.Pin')
|
||||
pin.pack(expand=True, fill='both')
|
||||
self.assertEqual(pin.winfo_reqwidth(), 16)
|
||||
self.assertEqual(pin.winfo_reqheight(), 16)
|
||||
|
||||
style.theme_use(curr_theme)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Vendored
+2202
File diff suppressed because it is too large.
Load diff
Vendored
+481
@@ -0,0 +1,481 @@
|
||||
from test.support import import_helper
|
||||
|
||||
# Skip this test if _tkinter does not exist.
|
||||
import_helper.import_module('_tkinter')
|
||||
|
||||
import unittest
|
||||
from tkinter import ttk
|
||||
|
||||
|
||||
class MockTkApp:
|
||||
|
||||
def splitlist(self, arg):
|
||||
if isinstance(arg, tuple):
|
||||
return arg
|
||||
return arg.split(':')
|
||||
|
||||
def wantobjects(self):
|
||||
return True
|
||||
|
||||
|
||||
class MockTclObj(object):
|
||||
typename = 'test'
|
||||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
def __str__(self):
|
||||
return str(self.val)
|
||||
|
||||
|
||||
class MockStateSpec(object):
|
||||
typename = 'StateSpec'
|
||||
|
||||
def __init__(self, *args):
|
||||
self.val = args
|
||||
|
||||
def __str__(self):
|
||||
return ' '.join(self.val)
|
||||
|
||||
|
||||
class InternalFunctionsTest(unittest.TestCase):
|
||||
|
||||
def test_format_optdict(self):
|
||||
def check_against(fmt_opts, result):
|
||||
for i in range(0, len(fmt_opts), 2):
|
||||
self.assertEqual(result.pop(fmt_opts[i]), fmt_opts[i + 1])
|
||||
if result:
|
||||
self.fail("result still got elements: %s" % result)
|
||||
|
||||
# passing an empty dict should return an empty object (tuple here)
|
||||
self.assertFalse(ttk._format_optdict({}))
|
||||
|
||||
# check list formatting
|
||||
check_against(
|
||||
ttk._format_optdict({'fg': 'blue', 'padding': [1, 2, 3, 4]}),
|
||||
{'-fg': 'blue', '-padding': '1 2 3 4'})
|
||||
|
||||
# check tuple formatting (same as list)
|
||||
check_against(
|
||||
ttk._format_optdict({'test': (1, 2, '', 0)}),
|
||||
{'-test': '1 2 {} 0'})
|
||||
|
||||
# check untouched values
|
||||
check_against(
|
||||
ttk._format_optdict({'test': {'left': 'as is'}}),
|
||||
{'-test': {'left': 'as is'}})
|
||||
|
||||
# check script formatting
|
||||
check_against(
|
||||
ttk._format_optdict(
|
||||
{'test': [1, -1, '', '2m', 0], 'test2': 3,
|
||||
'test3': '', 'test4': 'abc def',
|
||||
'test5': '"abc"', 'test6': '{}',
|
||||
'test7': '} -spam {'}, script=True),
|
||||
{'-test': '{1 -1 {} 2m 0}', '-test2': '3',
|
||||
'-test3': '{}', '-test4': '{abc def}',
|
||||
'-test5': '{"abc"}', '-test6': r'\{\}',
|
||||
'-test7': r'\}\ -spam\ \{'})
|
||||
|
||||
opts = {'αβγ': True, 'á': False}
|
||||
orig_opts = opts.copy()
|
||||
# check if giving unicode keys is fine
|
||||
check_against(ttk._format_optdict(opts), {'-αβγ': True, '-á': False})
|
||||
# opts should remain unchanged
|
||||
self.assertEqual(opts, orig_opts)
|
||||
|
||||
# passing values with spaces inside a tuple/list
|
||||
check_against(
|
||||
ttk._format_optdict(
|
||||
{'option': ('one two', 'three')}),
|
||||
{'-option': '{one two} three'})
|
||||
check_against(
|
||||
ttk._format_optdict(
|
||||
{'option': ('one\ttwo', 'three')}),
|
||||
{'-option': '{one\ttwo} three'})
|
||||
|
||||
# passing empty strings inside a tuple/list
|
||||
check_against(
|
||||
ttk._format_optdict(
|
||||
{'option': ('', 'one')}),
|
||||
{'-option': '{} one'})
|
||||
|
||||
# passing values with braces inside a tuple/list
|
||||
check_against(
|
||||
ttk._format_optdict(
|
||||
{'option': ('one} {two', 'three')}),
|
||||
{'-option': r'one\}\ \{two three'})
|
||||
|
||||
# passing quoted strings inside a tuple/list
|
||||
check_against(
|
||||
ttk._format_optdict(
|
||||
{'option': ('"one"', 'two')}),
|
||||
{'-option': '{"one"} two'})
|
||||
check_against(
|
||||
ttk._format_optdict(
|
||||
{'option': ('{one}', 'two')}),
|
||||
{'-option': r'\{one\} two'})
|
||||
|
||||
# ignore an option
|
||||
amount_opts = len(ttk._format_optdict(opts, ignore=('á'))) / 2
|
||||
self.assertEqual(amount_opts, len(opts) - 1)
|
||||
|
||||
# ignore non-existing options
|
||||
amount_opts = len(ttk._format_optdict(opts, ignore=('á', 'b'))) / 2
|
||||
self.assertEqual(amount_opts, len(opts) - 1)
|
||||
|
||||
# ignore every option
|
||||
self.assertFalse(ttk._format_optdict(opts, ignore=list(opts.keys())))
|
||||
|
||||
|
||||
def test_format_mapdict(self):
|
||||
opts = {'a': [('b', 'c', 'val'), ('d', 'otherval'), ('', 'single')]}
|
||||
result = ttk._format_mapdict(opts)
|
||||
self.assertEqual(len(result), len(list(opts.keys())) * 2)
|
||||
self.assertEqual(result, ('-a', '{b c} val d otherval {} single'))
|
||||
self.assertEqual(ttk._format_mapdict(opts, script=True),
|
||||
('-a', '{{b c} val d otherval {} single}'))
|
||||
|
||||
self.assertEqual(ttk._format_mapdict({2: []}), ('-2', ''))
|
||||
|
||||
opts = {'üñíćódè': [('á', 'vãl')]}
|
||||
result = ttk._format_mapdict(opts)
|
||||
self.assertEqual(result, ('-üñíćódè', 'á vãl'))
|
||||
|
||||
self.assertEqual(ttk._format_mapdict({'opt': [('value',)]}),
|
||||
('-opt', '{} value'))
|
||||
|
||||
# empty states
|
||||
valid = {'opt': [('', '', 'hi')]}
|
||||
self.assertEqual(ttk._format_mapdict(valid), ('-opt', '{ } hi'))
|
||||
|
||||
# when passing multiple states, they all must be strings
|
||||
invalid = {'opt': [(1, 2, 'valid val')]}
|
||||
self.assertRaises(TypeError, ttk._format_mapdict, invalid)
|
||||
invalid = {'opt': [([1], '2', 'valid val')]}
|
||||
self.assertRaises(TypeError, ttk._format_mapdict, invalid)
|
||||
# but when passing a single state, it can be anything
|
||||
valid = {'opt': [[1, 'value']]}
|
||||
self.assertEqual(ttk._format_mapdict(valid), ('-opt', '1 value'))
|
||||
# special attention to single states which evaluate to False
|
||||
for stateval in (None, 0, False, '', set()): # just some samples
|
||||
valid = {'opt': [(stateval, 'value')]}
|
||||
self.assertEqual(ttk._format_mapdict(valid),
|
||||
('-opt', '{} value'))
|
||||
|
||||
# values must be iterable
|
||||
opts = {'a': None}
|
||||
self.assertRaises(TypeError, ttk._format_mapdict, opts)
|
||||
|
||||
|
||||
def test_format_elemcreate(self):
|
||||
self.assertTrue(ttk._format_elemcreate(None), (None, ()))
|
||||
|
||||
## Testing type = image
|
||||
# image type expects at least an image name, so this should raise
|
||||
# IndexError since it tries to access the index 0 of an empty tuple
|
||||
self.assertRaises(IndexError, ttk._format_elemcreate, 'image')
|
||||
|
||||
# don't format returned values as a tcl script
|
||||
# minimum acceptable for image type
|
||||
self.assertEqual(ttk._format_elemcreate('image', False, 'test'),
|
||||
("test", ()))
|
||||
# specifying a state spec
|
||||
self.assertEqual(ttk._format_elemcreate('image', False, 'test',
|
||||
('', 'a')), ("test {} a", ()))
|
||||
# state spec with multiple states
|
||||
self.assertEqual(ttk._format_elemcreate('image', False, 'test',
|
||||
('a', 'b', 'c')), ("test {a b} c", ()))
|
||||
# state spec and options
|
||||
self.assertEqual(ttk._format_elemcreate('image', False, 'test',
|
||||
('a', 'b'), a='x'), ("test a b", ("-a", "x")))
|
||||
# format returned values as a tcl script
|
||||
# state spec with multiple states and an option with a multivalue
|
||||
self.assertEqual(ttk._format_elemcreate('image', True, 'test',
|
||||
('a', 'b', 'c', 'd'), x=[2, 3]), ("{test {a b c} d}", "-x {2 3}"))
|
||||
|
||||
## Testing type = vsapi
|
||||
# vsapi type expects at least a class name and a part_id, so this
|
||||
# should raise a ValueError since it tries to get two elements from
|
||||
# an empty tuple
|
||||
self.assertRaises(ValueError, ttk._format_elemcreate, 'vsapi')
|
||||
|
||||
# don't format returned values as a tcl script
|
||||
# minimum acceptable for vsapi
|
||||
self.assertEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b'),
|
||||
('a', 'b', ('', 1), ()))
|
||||
# now with a state spec with multiple states
|
||||
self.assertEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b',
|
||||
[('a', 'b', 'c')]), ('a', 'b', ('a b', 'c'), ()))
|
||||
# state spec and option
|
||||
self.assertEqual(ttk._format_elemcreate('vsapi', False, 'a', 'b',
|
||||
[('a', 'b')], opt='x'), ('a', 'b', ('a', 'b'), ("-opt", "x")))
|
||||
# format returned values as a tcl script
|
||||
# state spec with a multivalue and an option
|
||||
self.assertEqual(ttk._format_elemcreate('vsapi', True, 'a', 'b',
|
||||
opt='x'), ("a b {{} 1}", "-opt x"))
|
||||
self.assertEqual(ttk._format_elemcreate('vsapi', True, 'a', 'b',
|
||||
[('a', 'b', [1, 2])], opt='x'), ("a b {{a b} {1 2}}", "-opt x"))
|
||||
|
||||
# Testing type = from
|
||||
# from type expects at least a type name
|
||||
self.assertRaises(IndexError, ttk._format_elemcreate, 'from')
|
||||
|
||||
self.assertEqual(ttk._format_elemcreate('from', False, 'a'),
|
||||
('a', ()))
|
||||
self.assertEqual(ttk._format_elemcreate('from', False, 'a', 'b'),
|
||||
('a', ('b',)))
|
||||
self.assertEqual(ttk._format_elemcreate('from', True, 'a', 'b'),
|
||||
('a', 'b'))
|
||||
|
||||
|
||||
def test_format_layoutlist(self):
|
||||
def sample(indent=0, indent_size=2):
|
||||
return ttk._format_layoutlist(
|
||||
[('a', {'other': [1, 2, 3], 'children':
|
||||
[('b', {'children':
|
||||
[('c', {'children':
|
||||
[('d', {'nice': 'opt'})], 'something': (1, 2)
|
||||
})]
|
||||
})]
|
||||
})], indent=indent, indent_size=indent_size)[0]
|
||||
|
||||
def sample_expected(indent=0, indent_size=2):
|
||||
spaces = lambda amount=0: ' ' * (amount + indent)
|
||||
return (
|
||||
"%sa -other {1 2 3} -children {\n"
|
||||
"%sb -children {\n"
|
||||
"%sc -something {1 2} -children {\n"
|
||||
"%sd -nice opt\n"
|
||||
"%s}\n"
|
||||
"%s}\n"
|
||||
"%s}" % (spaces(), spaces(indent_size),
|
||||
spaces(2 * indent_size), spaces(3 * indent_size),
|
||||
spaces(2 * indent_size), spaces(indent_size), spaces()))
|
||||
|
||||
# empty layout
|
||||
self.assertEqual(ttk._format_layoutlist([])[0], '')
|
||||
|
||||
# _format_layoutlist always expects the second item (in every item)
|
||||
# to act like a dict (except when the value evaluates to False).
|
||||
self.assertRaises(AttributeError,
|
||||
ttk._format_layoutlist, [('a', 'b')])
|
||||
|
||||
smallest = ttk._format_layoutlist([('a', None)], indent=0)
|
||||
self.assertEqual(smallest,
|
||||
ttk._format_layoutlist([('a', '')], indent=0))
|
||||
self.assertEqual(smallest[0], 'a')
|
||||
|
||||
# testing indentation levels
|
||||
self.assertEqual(sample(), sample_expected())
|
||||
for i in range(4):
|
||||
self.assertEqual(sample(i), sample_expected(i))
|
||||
self.assertEqual(sample(i, i), sample_expected(i, i))
|
||||
|
||||
# invalid layout format, different kind of exceptions will be
|
||||
# raised by internal functions
|
||||
|
||||
# plain wrong format
|
||||
self.assertRaises(ValueError, ttk._format_layoutlist,
|
||||
['bad', 'format'])
|
||||
# will try to use iteritems in the 'bad' string
|
||||
self.assertRaises(AttributeError, ttk._format_layoutlist,
|
||||
[('name', 'bad')])
|
||||
# bad children formatting
|
||||
self.assertRaises(ValueError, ttk._format_layoutlist,
|
||||
[('name', {'children': {'a': None}})])
|
||||
|
||||
|
||||
def test_script_from_settings(self):
|
||||
# empty options
|
||||
self.assertFalse(ttk._script_from_settings({'name':
|
||||
{'configure': None, 'map': None, 'element create': None}}))
|
||||
|
||||
# empty layout
|
||||
self.assertEqual(
|
||||
ttk._script_from_settings({'name': {'layout': None}}),
|
||||
"ttk::style layout name {\nnull\n}")
|
||||
|
||||
configdict = {'αβγ': True, 'á': False}
|
||||
self.assertTrue(
|
||||
ttk._script_from_settings({'name': {'configure': configdict}}))
|
||||
|
||||
mapdict = {'üñíćódè': [('á', 'vãl')]}
|
||||
self.assertTrue(
|
||||
ttk._script_from_settings({'name': {'map': mapdict}}))
|
||||
|
||||
# invalid image element
|
||||
self.assertRaises(IndexError,
|
||||
ttk._script_from_settings, {'name': {'element create': ['image']}})
|
||||
|
||||
# minimal valid image
|
||||
self.assertTrue(ttk._script_from_settings({'name':
|
||||
{'element create': ['image', 'name']}}))
|
||||
|
||||
image = {'thing': {'element create':
|
||||
['image', 'name', ('state1', 'state2', 'val')]}}
|
||||
self.assertEqual(ttk._script_from_settings(image),
|
||||
"ttk::style element create thing image {name {state1 state2} val} ")
|
||||
|
||||
image['thing']['element create'].append({'opt': 30})
|
||||
self.assertEqual(ttk._script_from_settings(image),
|
||||
"ttk::style element create thing image {name {state1 state2} val} "
|
||||
"-opt 30")
|
||||
|
||||
image['thing']['element create'][-1]['opt'] = [MockTclObj(3),
|
||||
MockTclObj('2m')]
|
||||
self.assertEqual(ttk._script_from_settings(image),
|
||||
"ttk::style element create thing image {name {state1 state2} val} "
|
||||
"-opt {3 2m}")
|
||||
|
||||
vsapi = {'pin': {'element create':
|
||||
['vsapi', 'EXPLORERBAR', 3, [
|
||||
('pressed', '!selected', 3),
|
||||
('active', '!selected', 2),
|
||||
('pressed', 'selected', 6),
|
||||
('active', 'selected', 5),
|
||||
('selected', 4),
|
||||
('', 1)]]}}
|
||||
self.assertEqual(ttk._script_from_settings(vsapi),
|
||||
"ttk::style element create pin vsapi EXPLORERBAR 3 {"
|
||||
"{pressed !selected} 3 "
|
||||
"{active !selected} 2 "
|
||||
"{pressed selected} 6 "
|
||||
"{active selected} 5 "
|
||||
"selected 4 "
|
||||
"{} 1} ")
|
||||
|
||||
def test_tclobj_to_py(self):
|
||||
self.assertEqual(
|
||||
ttk._tclobj_to_py((MockStateSpec('a', 'b'), 'val')),
|
||||
[('a', 'b', 'val')])
|
||||
self.assertEqual(
|
||||
ttk._tclobj_to_py([MockTclObj('1'), 2, MockTclObj('3m')]),
|
||||
[1, 2, '3m'])
|
||||
|
||||
|
||||
def test_list_from_statespec(self):
|
||||
def test_it(sspec, value, res_value, states):
|
||||
self.assertEqual(ttk._list_from_statespec(
|
||||
(sspec, value)), [states + (res_value, )])
|
||||
|
||||
states_even = tuple('state%d' % i for i in range(6))
|
||||
statespec = MockStateSpec(*states_even)
|
||||
test_it(statespec, 'val', 'val', states_even)
|
||||
test_it(statespec, MockTclObj('val'), 'val', states_even)
|
||||
|
||||
states_odd = tuple('state%d' % i for i in range(5))
|
||||
statespec = MockStateSpec(*states_odd)
|
||||
test_it(statespec, 'val', 'val', states_odd)
|
||||
|
||||
test_it(('a', 'b', 'c'), MockTclObj('val'), 'val', ('a', 'b', 'c'))
|
||||
|
||||
|
||||
def test_list_from_layouttuple(self):
|
||||
tk = MockTkApp()
|
||||
|
||||
# empty layout tuple
|
||||
self.assertFalse(ttk._list_from_layouttuple(tk, ()))
|
||||
|
||||
# shortest layout tuple
|
||||
self.assertEqual(ttk._list_from_layouttuple(tk, ('name', )),
|
||||
[('name', {})])
|
||||
|
||||
# not so interesting ltuple
|
||||
sample_ltuple = ('name', '-option', 'value')
|
||||
self.assertEqual(ttk._list_from_layouttuple(tk, sample_ltuple),
|
||||
[('name', {'option': 'value'})])
|
||||
|
||||
# empty children
|
||||
self.assertEqual(ttk._list_from_layouttuple(tk,
|
||||
('something', '-children', ())),
|
||||
[('something', {'children': []})]
|
||||
)
|
||||
|
||||
# more interesting ltuple
|
||||
ltuple = (
|
||||
'name', '-option', 'niceone', '-children', (
|
||||
('otherone', '-children', (
|
||||
('child', )), '-otheropt', 'othervalue'
|
||||
)
|
||||
)
|
||||
)
|
||||
self.assertEqual(ttk._list_from_layouttuple(tk, ltuple),
|
||||
[('name', {'option': 'niceone', 'children':
|
||||
[('otherone', {'otheropt': 'othervalue', 'children':
|
||||
[('child', {})]
|
||||
})]
|
||||
})]
|
||||
)
|
||||
|
||||
# bad tuples
|
||||
self.assertRaises(ValueError, ttk._list_from_layouttuple, tk,
|
||||
('name', 'no_minus'))
|
||||
self.assertRaises(ValueError, ttk._list_from_layouttuple, tk,
|
||||
('name', 'no_minus', 'value'))
|
||||
self.assertRaises(ValueError, ttk._list_from_layouttuple, tk,
|
||||
('something', '-children')) # no children
|
||||
|
||||
|
||||
def test_val_or_dict(self):
|
||||
def func(res, opt=None, val=None):
|
||||
if opt is None:
|
||||
return res
|
||||
if val is None:
|
||||
return "test val"
|
||||
return (opt, val)
|
||||
|
||||
tk = MockTkApp()
|
||||
tk.call = func
|
||||
|
||||
self.assertEqual(ttk._val_or_dict(tk, {}, '-test:3'),
|
||||
{'test': '3'})
|
||||
self.assertEqual(ttk._val_or_dict(tk, {}, ('-test', 3)),
|
||||
{'test': 3})
|
||||
|
||||
self.assertEqual(ttk._val_or_dict(tk, {'test': None}, 'x:y'),
|
||||
'test val')
|
||||
|
||||
self.assertEqual(ttk._val_or_dict(tk, {'test': 3}, 'x:y'),
|
||||
{'test': 3})
|
||||
|
||||
|
||||
def test_convert_stringval(self):
|
||||
tests = (
|
||||
(0, 0), ('09', 9), ('a', 'a'), ('áÚ', 'áÚ'), ([], '[]'),
|
||||
(None, 'None')
|
||||
)
|
||||
for orig, expected in tests:
|
||||
self.assertEqual(ttk._convert_stringval(orig), expected)
|
||||
|
||||
|
||||
class TclObjsToPyTest(unittest.TestCase):
|
||||
|
||||
def test_unicode(self):
|
||||
adict = {'opt': 'välúè'}
|
||||
self.assertEqual(ttk.tclobjs_to_py(adict), {'opt': 'välúè'})
|
||||
|
||||
adict['opt'] = MockTclObj(adict['opt'])
|
||||
self.assertEqual(ttk.tclobjs_to_py(adict), {'opt': 'välúè'})
|
||||
|
||||
def test_multivalues(self):
|
||||
adict = {'opt': [1, 2, 3, 4]}
|
||||
self.assertEqual(ttk.tclobjs_to_py(adict), {'opt': [1, 2, 3, 4]})
|
||||
|
||||
adict['opt'] = [1, 'xm', 3]
|
||||
self.assertEqual(ttk.tclobjs_to_py(adict), {'opt': [1, 'xm', 3]})
|
||||
|
||||
adict['opt'] = (MockStateSpec('a', 'b'), 'válũè')
|
||||
self.assertEqual(ttk.tclobjs_to_py(adict),
|
||||
{'opt': [('a', 'b', 'válũè')]})
|
||||
|
||||
self.assertEqual(ttk.tclobjs_to_py({'x': ['y z']}),
|
||||
{'x': ['y z']})
|
||||
|
||||
def test_nosplit(self):
|
||||
self.assertEqual(ttk.tclobjs_to_py({'text': 'some text'}),
|
||||
{'text': 'some text'})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Vendored
+22
@@ -577,6 +577,28 @@ class TestTurtleScreen(unittest.TestCase):
|
||||
s.update.assert_not_called()
|
||||
s.update.assert_called_once()
|
||||
|
||||
def test_update_is_not_reentrant(self):
|
||||
# ondrag(goto) reenters _update() while cv.update() processes events;
|
||||
# without a guard this recurses without bound (gh-50966).
|
||||
s = turtle.TurtleScreen(cv=unittest.mock.MagicMock())
|
||||
depth = max_depth = 0
|
||||
|
||||
def reenter():
|
||||
nonlocal depth, max_depth
|
||||
depth += 1
|
||||
max_depth = max(max_depth, depth)
|
||||
if depth < 50:
|
||||
s._update() # as an event handler would
|
||||
depth -= 1
|
||||
|
||||
s.cv.update.reset_mock() # ignore calls made during construction
|
||||
s.cv.update.side_effect = reenter
|
||||
s._update()
|
||||
# cv.update() runs once; reentrant calls only flush idle tasks.
|
||||
self.assertEqual(s.cv.update.call_count, 1)
|
||||
self.assertEqual(max_depth, 1)
|
||||
self.assertTrue(s.cv.update_idletasks.called)
|
||||
|
||||
|
||||
class TestTurtle(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 405 B |
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1020 B |
Vendored
BIN
Binary file not shown.
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
#define python_width 16
|
||||
#define python_height 16
|
||||
static char python_bits[] = {
|
||||
0xDF, 0xFE, 0x8F, 0xFD, 0x5F, 0xFB, 0xAB, 0xFE, 0xB5, 0x8D, 0xDA, 0x8F,
|
||||
0xA5, 0x86, 0xFA, 0x83, 0x1A, 0x80, 0x0D, 0x80, 0x0D, 0x80, 0x0F, 0xE0,
|
||||
0x0F, 0xF8, 0x0F, 0xF8, 0x0F, 0xFC, 0xFF, 0xFF, };
|
||||
Vendored
+245
-86
@@ -3,7 +3,7 @@
|
||||
Tkinter provides classes which allow the display, positioning and
|
||||
control of widgets. Toplevel widgets are Tk and Toplevel. Other
|
||||
widgets are Frame, Label, Entry, Text, Canvas, Button, Radiobutton,
|
||||
Checkbutton, Scale, Listbox, Scrollbar, OptionMenu, Spinbox
|
||||
Checkbutton, Scale, Listbox, Scrollbar, OptionMenu, Spinbox,
|
||||
LabelFrame and PanedWindow.
|
||||
|
||||
Properties of the widgets are specified with keyword arguments.
|
||||
@@ -255,6 +255,10 @@ class Event:
|
||||
type - type of the event as a number
|
||||
widget - widget in which the event occurred
|
||||
delta - delta of wheel movement (MouseWheel)
|
||||
detail - certain fixed strings (see Tcl/Tk documentation)
|
||||
(Enter, Leave, FocusIn, FocusOut, ConfigureRequest)
|
||||
user_data - data string which was passed to event_generate() or empty
|
||||
string (VirtualEvent)
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
@@ -501,12 +505,14 @@ class Variable:
|
||||
Return the name of the callback.
|
||||
|
||||
This deprecated method wraps a deprecated Tcl method removed
|
||||
in Tcl 9.0. Use trace_add() instead.
|
||||
in Tcl 9.0 and will be removed in Python 3.17. Use trace_add()
|
||||
instead.
|
||||
"""
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"trace_variable() is deprecated and not supported with Tcl 9; "
|
||||
"use trace_add() instead.",
|
||||
"trace_variable() is deprecated and will be removed in Python "
|
||||
"3.17; use trace_add() instead. It is not supported with "
|
||||
"Tcl 9.",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
cbname = self._register(callback)
|
||||
self._tk.call("trace", "variable", self._name, mode, cbname)
|
||||
@@ -521,12 +527,14 @@ class Variable:
|
||||
CBNAME is the name of the callback returned from trace_variable or trace.
|
||||
|
||||
This deprecated method wraps a deprecated Tcl method removed
|
||||
in Tcl 9.0. Use trace_remove() instead.
|
||||
in Tcl 9.0 and will be removed in Python 3.17. Use trace_remove()
|
||||
instead.
|
||||
"""
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"trace_vdelete() is deprecated and not supported with Tcl 9; "
|
||||
"use trace_remove() instead.",
|
||||
"trace_vdelete() is deprecated and will be removed in Python "
|
||||
"3.17; use trace_remove() instead. It is not supported with "
|
||||
"Tcl 9.",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
self._tk.call("trace", "vdelete", self._name, mode, cbname)
|
||||
cbname = self._tk.splitlist(cbname)[0]
|
||||
@@ -544,12 +552,14 @@ class Variable:
|
||||
"""Return all trace callback information.
|
||||
|
||||
This deprecated method wraps a deprecated Tcl method removed
|
||||
in Tcl 9.0. Use trace_info() instead.
|
||||
in Tcl 9.0 and will be removed in Python 3.17. Use trace_info()
|
||||
instead.
|
||||
"""
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"trace_vinfo() is deprecated and not supported with Tcl 9; "
|
||||
"use trace_info() instead.",
|
||||
"trace_vinfo() is deprecated and will be removed in Python "
|
||||
"3.17; use trace_info() instead. It is not supported with "
|
||||
"Tcl 9.",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return [self._tk.splitlist(x) for x in self._tk.splitlist(
|
||||
self._tk.call("trace", "vinfo", self._name))]
|
||||
@@ -817,7 +827,10 @@ class Misc:
|
||||
the focus."""
|
||||
name = self.tk.call('focus')
|
||||
if name == 'none' or not name: return None
|
||||
return self._nametowidget(name)
|
||||
try:
|
||||
return self._nametowidget(name)
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def focus_displayof(self):
|
||||
"""Return the widget which has currently the focus on the
|
||||
@@ -826,14 +839,20 @@ class Misc:
|
||||
Return None if the application does not have the focus."""
|
||||
name = self.tk.call('focus', '-displayof', self._w)
|
||||
if name == 'none' or not name: return None
|
||||
return self._nametowidget(name)
|
||||
try:
|
||||
return self._nametowidget(name)
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def focus_lastfor(self):
|
||||
"""Return the widget which would have the focus if top level
|
||||
for this widget gets the focus from the window manager."""
|
||||
name = self.tk.call('focus', '-lastfor', self._w)
|
||||
if name == 'none' or not name: return None
|
||||
return self._nametowidget(name)
|
||||
try:
|
||||
return self._nametowidget(name)
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def tk_focusFollowsMouse(self):
|
||||
"""The widget under mouse will get automatically focus. Can not
|
||||
@@ -1236,7 +1255,10 @@ class Misc:
|
||||
+ self._displayof(displayof) + (rootX, rootY)
|
||||
name = self.tk.call(args)
|
||||
if not name: return None
|
||||
return self._nametowidget(name)
|
||||
try:
|
||||
return self._nametowidget(name)
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def winfo_depth(self):
|
||||
"""Return the number of bits per pixel."""
|
||||
@@ -1499,13 +1521,26 @@ class Misc:
|
||||
else:
|
||||
self.tk.call('bindtags', self._w, tagList)
|
||||
|
||||
def _bind(self, what, sequence, func, add, needcleanup=1):
|
||||
def _delete_bind_commands(self, *what):
|
||||
lines = self.tk.call(what).split('\n')
|
||||
p = re.compile(r'if \{"\[([^ ]+) .*\]" == "break"\} break')
|
||||
for line in lines:
|
||||
m = p.fullmatch(line)
|
||||
if m:
|
||||
funcid = m[1]
|
||||
try:
|
||||
self.deletecommand(funcid)
|
||||
except TclError:
|
||||
pass
|
||||
|
||||
def _bind(self, what, sequence, func, add):
|
||||
"""Internal function."""
|
||||
if isinstance(func, str):
|
||||
self.tk.call(what + (sequence, func))
|
||||
elif func:
|
||||
funcid = self._register(func, self._substitute,
|
||||
needcleanup)
|
||||
if not add:
|
||||
self._delete_bind_commands(*what, sequence)
|
||||
funcid = self._register(func, self._substitute, needcleanup=True)
|
||||
cmd = ('%sif {"[%s %s]" == "break"} break\n'
|
||||
%
|
||||
(add and '+' or '',
|
||||
@@ -1538,7 +1573,7 @@ class Misc:
|
||||
<Alt-A> for pressing A and the Alt key (KeyPress can be omitted).
|
||||
An event pattern can also be a virtual event of the form
|
||||
<<AString>> where AString can be arbitrary. This
|
||||
event can be generated by event_generate.
|
||||
event can be generated by event_generate().
|
||||
If events are concatenated they must appear shortly
|
||||
after each other.
|
||||
|
||||
@@ -1571,6 +1606,7 @@ class Misc:
|
||||
|
||||
def _unbind(self, what, funcid=None):
|
||||
if funcid is None:
|
||||
self._delete_bind_commands(*what)
|
||||
self.tk.call(*what, '')
|
||||
else:
|
||||
lines = self.tk.call(what).split('\n')
|
||||
@@ -1587,7 +1623,7 @@ class Misc:
|
||||
An additional boolean parameter ADD specifies whether FUNC will
|
||||
be called additionally to the other bound function or whether
|
||||
it will replace the previous function. See bind for the return value."""
|
||||
return self._root()._bind(('bind', 'all'), sequence, func, add, True)
|
||||
return self._root()._bind(('bind', 'all'), sequence, func, add)
|
||||
|
||||
def unbind_all(self, sequence):
|
||||
"""Unbind for all widgets for event SEQUENCE all functions."""
|
||||
@@ -1601,7 +1637,7 @@ class Misc:
|
||||
whether it will replace the previous function. See bind for
|
||||
the return value."""
|
||||
|
||||
return self._root()._bind(('bind', className), sequence, func, add, True)
|
||||
return self._root()._bind(('bind', className), sequence, func, add)
|
||||
|
||||
def unbind_class(self, className, sequence):
|
||||
"""Unbind for all widgets with bindtag CLASSNAME for event SEQUENCE
|
||||
@@ -1688,7 +1724,16 @@ class Misc:
|
||||
for n in name:
|
||||
if not n:
|
||||
break
|
||||
w = w.children[n]
|
||||
try:
|
||||
w = w.children[n]
|
||||
except KeyError:
|
||||
# Menu clones (a menu used as a menubar or a cascade) get
|
||||
# auto-generated names where each path component is the
|
||||
# original name prefixed with one or more '#' clone markers.
|
||||
# Map such a name back to the original widget.
|
||||
if not n.startswith('#'):
|
||||
raise
|
||||
w = w.children[n.rsplit('#', 1)[-1]]
|
||||
|
||||
return w
|
||||
|
||||
@@ -1723,7 +1768,7 @@ class Misc:
|
||||
w = self
|
||||
while w.master is not None: w = w.master
|
||||
return w
|
||||
_subst_format = ('%#', '%b', '%f', '%h', '%k',
|
||||
_subst_format = ('%#', '%b', '%d', '%f', '%h', '%k',
|
||||
'%s', '%t', '%w', '%x', '%y',
|
||||
'%A', '%E', '%K', '%N', '%W', '%T', '%X', '%Y', '%D')
|
||||
_subst_format_str = " ".join(_subst_format)
|
||||
@@ -1744,11 +1789,14 @@ class Misc:
|
||||
if any(isinstance(s, tuple) for s in args):
|
||||
args = [s[0] if isinstance(s, tuple) and len(s) == 1 else s
|
||||
for s in args]
|
||||
nsign, b, f, h, k, s, t, w, x, y, A, E, K, N, W, T, X, Y, D = args
|
||||
# Missing: (a, c, d, m, o, v, B, R)
|
||||
nsign, b, d, f, h, k, s, t, w, x, y, A, E, K, N, W, T, X, Y, D = args
|
||||
# Missing: (a, c, m, o, v, B, R)
|
||||
e = Event()
|
||||
# serial field: valid for all events
|
||||
# number of button: ButtonPress and ButtonRelease events only
|
||||
# detail: for Enter, Leave, FocusIn, FocusOut and ConfigureRequest
|
||||
# events certain fixed strings (see Tcl/Tk documentation)
|
||||
# user_data: data string from a virtual event or an empty string
|
||||
# height field: Configure, ConfigureRequest, Create,
|
||||
# ResizeRequest, and Expose events only
|
||||
# keycode field: KeyPress and KeyRelease events only
|
||||
@@ -1762,6 +1810,12 @@ class Misc:
|
||||
# KeyRelease, and Motion events
|
||||
e.serial = getint(nsign)
|
||||
e.num = getint_event(b)
|
||||
if T == EventType.VirtualEvent:
|
||||
e.user_data = d
|
||||
e.detail = '??'
|
||||
else:
|
||||
e.user_data = '??'
|
||||
e.detail = d
|
||||
try: e.focus = getboolean(f)
|
||||
except TclError: pass
|
||||
e.height = getint_event(h)
|
||||
@@ -1848,6 +1902,7 @@ class Misc:
|
||||
return self.tk.call(self._w, 'cget', '-' + key)
|
||||
|
||||
__getitem__ = cget
|
||||
__iter__ = None # prevent using __getitem__ for iteration
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.configure({key: value})
|
||||
@@ -1884,9 +1939,21 @@ class Misc:
|
||||
|
||||
propagate = pack_propagate
|
||||
|
||||
def pack_slaves(self):
|
||||
def pack_content(self):
|
||||
"""Returns a list of all of the content widgets in the packing order
|
||||
for this container."""
|
||||
try:
|
||||
res = self.tk.call('pack', 'content', self._w)
|
||||
except TclError:
|
||||
if self.info_patchlevel() >= (8, 6):
|
||||
raise
|
||||
res = self.tk.call('pack', 'slaves', self._w)
|
||||
return [self._nametowidget(x) for x in self.tk.splitlist(res)]
|
||||
|
||||
content = pack_content
|
||||
|
||||
def pack_slaves(self):
|
||||
"""Synonym for pack_content()."""
|
||||
return [self._nametowidget(x) for x in
|
||||
self.tk.splitlist(
|
||||
self.tk.call('pack', 'slaves', self._w))]
|
||||
@@ -1894,9 +1961,19 @@ class Misc:
|
||||
slaves = pack_slaves
|
||||
|
||||
# Place method that applies to the container widget
|
||||
def place_slaves(self):
|
||||
def place_content(self):
|
||||
"""Returns a list of all the content widgets for which this widget is
|
||||
the container."""
|
||||
try:
|
||||
res = self.tk.call('place', 'content', self._w)
|
||||
except TclError:
|
||||
if self.info_patchlevel() >= (8, 6):
|
||||
raise
|
||||
res = self.tk.call('place', 'slaves', self._w)
|
||||
return [self._nametowidget(x) for x in self.tk.splitlist(res)]
|
||||
|
||||
def place_slaves(self):
|
||||
"""Synonym for place_content()."""
|
||||
return [self._nametowidget(x) for x in
|
||||
self.tk.splitlist(
|
||||
self.tk.call(
|
||||
@@ -2017,7 +2094,7 @@ class Misc:
|
||||
|
||||
size = grid_size
|
||||
|
||||
def grid_slaves(self, row=None, column=None):
|
||||
def grid_content(self, row=None, column=None):
|
||||
"""Returns a list of the content widgets.
|
||||
|
||||
If no arguments are supplied, a list of all of the content in this
|
||||
@@ -2026,6 +2103,21 @@ class Misc:
|
||||
column is returned.
|
||||
"""
|
||||
args = ()
|
||||
if row is not None:
|
||||
args = args + ('-row', row)
|
||||
if column is not None:
|
||||
args = args + ('-column', column)
|
||||
try:
|
||||
res = self.tk.call('grid', 'content', self._w, *args)
|
||||
except TclError:
|
||||
if self.info_patchlevel() >= (8, 6):
|
||||
raise
|
||||
res = self.tk.call('grid', 'slaves', self._w, *args)
|
||||
return [self._nametowidget(x) for x in self.tk.splitlist(res)]
|
||||
|
||||
def grid_slaves(self, row=None, column=None):
|
||||
"""Synonym for grid_content()."""
|
||||
args = ()
|
||||
if row is not None:
|
||||
args = args + ('-row', row)
|
||||
if column is not None:
|
||||
@@ -2344,7 +2436,7 @@ class Wm:
|
||||
|
||||
def wm_iconposition(self, x=None, y=None):
|
||||
"""Set the position of the icon of this widget to X and Y. Return
|
||||
a tuple of the current values of X and X if None is given."""
|
||||
a tuple of the current values of X and Y if None is given."""
|
||||
return self._getints(self.tk.call(
|
||||
'wm', 'iconposition', self._w, x, y))
|
||||
|
||||
@@ -2547,11 +2639,13 @@ class Tk(Misc, Wm):
|
||||
if os.path.isfile(class_tcl):
|
||||
self.tk.call('source', class_tcl)
|
||||
if os.path.isfile(class_py):
|
||||
exec(open(class_py).read(), dir)
|
||||
with open(class_py, 'rb') as f:
|
||||
exec(f.read(), dir)
|
||||
if os.path.isfile(base_tcl):
|
||||
self.tk.call('source', base_tcl)
|
||||
if os.path.isfile(base_py):
|
||||
exec(open(base_py).read(), dir)
|
||||
with open(base_py, 'rb') as f:
|
||||
exec(f.read(), dir)
|
||||
|
||||
def report_callback_exception(self, exc, val, tb):
|
||||
"""Report callback exception on sys.stderr.
|
||||
@@ -2640,6 +2734,7 @@ class Pack:
|
||||
|
||||
info = pack_info
|
||||
propagate = pack_propagate = Misc.pack_propagate
|
||||
content = pack_content = Misc.pack_content
|
||||
slaves = pack_slaves = Misc.pack_slaves
|
||||
|
||||
|
||||
@@ -2697,6 +2792,7 @@ class Place:
|
||||
return d
|
||||
|
||||
info = place_info
|
||||
content = place_content = Misc.place_content
|
||||
slaves = place_slaves = Misc.place_slaves
|
||||
|
||||
|
||||
@@ -2752,6 +2848,7 @@ class Grid:
|
||||
propagate = grid_propagate = Misc.grid_propagate
|
||||
rowconfigure = grid_rowconfigure = Misc.grid_rowconfigure
|
||||
size = grid_size = Misc.grid_size
|
||||
content = grid_content = Misc.grid_content
|
||||
slaves = grid_slaves = Misc.grid_slaves
|
||||
|
||||
|
||||
@@ -2834,10 +2931,11 @@ class Toplevel(BaseWidget, Wm):
|
||||
def __init__(self, master=None, cnf={}, **kw):
|
||||
"""Construct a toplevel widget with the parent MASTER.
|
||||
|
||||
Valid option names: background, bd, bg, borderwidth, class,
|
||||
colormap, container, cursor, height, highlightbackground,
|
||||
highlightcolor, highlightthickness, menu, relief, screen, takefocus,
|
||||
use, visual, width."""
|
||||
Valid option names: background, backgroundimage (Tk 9.0+), bd, bg,
|
||||
bgimg (Tk 9.0+), borderwidth, class, colormap, container,
|
||||
cursor, height, highlightbackground, highlightcolor,
|
||||
highlightthickness, menu, padx, pady, relief, screen,
|
||||
takefocus, tile (Tk 9.0+), use, visual, width."""
|
||||
if kw:
|
||||
cnf = _cnfmerge((cnf, kw))
|
||||
extra = ()
|
||||
@@ -2957,7 +3055,7 @@ class Canvas(Widget, XView, YView):
|
||||
"""Add tag NEWTAG to all items with TAGORID."""
|
||||
self.addtag(newtag, 'withtag', tagOrId)
|
||||
|
||||
def bbox(self, *args):
|
||||
def bbox(self, *args): # overrides Misc.bbox
|
||||
"""Return a tuple of X1,Y1,X2,Y2 coordinates for a rectangle
|
||||
which encloses all items with tags specified as arguments."""
|
||||
return self._getints(
|
||||
@@ -3096,7 +3194,7 @@ class Canvas(Widget, XView, YView):
|
||||
"""Return all items with TAGORID."""
|
||||
return self.find('withtag', tagOrId)
|
||||
|
||||
def focus(self, *args):
|
||||
def focus(self, *args): # overrides Misc.focus
|
||||
"""Set focus to the first item specified in ARGS."""
|
||||
return self.tk.call((self._w, 'focus') + args)
|
||||
|
||||
@@ -3142,7 +3240,7 @@ class Canvas(Widget, XView, YView):
|
||||
(optional below another item)."""
|
||||
self.tk.call((self._w, 'lower') + args)
|
||||
|
||||
lower = tag_lower
|
||||
lower = tag_lower # overrides Misc.lower
|
||||
|
||||
def move(self, *args):
|
||||
"""Move an item TAGORID given in ARGS."""
|
||||
@@ -3170,7 +3268,7 @@ class Canvas(Widget, XView, YView):
|
||||
(optional above another item)."""
|
||||
self.tk.call((self._w, 'raise') + args)
|
||||
|
||||
lift = tkraise = tag_raise
|
||||
lift = tkraise = tag_raise # overrides Misc.tkraise
|
||||
|
||||
def scale(self, *args):
|
||||
"""Scale item TAGORID with XORIGIN, YORIGIN, XSCALE, YSCALE."""
|
||||
@@ -3220,12 +3318,13 @@ class Checkbutton(Widget):
|
||||
"""Construct a checkbutton widget with the parent MASTER.
|
||||
|
||||
Valid option names: activebackground, activeforeground, anchor,
|
||||
background, bd, bg, bitmap, borderwidth, command, cursor,
|
||||
disabledforeground, fg, font, foreground, height,
|
||||
highlightbackground, highlightcolor, highlightthickness, image,
|
||||
indicatoron, justify, offvalue, onvalue, padx, pady, relief,
|
||||
selectcolor, selectimage, state, takefocus, text, textvariable,
|
||||
underline, variable, width, wraplength."""
|
||||
background, bd, bg, bitmap, borderwidth, command, compound,
|
||||
cursor, disabledforeground, fg, font, foreground, height,
|
||||
highlightbackground, highlightcolor, highlightthickness,
|
||||
image, indicatoron, justify, offrelief, offvalue, onvalue,
|
||||
overrelief, padx, pady, relief, selectcolor, selectimage,
|
||||
state, takefocus, text, textvariable, tristateimage,
|
||||
tristatevalue, underline, variable, width, wraplength."""
|
||||
Widget.__init__(self, master, 'checkbutton', cnf, kw)
|
||||
|
||||
def _setup(self, master, cnf):
|
||||
@@ -3269,13 +3368,15 @@ class Entry(Widget, XView):
|
||||
"""Construct an entry widget with the parent MASTER.
|
||||
|
||||
Valid option names: background, bd, bg, borderwidth, cursor,
|
||||
exportselection, fg, font, foreground, highlightbackground,
|
||||
highlightcolor, highlightthickness, insertbackground,
|
||||
insertborderwidth, insertofftime, insertontime, insertwidth,
|
||||
invalidcommand, invcmd, justify, relief, selectbackground,
|
||||
selectborderwidth, selectforeground, show, state, takefocus,
|
||||
textvariable, validate, validatecommand, vcmd, width,
|
||||
xscrollcommand."""
|
||||
disabledbackground, disabledforeground, exportselection, fg,
|
||||
font, foreground, highlightbackground, highlightcolor,
|
||||
highlightthickness, insertbackground, insertborderwidth,
|
||||
insertofftime, insertontime, insertwidth, invalidcommand,
|
||||
invcmd, justify, locale (Tk 9.1+), placeholder (Tk 9.0+),
|
||||
placeholderforeground (Tk 9.0+), readonlybackground, relief,
|
||||
selectbackground, selectborderwidth, selectforeground, show,
|
||||
state, takefocus, textvariable, validate, validatecommand,
|
||||
vcmd, width, xscrollcommand."""
|
||||
Widget.__init__(self, master, 'entry', cnf, kw)
|
||||
|
||||
def delete(self, first, last=None):
|
||||
@@ -3315,7 +3416,7 @@ class Entry(Widget, XView):
|
||||
|
||||
select_adjust = selection_adjust
|
||||
|
||||
def selection_clear(self):
|
||||
def selection_clear(self): # overrides Misc.selection_clear
|
||||
"""Clear the selection if it is in this widget."""
|
||||
self.tk.call(self._w, 'selection', 'clear')
|
||||
|
||||
@@ -3354,9 +3455,11 @@ class Frame(Widget):
|
||||
def __init__(self, master=None, cnf={}, **kw):
|
||||
"""Construct a frame widget with the parent MASTER.
|
||||
|
||||
Valid option names: background, bd, bg, borderwidth, class,
|
||||
colormap, container, cursor, height, highlightbackground,
|
||||
highlightcolor, highlightthickness, relief, takefocus, visual, width."""
|
||||
Valid option names: background, backgroundimage (Tk 9.0+), bd, bg,
|
||||
bgimg (Tk 9.0+), borderwidth, class, colormap, container,
|
||||
cursor, height, highlightbackground, highlightcolor,
|
||||
highlightthickness, padx, pady, relief, takefocus, tile (Tk
|
||||
9.0+), visual, width."""
|
||||
cnf = _cnfmerge((cnf, kw))
|
||||
extra = ()
|
||||
if 'class_' in cnf:
|
||||
@@ -3386,7 +3489,8 @@ class Label(Widget):
|
||||
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
|
||||
height, state, width
|
||||
compound, height, state,
|
||||
textangle (Tk 9.1+), width
|
||||
|
||||
"""
|
||||
Widget.__init__(self, master, 'label', cnf, kw)
|
||||
@@ -3398,18 +3502,21 @@ class Listbox(Widget, XView, YView):
|
||||
def __init__(self, master=None, cnf={}, **kw):
|
||||
"""Construct a listbox widget with the parent MASTER.
|
||||
|
||||
Valid option names: background, bd, bg, borderwidth, cursor,
|
||||
exportselection, fg, font, foreground, height, highlightbackground,
|
||||
highlightcolor, highlightthickness, relief, selectbackground,
|
||||
selectborderwidth, selectforeground, selectmode, setgrid, takefocus,
|
||||
width, xscrollcommand, yscrollcommand, listvariable."""
|
||||
Valid option names: activestyle, background, bd, bg, borderwidth,
|
||||
cursor, disabledforeground, exportselection, fg, font,
|
||||
foreground, height, highlightbackground, highlightcolor,
|
||||
highlightthickness, inactiveselectbackground (Tk 9.1+),
|
||||
inactiveselectforeground (Tk 9.1+), justify, listvariable,
|
||||
relief, selectbackground, selectborderwidth, selectforeground,
|
||||
selectmode, setgrid, state, takefocus, width, xscrollcommand,
|
||||
yscrollcommand."""
|
||||
Widget.__init__(self, master, 'listbox', cnf, kw)
|
||||
|
||||
def activate(self, index):
|
||||
"""Activate item identified by INDEX."""
|
||||
self.tk.call(self._w, 'activate', index)
|
||||
|
||||
def bbox(self, index):
|
||||
def bbox(self, index): # overrides Misc.bbox
|
||||
"""Return a tuple of X1,Y1,X2,Y2 coordinates for a rectangle
|
||||
which encloses the item identified by the given index."""
|
||||
return self._getints(self.tk.call(self._w, 'bbox', index)) or None
|
||||
@@ -3465,7 +3572,7 @@ class Listbox(Widget, XView, YView):
|
||||
|
||||
select_anchor = selection_anchor
|
||||
|
||||
def selection_clear(self, first, last=None):
|
||||
def selection_clear(self, first, last=None): # overrides Misc.selection_clear
|
||||
"""Clear the selection from FIRST to LAST (included)."""
|
||||
self.tk.call(self._w,
|
||||
'selection', 'clear', first, last)
|
||||
@@ -3486,7 +3593,7 @@ class Listbox(Widget, XView, YView):
|
||||
|
||||
select_set = selection_set
|
||||
|
||||
def size(self):
|
||||
def size(self): # overrides Misc.size
|
||||
"""Return the number of elements in the listbox."""
|
||||
return self.tk.getint(self.tk.call(self._w, 'size'))
|
||||
|
||||
@@ -3512,7 +3619,8 @@ class Menu(Widget):
|
||||
"""Construct menu widget with the parent MASTER.
|
||||
|
||||
Valid option names: activebackground, activeborderwidth,
|
||||
activeforeground, background, bd, bg, borderwidth, cursor,
|
||||
activeforeground, activerelief (Tk 9.0+), background, bd, bg,
|
||||
borderwidth, cursor,
|
||||
disabledforeground, fg, font, foreground, postcommand, relief,
|
||||
selectcolor, takefocus, tearoff, tearoffcommand, title, type."""
|
||||
Widget.__init__(self, master, 'menu', cnf, kw)
|
||||
@@ -3642,6 +3750,14 @@ class Menubutton(Widget):
|
||||
"""Menubutton widget, obsolete since Tk8.0."""
|
||||
|
||||
def __init__(self, master=None, cnf={}, **kw):
|
||||
"""Construct a menubutton widget with the parent MASTER.
|
||||
|
||||
Valid option names: activebackground, activeforeground, anchor,
|
||||
background, bd, bg, bitmap, borderwidth, compound, cursor,
|
||||
direction, disabledforeground, fg, font, foreground, height,
|
||||
highlightbackground, highlightcolor, highlightthickness,
|
||||
image, indicatoron, justify, menu, padx, pady, relief, state,
|
||||
takefocus, text, textvariable, underline, width, wraplength."""
|
||||
Widget.__init__(self, master, 'menubutton', cnf, kw)
|
||||
|
||||
|
||||
@@ -3649,6 +3765,12 @@ class Message(Widget):
|
||||
"""Message widget to display multiline text. Obsolete since Label does it too."""
|
||||
|
||||
def __init__(self, master=None, cnf={}, **kw):
|
||||
"""Construct a message widget with the parent MASTER.
|
||||
|
||||
Valid option names: anchor, aspect, background, bd, bg, borderwidth,
|
||||
cursor, fg, font, foreground, highlightbackground,
|
||||
highlightcolor, highlightthickness, justify, padx, pady,
|
||||
relief, takefocus, text, textvariable, width."""
|
||||
Widget.__init__(self, master, 'message', cnf, kw)
|
||||
|
||||
|
||||
@@ -3659,12 +3781,13 @@ class Radiobutton(Widget):
|
||||
"""Construct a radiobutton widget with the parent MASTER.
|
||||
|
||||
Valid option names: activebackground, activeforeground, anchor,
|
||||
background, bd, bg, bitmap, borderwidth, command, cursor,
|
||||
disabledforeground, fg, font, foreground, height,
|
||||
highlightbackground, highlightcolor, highlightthickness, image,
|
||||
indicatoron, justify, padx, pady, relief, selectcolor, selectimage,
|
||||
state, takefocus, text, textvariable, underline, value, variable,
|
||||
width, wraplength."""
|
||||
background, bd, bg, bitmap, borderwidth, command, compound,
|
||||
cursor, disabledforeground, fg, font, foreground, height,
|
||||
highlightbackground, highlightcolor, highlightthickness,
|
||||
image, indicatoron, justify, offrelief, overrelief, padx,
|
||||
pady, relief, selectcolor, selectimage, state, takefocus,
|
||||
text, textvariable, tristateimage, tristatevalue, underline,
|
||||
value, variable, width, wraplength."""
|
||||
Widget.__init__(self, master, 'radiobutton', cnf, kw)
|
||||
|
||||
def deselect(self):
|
||||
@@ -3795,14 +3918,16 @@ class Text(Widget, XView, YView):
|
||||
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
|
||||
autoseparators, height, maxundo,
|
||||
spacing1, spacing2, spacing3,
|
||||
state, tabs, undo, width, wrap,
|
||||
autoseparators, blockcursor, endline,
|
||||
height, inactiveselectbackground,
|
||||
insertunfocussed, locale (Tk 9.1+), maxundo,
|
||||
spacing1, spacing2, spacing3, startline,
|
||||
state, tabs, tabstyle, undo, width, wrap,
|
||||
|
||||
"""
|
||||
Widget.__init__(self, master, 'text', cnf, kw)
|
||||
|
||||
def bbox(self, index):
|
||||
def bbox(self, index): # overrides Misc.bbox
|
||||
"""Return a tuple of (x,y,width,height) which gives the bounding
|
||||
box of the visible part of the character at the given index."""
|
||||
return self._getints(
|
||||
@@ -3987,7 +4112,7 @@ class Text(Widget, XView, YView):
|
||||
self._w, "image", "create", index,
|
||||
*self._options(cnf, kw))
|
||||
|
||||
def image_names(self):
|
||||
def image_names(self): # overrides Misc.image_names
|
||||
"""Return all names of embedded images in this widget."""
|
||||
return self.tk.call(self._w, "image", "names")
|
||||
|
||||
@@ -4059,8 +4184,9 @@ class Text(Widget, XView, YView):
|
||||
self.tk.call(self._w, 'scan', 'dragto', x, y)
|
||||
|
||||
def search(self, pattern, index, stopindex=None,
|
||||
forwards=None, backwards=None, exact=None,
|
||||
regexp=None, nocase=None, count=None, elide=None):
|
||||
forwards=None, backwards=None, exact=None,
|
||||
regexp=None, nocase=None, count=None,
|
||||
elide=None, *, nolinestop=None, strictlimits=None):
|
||||
"""Search PATTERN beginning from INDEX until STOPINDEX.
|
||||
Return the index of the first character of a match or an
|
||||
empty string."""
|
||||
@@ -4072,12 +4198,39 @@ class Text(Widget, XView, YView):
|
||||
if nocase: args.append('-nocase')
|
||||
if elide: args.append('-elide')
|
||||
if count: args.append('-count'); args.append(count)
|
||||
if nolinestop: args.append('-nolinestop')
|
||||
if strictlimits: args.append('-strictlimits')
|
||||
if pattern and pattern[0] == '-': args.append('--')
|
||||
args.append(pattern)
|
||||
args.append(index)
|
||||
if stopindex: args.append(stopindex)
|
||||
if stopindex is not None: args.append(stopindex)
|
||||
return str(self.tk.call(tuple(args)))
|
||||
|
||||
def search_all(self, pattern, index, stopindex=None, *,
|
||||
forwards=None, backwards=None, exact=None,
|
||||
regexp=None, nocase=None, count=None,
|
||||
elide=None, nolinestop=None, overlap=None,
|
||||
strictlimits=None):
|
||||
"""Search all occurrences of PATTERN from INDEX to STOPINDEX.
|
||||
Return a tuple of indices where matches begin."""
|
||||
args = [self._w, 'search', '-all']
|
||||
if forwards: args.append('-forwards')
|
||||
if backwards: args.append('-backwards')
|
||||
if exact: args.append('-exact')
|
||||
if regexp: args.append('-regexp')
|
||||
if nocase: args.append('-nocase')
|
||||
if elide: args.append('-elide')
|
||||
if count: args.append('-count'); args.append(count)
|
||||
if nolinestop: args.append('-nolinestop')
|
||||
if overlap: args.append('-overlap')
|
||||
if strictlimits: args.append('-strictlimits')
|
||||
if pattern and pattern[0] == '-': args.append('--')
|
||||
args.append(pattern)
|
||||
args.append(index)
|
||||
if stopindex is not None: args.append(stopindex)
|
||||
result = self.tk.call(tuple(args))
|
||||
return self.tk.splitlist(result)
|
||||
|
||||
def see(self, index):
|
||||
"""Scroll such that the character at INDEX is visible."""
|
||||
self.tk.call(self._w, 'see', index)
|
||||
@@ -4291,6 +4444,8 @@ class Image:
|
||||
def __getitem__(self, key):
|
||||
return self.tk.call(self.name, 'configure', '-'+key)
|
||||
|
||||
__iter__ = None # prevent using __getitem__ for iteration
|
||||
|
||||
def configure(self, **kw):
|
||||
"""Configure the image."""
|
||||
res = ()
|
||||
@@ -4625,14 +4780,17 @@ class Spinbox(Widget, XView):
|
||||
buttondownrelief, buttonuprelief,
|
||||
command, disabledbackground,
|
||||
disabledforeground, format, from,
|
||||
invalidcommand, increment,
|
||||
invalidcommand, invcmd, increment,
|
||||
locale (Tk 9.1+),
|
||||
placeholder (Tk 9.0+),
|
||||
placeholderforeground (Tk 9.0+),
|
||||
readonlybackground, state, to,
|
||||
validate, validatecommand values,
|
||||
validate, validatecommand, vcmd, values,
|
||||
width, wrap,
|
||||
"""
|
||||
Widget.__init__(self, master, 'spinbox', cnf, kw)
|
||||
|
||||
def bbox(self, index):
|
||||
def bbox(self, index): # overrides Misc.bbox
|
||||
"""Return a tuple of X1,Y1,X2,Y2 coordinates for a
|
||||
rectangle which encloses the character given by index.
|
||||
|
||||
@@ -4741,7 +4899,7 @@ class Spinbox(Widget, XView):
|
||||
"""
|
||||
return self.selection("adjust", index)
|
||||
|
||||
def selection_clear(self):
|
||||
def selection_clear(self): # overrides Misc.selection_clear
|
||||
"""Clear the selection
|
||||
|
||||
If the selection isn't in this widget then the
|
||||
@@ -4816,8 +4974,9 @@ class PanedWindow(Widget):
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
|
||||
handlepad, handlesize, opaqueresize,
|
||||
sashcursor, sashpad, sashrelief,
|
||||
sashwidth, showhandle,
|
||||
proxybackground, proxyborderwidth,
|
||||
proxyrelief, sashcursor, sashpad,
|
||||
sashrelief, sashwidth, showhandle,
|
||||
"""
|
||||
Widget.__init__(self, master, 'panedwindow', cnf, kw)
|
||||
|
||||
@@ -4838,7 +4997,7 @@ class PanedWindow(Widget):
|
||||
"""
|
||||
self.tk.call(self._w, 'forget', child)
|
||||
|
||||
forget = remove
|
||||
forget = remove # overrides Pack.forget
|
||||
|
||||
def identify(self, x, y):
|
||||
"""Identify the panedwindow component at point x, y
|
||||
|
||||
Vendored
+2
@@ -1,3 +1,5 @@
|
||||
"""Interface to the native Tk color selection dialog."""
|
||||
|
||||
# tk common color chooser dialogue
|
||||
#
|
||||
# this module provides an interface to the native color dialogue
|
||||
|
||||
Vendored
+2
@@ -1,3 +1,5 @@
|
||||
"""Base class for the Tk common dialogs."""
|
||||
|
||||
# base class for tk common dialogues
|
||||
#
|
||||
# this module provides a base class for accessing the common
|
||||
|
||||
Vendored
+6
-1
@@ -1,5 +1,7 @@
|
||||
# dialog.py -- Tkinter interface to the tk_dialog script.
|
||||
|
||||
"""Classic Tk dialog box, wrapping the tk_dialog script."""
|
||||
|
||||
from tkinter import _cnfmerge, Widget, TclError, Button, Pack
|
||||
|
||||
__all__ = ["Dialog"]
|
||||
@@ -8,6 +10,8 @@ DIALOG_ICON = 'questhead'
|
||||
|
||||
|
||||
class Dialog(Widget):
|
||||
"""A modal dialog box built from the classic (non-themed) Tk widgets."""
|
||||
|
||||
def __init__(self, master=None, cnf={}, **kw):
|
||||
cnf = _cnfmerge((cnf, kw))
|
||||
self.widgetName = '__dialog__'
|
||||
@@ -21,7 +25,8 @@ class Dialog(Widget):
|
||||
try: Widget.destroy(self)
|
||||
except TclError: pass
|
||||
|
||||
def destroy(self): pass
|
||||
def destroy(self):
|
||||
"""Do nothing; the dialog window is already destroyed."""
|
||||
|
||||
|
||||
def _test():
|
||||
|
||||
Vendored
+23
-4
@@ -1,3 +1,5 @@
|
||||
"""Utilities to help work with fonts in Tkinter."""
|
||||
|
||||
# Tkinter font wrapper
|
||||
#
|
||||
# written by Fredrik Lundh, February 1998
|
||||
@@ -6,7 +8,6 @@
|
||||
import itertools
|
||||
import tkinter
|
||||
|
||||
__version__ = "0.9"
|
||||
__all__ = ["NORMAL", "ROMAN", "BOLD", "ITALIC",
|
||||
"nametofont", "Font", "families", "names"]
|
||||
|
||||
@@ -72,8 +73,15 @@ class Font:
|
||||
root = tkinter._get_default_root('use font')
|
||||
tk = getattr(root, 'tk', root)
|
||||
if font:
|
||||
# get actual settings corresponding to the given font
|
||||
font = tk.splitlist(tk.call("font", "actual", font))
|
||||
# start from the settings of the given font
|
||||
try:
|
||||
# a named font: copy its options, preserving the size,
|
||||
# which can be negative (specified in pixels)
|
||||
font = tk.splitlist(tk.call("font", "configure", font))
|
||||
except tkinter.TclError:
|
||||
# a font description: resolve it ("font configure" only
|
||||
# accepts a font name); this loses a size in pixels
|
||||
font = tk.splitlist(tk.call("font", "actual", font))
|
||||
else:
|
||||
font = self._set(options)
|
||||
if not name:
|
||||
@@ -115,6 +123,8 @@ class Font:
|
||||
def __setitem__(self, key, value):
|
||||
self.configure(**{key: value})
|
||||
|
||||
__iter__ = None # prevent using __getitem__ for iteration
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
if self.delete_font:
|
||||
@@ -124,7 +134,7 @@ class Font:
|
||||
|
||||
def copy(self):
|
||||
"Return a distinct copy of the current font"
|
||||
return Font(self._tk, **self.actual())
|
||||
return Font(self._tk, self.name)
|
||||
|
||||
def actual(self, option=None, displayof=None):
|
||||
"Return actual font attributes"
|
||||
@@ -198,6 +208,15 @@ def names(root=None):
|
||||
return root.tk.splitlist(root.tk.call("font", "names"))
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
if name == "__version__":
|
||||
from warnings import _deprecated
|
||||
|
||||
_deprecated("__version__", remove=(3, 20))
|
||||
return "0.9" # Do not change
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# test stuff
|
||||
|
||||
|
||||
Vendored
+7
-5
@@ -1,3 +1,5 @@
|
||||
"""Interface to the standard Tk message boxes."""
|
||||
|
||||
# tk common message boxes
|
||||
#
|
||||
# this module provides an interface to the native message boxes
|
||||
@@ -99,24 +101,24 @@ def showerror(title=None, message=None, **options):
|
||||
|
||||
|
||||
def askquestion(title=None, message=None, **options):
|
||||
"Ask a question"
|
||||
"Ask a question; return the symbolic name of the selected button"
|
||||
return _show(title, message, QUESTION, YESNO, **options)
|
||||
|
||||
|
||||
def askokcancel(title=None, message=None, **options):
|
||||
"Ask if operation should proceed; return true if the answer is ok"
|
||||
"Ask if operation should proceed; return True if the answer is ok"
|
||||
s = _show(title, message, QUESTION, OKCANCEL, **options)
|
||||
return s == OK
|
||||
|
||||
|
||||
def askyesno(title=None, message=None, **options):
|
||||
"Ask a question; return true if the answer is yes"
|
||||
"Ask a question; return True if the answer is yes"
|
||||
s = _show(title, message, QUESTION, YESNO, **options)
|
||||
return s == YES
|
||||
|
||||
|
||||
def askyesnocancel(title=None, message=None, **options):
|
||||
"Ask a question; return true if the answer is yes, None if cancelled."
|
||||
"Ask a question; return True if the answer is yes, None if cancelled"
|
||||
s = _show(title, message, QUESTION, YESNOCANCEL, **options)
|
||||
# s might be a Tcl index object, so convert it to a string
|
||||
s = str(s)
|
||||
@@ -126,7 +128,7 @@ def askyesnocancel(title=None, message=None, **options):
|
||||
|
||||
|
||||
def askretrycancel(title=None, message=None, **options):
|
||||
"Ask if operation should be retried; return true if the answer is yes"
|
||||
"Ask if operation should be retried; return True if the answer is retry"
|
||||
s = _show(title, message, WARNING, RETRYCANCEL, **options)
|
||||
return s == RETRY
|
||||
|
||||
|
||||
Vendored
+5
-2
@@ -8,7 +8,7 @@
|
||||
# fredrik@pythonware.com
|
||||
# http://www.pythonware.com
|
||||
#
|
||||
"""This modules handles dialog boxes.
|
||||
"""This module handles dialog boxes.
|
||||
|
||||
It contains the following public symbols:
|
||||
|
||||
@@ -23,9 +23,12 @@ askfloat -- get a float from the user
|
||||
askstring -- get a string from the user
|
||||
"""
|
||||
|
||||
from tkinter import *
|
||||
from tkinter import Button, Entry, Frame, Label, Message, Tk, Toplevel
|
||||
from tkinter import _get_temp_root, _destroy_temp_root
|
||||
from tkinter import messagebox
|
||||
from tkinter.constants import ACTIVE, BOTH, END, LEFT, RIDGE, W, E
|
||||
|
||||
__all__ = ["SimpleDialog", "Dialog", "askinteger", "askfloat", "askstring"]
|
||||
|
||||
|
||||
class SimpleDialog:
|
||||
|
||||
Vendored
+51
-31
@@ -12,8 +12,6 @@ maintaining the widget state and invoking callbacks, all aspects
|
||||
of the widgets appearance lies at Themes.
|
||||
"""
|
||||
|
||||
__version__ = "0.3.1"
|
||||
|
||||
__author__ = "Guilherme Polo <ggpolo@gmail.com>"
|
||||
|
||||
__all__ = ["Button", "Checkbutton", "Combobox", "Entry", "Frame", "Label",
|
||||
@@ -581,8 +579,8 @@ class Button(Widget):
|
||||
|
||||
STANDARD OPTIONS
|
||||
|
||||
class, compound, cursor, image, state, style, takefocus,
|
||||
text, textvariable, underline, width
|
||||
class, compound, cursor, image, justify (Tk 9.0+), padding,
|
||||
state, style, takefocus, text, textvariable, underline, width
|
||||
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
|
||||
@@ -604,8 +602,8 @@ class Checkbutton(Widget):
|
||||
|
||||
STANDARD OPTIONS
|
||||
|
||||
class, compound, cursor, image, state, style, takefocus,
|
||||
text, textvariable, underline, width
|
||||
class, compound, cursor, image, justify (Tk 9.0+), padding,
|
||||
state, style, takefocus, text, textvariable, underline, width
|
||||
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
|
||||
@@ -638,8 +636,10 @@ class Entry(Widget, tkinter.Entry):
|
||||
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
|
||||
exportselection, invalidcommand, justify, show, state,
|
||||
textvariable, validate, validatecommand, width
|
||||
background, exportselection, font, foreground, invalidcommand,
|
||||
justify, locale (Tk 9.1+), placeholder (Tk 9.0+),
|
||||
placeholderforeground (Tk 9.0+), show, state, textvariable,
|
||||
validate, validatecommand, width
|
||||
|
||||
VALIDATION MODES
|
||||
|
||||
@@ -648,7 +648,7 @@ class Entry(Widget, tkinter.Entry):
|
||||
Widget.__init__(self, master, widget or "ttk::entry", kw)
|
||||
|
||||
|
||||
def bbox(self, index):
|
||||
def bbox(self, index): # overrides Misc.bbox
|
||||
"""Return a tuple of (x, y, width, height) which describes the
|
||||
bounding box of the character given by index."""
|
||||
return self._getints(self.tk.call(self._w, "bbox", index))
|
||||
@@ -676,12 +676,14 @@ class Combobox(Entry):
|
||||
|
||||
STANDARD OPTIONS
|
||||
|
||||
class, cursor, style, takefocus
|
||||
class, cursor, style, takefocus, xscrollcommand
|
||||
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
|
||||
exportselection, justify, height, postcommand, state,
|
||||
textvariable, values, width
|
||||
background, exportselection, font, foreground, height,
|
||||
invalidcommand, justify, locale (Tk 9.1+), placeholder (Tk 9.0+),
|
||||
placeholderforeground (Tk 9.0+), postcommand, show, state,
|
||||
textvariable, validate, validatecommand, values, width
|
||||
"""
|
||||
Entry.__init__(self, master, "ttk::combobox", **kw)
|
||||
|
||||
@@ -730,13 +732,13 @@ class Label(Widget):
|
||||
|
||||
STANDARD OPTIONS
|
||||
|
||||
class, compound, cursor, image, style, takefocus, text,
|
||||
textvariable, underline, width
|
||||
class, compound, cursor, image, state, style, takefocus,
|
||||
text, textvariable, underline, width
|
||||
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
|
||||
anchor, background, font, foreground, justify, padding,
|
||||
relief, text, wraplength
|
||||
anchor, background, borderwidth, font, foreground, justify,
|
||||
padding, relief, text, textangle (Tk 9.1+), wraplength
|
||||
"""
|
||||
Widget.__init__(self, master, "ttk::label", kw)
|
||||
|
||||
@@ -754,8 +756,9 @@ class Labelframe(Widget):
|
||||
class, cursor, style, takefocus
|
||||
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
labelanchor, text, underline, padding, labelwidget, width,
|
||||
height
|
||||
|
||||
borderwidth, height, labelanchor, labelwidget, padding,
|
||||
relief, text, underline, width
|
||||
"""
|
||||
Widget.__init__(self, master, "ttk::labelframe", kw)
|
||||
|
||||
@@ -771,8 +774,8 @@ class Menubutton(Widget):
|
||||
|
||||
STANDARD OPTIONS
|
||||
|
||||
class, compound, cursor, image, state, style, takefocus,
|
||||
text, textvariable, underline, width
|
||||
class, compound, cursor, image, justify (Tk 9.0+), padding,
|
||||
state, style, takefocus, text, textvariable, underline, width
|
||||
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
|
||||
@@ -826,7 +829,7 @@ class Notebook(Widget):
|
||||
self.tk.call(self._w, "add", child, *(_format_optdict(kw)))
|
||||
|
||||
|
||||
def forget(self, tab_id):
|
||||
def forget(self, tab_id): # overrides Pack.forget
|
||||
"""Removes the tab specified by tab_id, unmaps and unmanages the
|
||||
associated window."""
|
||||
self.tk.call(self._w, "forget", tab_id)
|
||||
@@ -985,7 +988,9 @@ class Progressbar(Widget):
|
||||
|
||||
STANDARD OPTIONS
|
||||
|
||||
class, cursor, style, takefocus
|
||||
anchor (Tk 9.0+), class, cursor, font (Tk 9.0+),
|
||||
foreground (Tk 9.0+), justify (Tk 9.0+), style, takefocus,
|
||||
text (Tk 9.0+), wraplength (Tk 9.0+)
|
||||
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
|
||||
@@ -1024,8 +1029,8 @@ class Radiobutton(Widget):
|
||||
|
||||
STANDARD OPTIONS
|
||||
|
||||
class, compound, cursor, image, state, style, takefocus,
|
||||
text, textvariable, underline, width
|
||||
class, compound, cursor, image, justify (Tk 9.0+), padding,
|
||||
state, style, takefocus, text, textvariable, underline, width
|
||||
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
|
||||
@@ -1052,7 +1057,7 @@ class Scale(Widget, tkinter.Scale):
|
||||
|
||||
STANDARD OPTIONS
|
||||
|
||||
class, cursor, style, takefocus
|
||||
class, cursor, state, style, takefocus
|
||||
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
|
||||
@@ -1149,7 +1154,10 @@ class Spinbox(Entry):
|
||||
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
|
||||
to, from_, increment, values, wrap, format, command
|
||||
background, command, exportselection, font, foreground,
|
||||
format, from_, increment, justify, locale (Tk 9.1+),
|
||||
placeholder (Tk 9.0+), placeholderforeground (Tk 9.0+), show,
|
||||
state, textvariable, to, values, width, wrap
|
||||
"""
|
||||
Entry.__init__(self, master, "ttk::spinbox", **kw)
|
||||
|
||||
@@ -1176,7 +1184,10 @@ class Treeview(Widget, tkinter.XView, tkinter.YView):
|
||||
|
||||
WIDGET-SPECIFIC OPTIONS
|
||||
|
||||
columns, displaycolumns, height, padding, selectmode, show
|
||||
columns, displaycolumns, headingheight (Tk 9.1+), height,
|
||||
padding, rowheight (Tk 9.1+), selectmode, selecttype (Tk 9.0+),
|
||||
show, striped (Tk 9.0+), titlecolumns (Tk 9.0+),
|
||||
titleitems (Tk 9.0+)
|
||||
|
||||
ITEM OPTIONS
|
||||
|
||||
@@ -1189,7 +1200,7 @@ class Treeview(Widget, tkinter.XView, tkinter.YView):
|
||||
Widget.__init__(self, master, "ttk::treeview", kw)
|
||||
|
||||
|
||||
def bbox(self, item, column=None):
|
||||
def bbox(self, item, column=None): # overrides Misc.bbox
|
||||
"""Returns the bounding box (relative to the treeview widget's
|
||||
window) of the specified item in the form x y width height.
|
||||
|
||||
@@ -1248,7 +1259,7 @@ class Treeview(Widget, tkinter.XView, tkinter.YView):
|
||||
return self.tk.getboolean(self.tk.call(self._w, "exists", item))
|
||||
|
||||
|
||||
def focus(self, item=None):
|
||||
def focus(self, item=None): # overrides Misc.focus
|
||||
"""If item is specified, sets the focus item to item. Otherwise,
|
||||
returns the current focus item, or '' if there is none."""
|
||||
return self.tk.call(self._w, "focus", item)
|
||||
@@ -1472,8 +1483,8 @@ class Treeview(Widget, tkinter.XView, tkinter.YView):
|
||||
|
||||
|
||||
def tag_has(self, tagname, item=None):
|
||||
"""If item is specified, returns 1 or 0 depending on whether the
|
||||
specified item has the given tagname. Otherwise, returns a list of
|
||||
"""If item is specified, returns True if the specified item has the
|
||||
given tagname, False otherwise. Otherwise, returns a list of
|
||||
all items which have the specified tag.
|
||||
|
||||
* Availability: Tk 8.6"""
|
||||
@@ -1648,3 +1659,12 @@ class OptionMenu(Menubutton):
|
||||
except AttributeError:
|
||||
pass
|
||||
super().destroy()
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
if name == "__version__":
|
||||
from warnings import _deprecated
|
||||
|
||||
_deprecated("__version__", remove=(3, 20))
|
||||
return "0.3.1" # Do not change
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
Vendored
+341
-389
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user