示例#1
0
文件: menu.py 项目: PurpleMyst/teek
    def __init__(self, *args, **kwargs):
        self._options = kwargs.copy()

        if not args:
            self.type = 'separator'
        elif len(args) == 2:
            self._options['label'], second_object = args
            if isinstance(second_object, teek.BooleanVar):
                self.type = 'checkbutton'
                self._options['variable'] = second_object
            elif callable(second_object):
                self.type = 'command'
                self._command_callback = second_object  # see _adding_finalizer
            elif isinstance(second_object, Menu):
                self.type = 'cascade'
                self._options['menu'] = second_object
            else:   # assume an iterable
                self.type = 'cascade'
                self._options['menu'] = Menu(second_object)
        elif len(args) == 3:
            self.type = 'radiobutton'
            (self._options['label'],
             self._options['variable'],
             self._options['value']) = args
        else:
            raise TypeError(
                "expected 0, 2 or 3 arguments to MenuItem, got %d" % len(args))

        self._args = args
        self._kwargs = kwargs
        self._menu = None
        self._index = None

        self.config = CgetConfigureConfigDict(self._config_entrycommand_caller)
        self.config._types.update({
            'activebackground': teek.Color,
            'activeforeground': teek.Color,
            'accelerator': str,
            'background': teek.Color,
            #'bitmap': ???,
            'columnbreak': bool,
            'compound': str,
            'font': teek.Font,
            'foreground': teek.Color,
            'hidemargin': bool,
            'image': teek.Image,
            'indicatoron': bool,
            'label': str,
            'menu': Menu,
            'offvalue': bool,
            'onvalue': bool,
            'selectcolor': teek.Color,
            'selectimage': teek.Image,
            'state': str,
            'underline': bool,
            'value': str,
            'variable': (teek.BooleanVar if self.type == 'checkbutton'
                         else teek.StringVar),
        })
        self.config._special['command'] = self._create_command
示例#2
0
    def _setup(self, type_string, id_):
        self.type_string = type_string
        self._id = id_

        self.tags = Tags(self)
        self.config = CgetConfigureConfigDict(self._config_caller)

        prefixed = {
            #'stipple': ???,
            #'outlinestipple': ???,
            'fill': teek.Color,
            'outline': teek.Color,
            'dash': str,
            # TODO: support non-float coordinates? see COORDINATES in man page
            'width': float,
        }
        for prefix in ['', 'active', 'disabled']:
            self.config._types.update(
                {prefix + key: value
                 for key, value in prefixed.items()})

        self.config._types.update({
            'offset': str,
            'outlineoffset': str,
            'joinstyle': str,
            'splinesteps': int,
            'smooth': str,
            'state': str,
            'tags': [str],
            'capstyle': str,
            'arrow': str,
            # see comment about floats in prefixed
            'dashoffset': float,
            'arrowshape': (float, float, float),
        })
示例#3
0
文件: base.py 项目: rdbende/teek
    def __init__(self, parent, **kwargs):
        if type(self)._widget_name is None:
            raise TypeError("cannot create instances of %s directly, "
                            "use one of its subclasses instead" %
                            type(self).__name__)
        if parent is None:
            parentpath = ''
        else:
            parentpath = parent.to_tcl()
        self.parent = parent

        # yes, it must be lowercase
        safe_class_name = re.sub(r'\W', '_', type(self).__name__).lower()

        # use some_widget.to_tcl() to access the _widget_path
        self._widget_path = '%s.%s%d' % (parentpath, safe_class_name,
                                         next(counts[safe_class_name]))

        # TODO: some config options can only be given when the widget is
        # created, add support for them
        self._call(None, type(self)._widget_name, self.to_tcl())
        _widgets[self.to_tcl()] = self

        self.config = CgetConfigureConfigDict(
            lambda returntype, *args: self._call(returntype, self, *args))
        self._init_config()  # subclasses should override this and use super

        # support kwargs like from_=1, because from=1 is invalid syntax
        for invalid_syntax in keyword.kwlist:
            if invalid_syntax + '_' in kwargs:
                kwargs[invalid_syntax] = kwargs.pop(invalid_syntax + '_')

        self.config.update(kwargs)

        # command strings that are deleted when the widget is destroyed
        self.command_list = []

        self.bindings = BindingDict(  # BindingDict is defined below
            lambda returntype, *args: self._call(returntype, 'bind', self, *
                                                 args), self.command_list)
        self.bind = self.bindings._convenience_bind

        if type(self)._widget_name.startswith('ttk::'):
            self.state = StateSet(self)
        else:
            self.state = None
