Welcome, guest | Sign In | My Account | Store | Cart
# encoding: utf-8
# Author: Miguel Martínez López
#
# Please let me know if you find a bug
# Uncomment the next line to see my email
#   print("Author's email: %s"%"61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex"))

"""Tkinter Selectors
    Select widgets using selectors synchronously or asynchronously-

    This is the provided API:
        toplevel_titles(widget)
        window_titles(widget)
        full_name_of_widgets(root)
        find_toplevel_by_title(widget, title)
        find_widgets_by_name(root, name)
        find_widget_by_name(root, name)
        find_widgets_by_class(root, class_)
        find_widget_by_class(root, class_)
        find(root, selector, callback, async=True)
        find_all(root, selector, callback, async=True)
        config(root, selector, **kwargs)
        config_async(root, selector, **kwargs)
        config_all(root, selector, **kwargs)
        config_all_async(root, selector, **kwargs)
        tkinter_print(root, name=True, class_= True, indent="\t")
"""
# Python 2/3 compatibility
import sys

PY3 = sys.version_info[0] ==3

if PY3:
  basestring = str
  unicode = str

class SubSelector(unicode):
    def __add__(self, txt):
        return self.__class__(unicode(self)+txt)

class SubSelector_Name(SubSelector):
    """Subselector that match the widget name"""

class SubSelector_Class(SubSelector):
    """Subselector that match the widget class"""

class Tkinter_Selector(object):


    def __init__(self, selector_string):

        if len(selector_string) == 0:
            raise ValueError("selector can't be zero length")

        if selector_string[0] == ".":
            self._anchor_start = True
            last_char = "."
            selector_string = selector_string[1:]

        else:            
            self._anchor_start = False
            
            if selector_string[0] == "*":
                last_char = "*"
                selector_string = selector_string[1:]
            else:
                last_char = None


        if selector_string[-1] == "*" or selector_string[-1] == ".":
            raise ValueError("'%s' is not allowed at the end"%selector_string[-1])

        selector_obj = []
        
        sequence_of_selector_elements = []
        
        selector_element = None

        for position, char in enumerate(selector_string):
            if char == ".":
                if last_char == "." or last_char == "*":
                    raise ValueError("Not allowed '%s' at position %d"%(last_char, position))
                
                sequence_of_selector_elements.append(selector_element)

                selector_element = None
            elif char == "*":
                if last_char == "." or last_char == "*":
                    raise ValueError("Not allowed '%s' at position %d"%(last_char, position))
    
                sequence_of_selector_elements.append(selector_element)
                selector_element = None

                selector_obj.append(sequence_of_selector_elements)
                sequence_of_selector_elements = []
            else:
                if selector_element is None:
                    if char.isupper():
                        selector_element = SubSelector_Class(char)
                    else:
                        selector_element = SubSelector_Name(char)
                else:
                    selector_element += char
                
            last_char = char

        if selector_element is not None:
            sequence_of_selector_elements.append(selector_element)

        if len(sequence_of_selector_elements) != 0:
            selector_obj.append(sequence_of_selector_elements)

        
        self._selector_obj = selector_obj
        self._selector_string = selector_string

    def match(self, root, callback, async=True):
        queue = [(root, 0, 0)]

        if async:
            def async_match():
                self._match(queue, callback)

                if len(queue) != 0:
                    root.after(0, async_match)
            async_match()
        else:
            while len(queue) != 0:
                self._match(queue, callback)

    def _match(self, queue, callback):
        widget, index_1, index_2 = queue.pop(0)
        selector_element = self._selector_obj[index_1][index_2]

        if len(self._selector_obj) == index_1 + 1 and len(self._selector_obj[-1]) == index_2 + 1:
            for child in self._matched_children(widget, selector_element):
                try:
                    callback(child)
                except StopIteration:
                    del queue[:]
                    return
        else:
            if len(self._selector_obj[index_1]) == index_2 +1:
                new_index_1 = index_1 + 1
                new_index_2 = 0
            else:
                new_index_1 = index_1
                new_index_2 = index_2 + 1

            for child in self._matched_children(widget, selector_element):
                queue.insert(0, (child, new_index_1, new_index_2))

        if index_2 == 0 and not (self._anchor_start and index_1 == 0):
            for child in widget.children.values():
                queue.append((child, index_1, 0))
            
    def _matched_children(self, widget, selector_element):
        for child_name, child in widget.children.items():
            if isinstance(selector_element, SubSelector_Name):
                if child_name == selector_element:
                    yield child
            else:
                if child.winfo_class() == selector_element:
                    yield child
    @property
    def anchor_start(self):
        return self._anchor_start
    
    def __str__(self):
        return self._selector_string

def iterate(root):
    queue_of_widgets = root.children.values()

    while True:
            
        if len(queue_of_widgets) == 0:
            return
        else:
            yield queue_of_widgets.pop()
            queue.extend(widget.children.values())


def get_root(widget):
    if str(widget) == ".":
        root = widget
    else:
        root = widget.nametowidget(".")
    return root
            
def find_toplevel_by_title(widget, title):
    root = get_root(widget)

    if  isinstance(title, re._pattern_type):
        for child in root.children.values():
            if child.winfo_class() == "Toplevel" and title.search(child.wm_title()):
                return child
    else:
        for child in root.children.values():
            if child.winfo_class() == "Toplevel" and child.wm_title() == title:
                return child