示例#4
0
class MenuItem:
    """
    Represents an item of a menu. See :ref:`creating-menu-items` for details
    about the arguments.

    Tk's manual pages call these things "menu entries" instead of "menu items",
    but I called them items to avoid confusing these with :class:`.Entry`.

    There are two kinds of :class:`.MenuItem` objects:

    * Menu items that are not in any :class:`.Menu` widget because they haven't
      been added to a menu yet, or they have been removed from a menu. Trying
      to do something with these menu items will likely raise a
      :class:`.RuntimeError`.
    * Menu items that are currently in a :class:`.Menu`.

    Here's an example:

    >>> item = teek.MenuItem("Click me", print)
    >>> item.config['label'] = "New text"
    Traceback (most recent call last):
        ...
    RuntimeError: the MenuItem hasn't been added to a Menu yet
    >>> menu = teek.Menu()
    >>> menu.append(item)
    >>> item.config['label'] = "New text"
    >>> item.config['label']
    'New text'

    .. attribute:: config

        This attribute is similar to :attr:`.Widget.config`. See
        ``MENU ENTRY OPTIONS`` in :man:`menu(3tk)`.

        The types of the values are the same as for similar widgets. For
        example, the ``'command'`` of a :class:`.Button` widget is a
        :class:`.Callback` object connected to a function passed to
        :class:`.Button`, and so is the ``'command'`` of
        ``teek.MenuItem("Click me", some_function)``.

    .. attribute:: type

        This is a string. Currently the possible values are
        ``'separator'``, ``'checkbutton'``, ``'command'``, ``'cascade'`` and
        ``'radiobutton'`` as documented :ref:`above <creating-menu-items>`.
        Don't set this attribute yourself.
    """
    def __init__(self, *args, **kwargs):
        self._options = kwargs.copy()

        if not args:
            self.type = 'separator'
        elif len(args) == 2:
            self._options['label'], second_object = args
            if isinstance(second_object, teek.BooleanVar):
                self.type = 'checkbutton'
                self._options['variable'] = second_object
            elif callable(second_object):
                self.type = 'command'
                self._command_callback = second_object  # see _adding_finalizer
            elif isinstance(second_object, Menu):
                self.type = 'cascade'
                self._options['menu'] = second_object
            else:  # assume an iterable
                self.type = 'cascade'
                self._options['menu'] = Menu(second_object)
        elif len(args) == 3:
            self.type = 'radiobutton'
            (self._options['label'], self._options['variable'],
             self._options['value']) = args
        else:
            raise TypeError(
                "expected 0, 2 or 3 arguments to MenuItem, got %d" % len(args))

        self._args = args
        self._kwargs = kwargs
        self._menu = None
        self._index = None

        self.config = CgetConfigureConfigDict(self._config_entrycommand_caller)
        self.config._types.update({
            'activebackground':
            teek.Color,
            'activeforeground':
            teek.Color,
            'accelerator':
            str,
            'background':
            teek.Color,
            #'bitmap': ???,
            'columnbreak':
            bool,
            'compound':
            str,
            'font':
            teek.Font,
            'foreground':
            teek.Color,
            'hidemargin':
            bool,
            'image':
            teek.Image,
            'indicatoron':
            bool,
            'label':
            str,
            'menu':
            Menu,
            'offvalue':
            bool,
            'onvalue':
            bool,
            'selectcolor':
            teek.Color,
            'selectimage':
            teek.Image,
            'state':
            str,
            'underline':
            bool,
            'value':
            str,
            'variable': (teek.BooleanVar
                         if self.type == 'checkbutton' else teek.StringVar),
        })
        self.config._special['command'] = self._create_command

    def __repr__(self):
        parts = ['type=%r' % self.type]
        if self._menu is None:
            parts.append("not added to a menu yet")
        else:
            parts.append("added to a menu")

        return '<%s%r: %s>' % (type(self).__name__, self._args,
                               ', '.join(parts))

    def _prepare_adding(self):
        if self._menu is not None:
            raise RuntimeError("cannot add a MenuItem to two different menus "
                               "or twice to the same menu")

    def _after_adding(self, menu, index):
        self._menu = menu
        self._index = index
        self.config.update(self._options)
        if self.type == 'command':
            self.config['command'].connect(self._command_callback)

    def _after_removing(self):
        self._menu = None
        self._index = None

    def _check_in_menu(self):
        assert (self._menu is None) == (self._index is None)
        if self._menu is None:
            raise RuntimeError("the MenuItem hasn't been added to a Menu yet")

    @make_thread_safe
    def _config_entrycommand_caller(self, returntype, subcommand, *args):
        assert subcommand in {'cget', 'configure'}
        self._check_in_menu()
        return teek.tcl_call(returntype, self._menu, 'entry' + subcommand,
                             self._index, *args)

    def _create_command(self):
        self._check_in_menu()
        result = teek.Callback()
        command_string = teek.create_command(result.run)
        teek.tcl_call(None, self._menu, 'entryconfigure', self._index,
                      '-command', command_string)
        self._menu.command_list.append(command_string)
        return result
示例#5
0
文件: base.py 项目: rdbende/teek
class Widget:
    """This is a base class for all widgets.

    All widgets inherit from this class, and they have all the attributes
    and methods documented here.

    Don't create instances of ``Widget`` yourself like ``Widget(...)``; use one
    of the classes documented below instead. However, you can use ``Widget``
    with :func:`isinstance`; e.g. ``isinstance(thingy, teek.Widget)`` returns
    ``True`` if ``thingy`` is a teek widget.

    .. attribute:: config

        A dict-like object that represents the widget's options.

        >>> window = teek.Window()
        >>> label = teek.Label(window, text='Hello World')
        >>> label.config
        <a config object, behaves like a dict>
        >>> label.config['text']
        'Hello World'
        >>> label.config['text'] = 'New Text'
        >>> label.config['text']
        'New Text'
        >>> label.config.update({'text': 'Even newer text'})
        >>> label.config['text']
        'Even newer text'
        >>> import pprint
        >>> pprint.pprint(dict(label.config))  # prints everything nicely  \
        # doctest: +ELLIPSIS
        {...,
         'text': 'Even newer text',
         ...}

    .. attribute:: state

        Represents the Ttk state of the widget. The state object behaves like a
        :class:`set` of strings. For example, ``widget.state.add('disabled')``
        makes a widget look like it's grayed out, and
        ``widget.state.remove('disabled')`` undoes that. See ``STATES`` in
        :man:`ttk_intro(3tk)` for more details about states.

        .. note::
            Only Ttk widgets have states, and this attribute is set to None for
            non-Ttk widgets. If you don't know what Ttk is, you should read
            about it in :ref:`the teek tutorial <tcl-tk-tkinter-teek>`.
            Most teek widgets are ttk widgets, but some aren't, and that's
            mentioned in the documentation of those widgets.

    .. attribute:: tk_class_name

        Tk's class name of the widget class, as a string.

        This is a class attribute, but it can be accessed from instances as
        well:

        >>> text = teek.Text(teek.Window())
        >>> text.tk_class_name
        'Text'
        >>> teek.Text.tk_class_name
        'Text'

        Note that Tk's class names are sometimes different from the names of
        Python classes, and this attribute can also be None in some special
        cases.

        >>> teek.Label.tk_class_name
        'TLabel'
        >>> class AsdLabel(teek.Label):
        ...     pass
        ...
        >>> AsdLabel.tk_class_name
        'TLabel'
        >>> print(teek.Window.tk_class_name)
        None
        >>> print(teek.Widget.tk_class_name)
        None

    .. attribute:: command_list

        A list of command strings from :func:`.create_command`.

        Append a command to this if you want the command to be deleted with
        :func:`.delete_command` when the widget is destroyed (with e.g.
        :meth:`.destroy`).
    """

    _widget_name = None
    tk_class_name = None

    @make_thread_safe
    def __init__(self, parent, **kwargs):
        if type(self)._widget_name is None:
            raise TypeError("cannot create instances of %s directly, "
                            "use one of its subclasses instead" %
                            type(self).__name__)
        if parent is None:
            parentpath = ''
        else:
            parentpath = parent.to_tcl()
        self.parent = parent

        # yes, it must be lowercase
        safe_class_name = re.sub(r'\W', '_', type(self).__name__).lower()

        # use some_widget.to_tcl() to access the _widget_path
        self._widget_path = '%s.%s%d' % (parentpath, safe_class_name,
                                         next(counts[safe_class_name]))

        # TODO: some config options can only be given when the widget is
        # created, add support for them
        self._call(None, type(self)._widget_name, self.to_tcl())
        _widgets[self.to_tcl()] = self

        self.config = CgetConfigureConfigDict(
            lambda returntype, *args: self._call(returntype, self, *args))
        self._init_config()  # subclasses should override this and use super

        # support kwargs like from_=1, because from=1 is invalid syntax
        for invalid_syntax in keyword.kwlist:
            if invalid_syntax + '_' in kwargs:
                kwargs[invalid_syntax] = kwargs.pop(invalid_syntax + '_')

        self.config.update(kwargs)

        # command strings that are deleted when the widget is destroyed
        self.command_list = []

        self.bindings = BindingDict(  # BindingDict is defined below
            lambda returntype, *args: self._call(returntype, 'bind', self, *
                                                 args), self.command_list)
        self.bind = self.bindings._convenience_bind

        if type(self)._widget_name.startswith('ttk::'):
            self.state = StateSet(self)
        else:
            self.state = None

    def _init_config(self):
        # width and height aren't here because they are integers for some
        # widgets and ScreenDistances for others... and sometimes the manual
        # pages don't say which, so i have checked them by hand
        self.config._types.update({
            # ttk_widget(3tk)
            'class': str,
            'cursor': str,
            'style': str,

            # options(3tk)
            'activebackground': teek.Color,
            'activeborderwidth': teek.ScreenDistance,
            'activeforeground': teek.Color,
            'anchor': str,
            'background': teek.Color,
            'bg': teek.Color,
            #'bitmap': ???,
            'borderwidth': teek.ScreenDistance,
            'bd': teek.ScreenDistance,
            'cursor': str,
            'compound': str,
            'disabledforeground': teek.Color,
            'exportselection': bool,
            'font': teek.Font,
            'foreground': teek.Color,
            'fg': teek.Color,
            'highlightbackground': teek.Color,
            'highlightcolor': teek.Color,
            'highlightthickness': str,
            'insertbackground': teek.Color,
            'insertborderwidth': teek.ScreenDistance,
            'insertofftime': int,
            'insertontime': int,
            'insertwidth': teek.ScreenDistance,
            'jump': bool,
            'justify': str,
            'orient': str,
            'padx': teek.ScreenDistance,
            'pady': teek.ScreenDistance,
            'relief': str,
            'repeatdelay': int,
            'repeatinterval': int,
            'selectbackground': teek.Color,
            'selectborderwidth': teek.ScreenDistance,
            'selectforeground': teek.Color,
            'setgrid': bool,
            'text': str,
            'troughcolor': teek.Color,
            'wraplength': teek.ScreenDistance,

            # these options are in both man pages
            'textvariable': teek.StringVar,
            'underline': int,
            'image': teek.Image,
            # 'xscrollcommand' and 'yscrollcommand' are done below
            'takefocus': str,  # this one is harder to do right than you think

            # other stuff that many things seem to have
            'padding': teek.ScreenDistance,
            'state': str,
        })

        for option_name in ('xscrollcommand', 'yscrollcommand'):
            self.config._special[option_name] = functools.partial(
                self._create_scroll_callback, option_name)

    @classmethod
    @make_thread_safe
    def from_tcl(cls, path_string):
        """Creates a widget from a Tcl path name.

        In Tcl, widgets are represented as commands, and doing something to the
        widget invokes the command. Use this method if you know the Tcl command
        and you would like to have a widget object instead.

        This method raises :exc:`TypeError` if it's called from a different
        ``Widget`` subclass than what the type of the ``path_string`` widget
        is:

        >>> window = teek.Window()
        >>> teek.Button.from_tcl(teek.Label(window).to_tcl())  \
# doctest: +ELLIPSIS
        Traceback (most recent call last):
            ...
        TypeError: '...' is a Label, not a Button
        """
        if path_string == '.':
            # this kind of sucks, i might make a _RootWindow class later
            return None

        result = _widgets[path_string]
        if not isinstance(result, cls):
            raise TypeError("%r is a %s, not a %s" %
                            (path_string, type(result).__name__, cls.__name__))
        return result

    def to_tcl(self):
        """Returns the widget's Tcl command name. See :meth:`from_tcl`."""
        return self._widget_path

    def __repr__(self):
        class_name = type(self).__name__
        if getattr(teek, class_name, None) is type(self):
            result = 'teek.%s widget' % class_name
        else:
            result = '{0.__module__}.{0.__name__} widget'.format(type(self))

        if not self.winfo_exists():
            # _repr_parts() doesn't need to work with destroyed widgets
            return '<destroyed %s>' % result

        parts = self._repr_parts()
        if parts:
            result += ': ' + ', '.join(parts)
        return '<' + result + '>'

    def _repr_parts(self):
        # overrided in subclasses
        return []

    def _create_scroll_callback(self, option_name):
        result = teek.Callback()
        command_string = teek.create_command(result.run, [float, float])
        self.command_list.append(command_string)
        self._call(None, self, 'configure', '-' + option_name, command_string)
        return result

    __getitem__ = _tkinter_hint("widget.config['option']", "widget['option']")
    __setitem__ = _tkinter_hint("widget.config['option']", "widget['option']")
    cget = _tkinter_hint("widget.config['option']", "widget.cget('option')")
    configure = _tkinter_hint("widget.config['option'] = value",
                              "widget.configure(option=value)")

    # like _tcl_calls.tcl_call, but with better error handling
    @make_thread_safe
    def _call(self, *args, **kwargs):
        try:
            return teek.tcl_call(*args, **kwargs)
        except teek.TclError as err:
            if not self.winfo_exists():
                raise RuntimeError("the widget has been destroyed") from None
            raise err

    @make_thread_safe
    def destroy(self):
        """Delete this widget and all child widgets.

        Manual page: :man:`destroy(3tk)`

        .. note::
            Don't override this in a subclass. In some cases, the widget is
            destroyed without a call to this method.

            >>> class BrokenFunnyLabel(teek.Label):
            ...     def destroy(self):
            ...         print("destroying")
            ...         super().destroy()
            ...
            >>> BrokenFunnyLabel(teek.Window()).pack()
            >>> teek.quit()
            >>> # nothing was printed!

            Use the ``<Destroy>`` event instead:

            >>> class WorkingFunnyLabel(teek.Label):
            ...     def __init__(self, *args, **kwargs):
            ...         super().__init__(*args, **kwargs)
            ...         self.bind('<Destroy>', self._destroy_callback)
            ...     def _destroy_callback(self):
            ...         print("destroying")
            ...
            >>> WorkingFunnyLabel(teek.Window()).pack()
            >>> teek.quit()
            destroying
        """
        for name in self._call([str], 'winfo', 'children', self):
            # allow overriding the destroy() method if the widget was
            # created by teek
            if name in _widgets:
                _widgets[name]._destroy_recurser()
            else:
                self._call(None, 'destroy', name)

        # this must be BEFORE deleting command_list commands because <Destroy>
        # bindings may need command_list stuff
        self._call(None, 'destroy', self)

        # this is here because now the widget is basically useless
        del _widgets[self.to_tcl()]

        for command in self.command_list:
            teek.delete_command(command)
        self.command_list.clear()  # why not

    # can be overrided when .destroy() in .destroy() would cause infinite
    # recursion, see Window in windows.py
    def _destroy_recurser(self):
        self.destroy()

    @_ClassProperty
    @make_thread_safe
    def class_bindings(cls):
        if cls is Widget:
            assert cls.tk_class_name is None
            bindtag = 'all'
        elif cls.tk_class_name is not None:
            bindtag = cls.tk_class_name
        else:
            raise AttributeError(
                "%s cannot be used with class_bindings and bind_class()" %
                cls.__name__)

        try:
            return _class_bindings[bindtag]
        except KeyError:

            def call_bind(returntype, *args):
                return teek.tcl_call(returntype, 'bind', bindtag, *args)

            # all commands are deleted when the interpreter shuts down, and the
            # binding dict created here should be alive until then, so it's
            # fine to pass a new empty list for command list
            bindings = BindingDict(call_bind, [])
            _class_bindings[bindtag] = bindings
            return bindings

    @classmethod
    @make_thread_safe
    def bind_class(cls, *args, **kwargs):
        return cls.class_bindings._convenience_bind(*args, **kwargs)

    def winfo_children(self):
        """Returns a list of child widgets that this widget has.

        Manual page: :man:`winfo(3tk)`
        """
        return self._call([Widget], 'winfo', 'children', self)

    def winfo_exists(self):
        """Returns False if the widget has been destroyed. See :meth:`destroy`.

        Manual page: :man:`winfo(3tk)`
        """
        # self._call uses this, so this must not use that
        return teek.tcl_call(bool, 'winfo', 'exists', self)

    def winfo_ismapped(self):
        """
        Returns True if the widget is showing on the screen, or False
        otherwise.

        Manual page: :man:`winfo(3tk)`
        """
        return self._call(bool, 'winfo', 'ismapped', self)

    def winfo_toplevel(self):
        """Returns the :class:`Toplevel` widget that this widget is in.

        Manual page: :man:`winfo(3tk)`
        """
        return self._call(Widget, 'winfo', 'toplevel', self)

    def winfo_width(self):
        """Calls ``winfo width``. Returns an integer.

        Manual page: :man:`winfo(3tk)`
        """
        return self._call(int, 'winfo', 'width', self)

    def winfo_height(self):
        """Calls ``winfo height``. Returns an integer.

        Manual page: :man:`winfo(3tk)`
        """
        return self._call(int, 'winfo', 'height', self)

    def winfo_reqwidth(self):
        """Calls ``winfo reqwidth``. Returns an integer.

        Manual page: :man:`winfo(3tk)`
        """
        return self._call(int, 'winfo', 'reqwidth', self)

    def winfo_reqheight(self):
        """Calls ``winfo reqheight``. Returns an integer.

        Manual page: :man:`winfo(3tk)`
        """
        return self._call(int, 'winfo', 'reqheight', self)

    def winfo_x(self):
        """Calls ``winfo x``. Returns an integer.

        Manual page: :man:`winfo(3tk)`
        """
        return self._call(int, 'winfo', 'x', self)

    def winfo_y(self):
        """Calls ``winfo y``. Returns an integer.

        Manual page: :man:`winfo(3tk)`
        """
        return self._call(int, 'winfo', 'y', self)

    def winfo_rootx(self):
        """Calls ``winfo rootx``. Returns an integer.

        Manual page: :man:`winfo(3tk)`
        """
        return self._call(int, 'winfo', 'rootx', self)

    def winfo_rooty(self):
        """Calls ``winfo rooty``. Returns an integer.

        Manual page: :man:`winfo(3tk)`
        """
        return self._call(int, 'winfo', 'rooty', self)

    def winfo_id(self):
        """Calls ``winfo id``. Returns an integer.

        Manual page: :man:`winfo(3tk)`
        """
        return self._call(int, 'winfo', 'id', self)

    def focus(self, *, force=False):
        """Focuses the widget with :man:`focus(3tk)`.

        If ``force=True`` is given, the ``-force`` option is used.
        """
        if force:
            self._call(None, 'focus', '-force', self)
        else:
            self._call(None, 'focus', self)

    def _geometry_manager_slaves(self, geometry_manager):
        return self._call([Widget], geometry_manager, 'slaves', self)

    pack_slaves = functools.partialmethod(_geometry_manager_slaves, 'pack')
    grid_slaves = functools.partialmethod(_geometry_manager_slaves, 'grid')
    place_slaves = functools.partialmethod(_geometry_manager_slaves, 'place')

    @property
    def grid_rows(self):
        width, height = self._call([int], 'grid', 'size', self)
        return [
            GridRowOrColumn(self, 'row', number) for number in range(height)
        ]

    @property
    def grid_columns(self):
        width, height = self._call([int], 'grid', 'size', self)
        return [
            GridRowOrColumn(self, 'column', number) for number in range(width)
        ]

    grid_rowconfigure = _tkinter_hint(
        "widget.grid_rows[index].config['option'] = value",
        "widget.grid_rowconfigure(index, option=value)")
    grid_columnconfigure = _tkinter_hint(
        "widget.grid_columns[index].config['option'] = value",
        "widget.grid_columnconfigure(index, option=value)")

    def busy_hold(self):
        """See ``tk busy hold`` in :man:`busy(3tk)`.

        *New in Tk 8.6.*
        """
        self._call(None, 'tk', 'busy', 'hold', self)

    def busy_forget(self):
        """See ``tk busy forget`` in :man:`busy(3tk)`.

        *New in Tk 8.6.*
        """
        self._call(None, 'tk', 'busy', 'forget', self)

    def busy_status(self):
        """See ``tk busy status`` in :man:`busy(3tk)`.

        This Returns True or False.

        *New in Tk 8.6.*
        """
        return self._call(bool, 'tk', 'busy', 'status', self)

    @contextlib.contextmanager
    def busy(self):
        """A context manager that calls :func:`busy_hold` and :func:`busy_forg\
et`.

        Example::

            with window.busy():
                # window.busy_hold() has been called, do something
                ...

            # now window.busy_forget() has been called
        """
        self.busy_hold()
        try:
            yield
        finally:
            self.busy_forget()

    def event_generate(self, event, **kwargs):
        """Calls ``event generate`` documented in :man:`event(3tk)`.

        As usual, options are given without dashes as keyword arguments, so Tcl
        code like ``event generate $widget <SomeEvent> -data $theData`` looks
        like ``widget.event_generate('<SomeEvent>', data=the_data)`` in
        teek.
        """
        option_args = []
        for name, value in kwargs.items():
            option_args.extend(['-' + name, value])
        self._call(None, 'event', 'generate', self, event, *option_args)