def toplevel_titles(widget):
    root = get_root(widget)

    list_of_titles = []

    for child in root.children.values():
        if child.winfo_class() == "Toplevel":
            list_of_titles.append(child.wm_title())

    return list_of_titles

def window_titles(widget):
    root = get_root(widget)

    return [root.wm_title()] + list_toplevel_titles(root)

def find_widgets_by_name(root, name):
    list_of_found_widgets = []
    
    queue = root.children.items()

    while True:
        
        widget_name, widget = queue.pop()
        if widget_name == name:
            list_of_found_widgets.append(widget)
            
        if len(queue) == 0:
            return list_of_found_widgets
        else:
            queue.extend(widget.children.items())

def find_widget_by_name(root, name):
    queue = root.children.items()

    while True:
        
        widget_name, widget = queue.pop()
        if widget_name == name:
            return widget
            
        if len(queue) == 0:
            return
        else:
            queue.extend(widget.children.items())
    
def find_widgets_by_class(root, class_):
    list_of_found_widgets = []
    
    for widget in iterate(root):
        if widget.winfo_class() == class_:
            list_of_found_widgets.append(widget)
        
    return list_of_found_widgets

def find_widget_by_class(root, class_):
    for widget in iterate(root):
        if widget.winfo_class() == class_:
            return widget

def full_name_of_widgets(root):
    names = [str(root)]

    queue_of_widgets = root.children.values()

    while True:

        if len(queue_of_widgets) == 0:
            return names
        else:
            names.append(str(queue_of_widgets.pop()))
            queue_of_widgets.extend(widget.children.values())
 

def find_all(root, selector, callback, async=True):
    list_of_found_widgets = []
    
    if isinstance(selector, basestring):
        selector = Tkinter_Selector(selector)

    selector.match(root, callback, async)

def find(root, selector, callback, async=True):
    if isinstance(selector, basestring):
        selector = Tkinter_Selector(selector)

    def wrapper_callback(widget):
        callback(widget)
        raise StopIteration

    selector.match(root, wrapper_callback, async)

def config_extended(root, selector, async, config_kwargs):
    if isinstance(selector, basestring):
        selector = Tkinter_Selector(selector)
    
    def callback(widget, config_kwargs=config_kwargs):
        widget.configure(**config_kwargs)
        raise StopIteration

    selector.match(root, callback, async)

def config_all_extended(root, selector, async, config_kwargs):
    if isinstance(selector, basestring):
        selector = Tkinter_Selector(selector)
    
    selector.match(root, lambda widget, config_kwargs = config_kwargs: widget.configure(**config_kwargs), async)


def config(root, selector, **kwargs):
    config_extended(root, selector, False, kwargs)

def config_async(root, selector, **kwargs):
    config_extended(root, selector, True, kwargs)

def config_all(root, selector, **kwargs):
    config_all_extended(root, selector, False, kwargs)

def config_all_async(root, selector, **kwargs):
    config_all_extended(root, selector, True, kwargs)

def tkinter_print(root, name=True, class_= True, indent="\t"):
    print(root)
    
    if name or class_:
        _print_widget_tree_from(root, name, class_, 1, indent)

def _print_widget_tree_from(root, print_names, print_classes, level, indent):
    for child_name, child in root.children.items():
        
        if print_names:
            if print_classes:
                output = "%s (%s)"%(child_name, child.winfo_class())
            else:
                output = child_name
        else:
            output = child.winfo_class()

        print(indent*level + output)
        _print_widget_tree_from(child, print_names, print_classes, level+1, indent)
    


if __name__ == "__main__":
    try:
        from Tkinter import Tk, Frame, Label, Button, LabelFrame
    except ImportError:
        from tkinter import Tk, Frame, Label, Button, LabelFrame
    
    root = Tk()
    
    container1 = LabelFrame(root, class_="Container1")
    container1.pack()
    
    left = Frame(container1, name="left")
    left.pack()
    
    Label(left, name="label0").pack()
    
    center = Frame(container1, name="center")
    center.pack()
    
    area1 = Frame(center, class_="Area1")
    area1.pack()
    
    area2 = Frame(center, class_="Area2")
    area2.pack()
    
    Button(area2, name="my_button").pack()
    Button(area2).pack()
    
    area3 = Frame(center, class_="Area3")
    area3.pack()
    
    area4 = Frame(center, class_="Area4")
    area4.pack()
    
    child_frame = Frame(area4, class_="Child_Frame")
    child_frame.pack()
    
    child_label = Label(area4)
    child_label.pack()
    
    right = Frame(container1, name="right")
    right.pack()
    
    container2 = Frame(root, class_="Container2")
    container2.pack()

    tkinter_print(root)

    def show(widget):
        print(widget)

    find_all(root, "Container1*Label", show, False)
    find(root, "Container1*label0", show)
    find_all(root, "center*Button", show)
    find_all(root, ".Container1.left.label0", show)
    
    config_all(root, "Container1*Label", text="this is a label")
    config(root, "Container1*label0", text="this is a label number 0")
    config_all(root, "Container1*Button", text="this is a button")
    config(root, "Container1", text="this is Container 1")
    root.mainloop()

History