Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
wimp.py797 linesDownload Raw Back to widget
1# Urwid Window-Icon-Menu-Pointer-style widget classes2#    Copyright (C) 2004-2011  Ian Ward3#4#    This library is free software; you can redistribute it and/or5#    modify it under the terms of the GNU Lesser General Public6#    License as published by the Free Software Foundation; either7#    version 2.1 of the License, or (at your option) any later version.8#9#    This library is distributed in the hope that it will be useful,10#    but WITHOUT ANY WARRANTY; without even the implied warranty of11#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU12#    Lesser General Public License for more details.13#14#    You should have received a copy of the GNU Lesser General Public15#    License along with this library; if not, write to the Free Software16#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA17#18# Urwid web site: https://urwid.org/19 20 21from __future__ import annotations22 23import typing24 25from urwid.canvas import CompositeCanvas26from urwid.command_map import Command27from urwid.signals import connect_signal28from urwid.text_layout import calc_coords29from urwid.util import is_mouse_press30 31from .columns import Columns32from .constants import Align, WrapMode33from .text import Text34from .widget import WidgetError, WidgetWrap35 36if typing.TYPE_CHECKING:37    from collections.abc import Callable, Hashable, MutableSequence38 39    from typing_extensions import Literal, Self40 41    from urwid.canvas import TextCanvas42    from urwid.text_layout import TextLayout43 44    _T = typing.TypeVar("_T")45 46 47class SelectableIcon(Text):48    ignore_focus = False49    _selectable = True50 51    def __init__(52        self,53        text: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],54        cursor_position: int = 0,55        align: Literal["left", "center", "right"] | Align = Align.LEFT,56        wrap: Literal["space", "any", "clip", "ellipsis"] | WrapMode = WrapMode.SPACE,57        layout: TextLayout | None = None,58    ) -> None:59        """60        :param text: markup for this widget; see :class:`Text` for61                     description of text markup62        :param cursor_position: position the cursor will appear in the63                                text when this widget is in focus64        :param align: typically ``'left'``, ``'center'`` or ``'right'``65        :type align: text alignment mode66        :param wrap: typically ``'space'``, ``'any'``, ``'clip'`` or ``'ellipsis'``67        :type wrap: text wrapping mode68        :param layout: defaults to a shared :class:`StandardTextLayout` instance69        :type layout: text layout instance70 71        This is a text widget that is selectable.  A cursor72        displayed at a fixed location in the text when in focus.73        This widget has no special handling of keyboard or mouse input.74        """75        super().__init__(text, align=align, wrap=wrap, layout=layout)76        self._cursor_position = cursor_position77 78    def render(  # type: ignore[override]79        self,80        size: tuple[int] | tuple[()],  # type: ignore[override]81        focus: bool = False,82    ) -> TextCanvas | CompositeCanvas:  # type: ignore[override]83        """84        Render the text content of this widget with a cursor when85        in focus.86 87        >>> si = SelectableIcon("[!]")88        >>> si89        <SelectableIcon selectable fixed/flow widget '[!]'>90        >>> si.render((4,), focus=True).cursor91        (0, 0)92        >>> si = SelectableIcon("((*))", 2)93        >>> si.render((8,), focus=True).cursor94        (2, 0)95        >>> si.render((2,), focus=True).cursor96        (0, 1)97        >>> si.render(()).cursor98        >>> si.render(()).text99        [b'((*))']100        >>> si.render((), focus=True).cursor101        (2, 0)102        """103        c: TextCanvas | CompositeCanvas = super().render(size, focus)104        if focus:105            # create a new canvas so we can add a cursor106            c = CompositeCanvas(c)107            c.cursor = self.get_cursor_coords(size)108        return c109 110    def get_cursor_coords(self, size: tuple[int] | tuple[()]) -> tuple[int, int] | None:111        """112        Return the position of the cursor if visible.  This method113        is required for widgets that display a cursor.114        """115        if self._cursor_position > len(self.text):116            return None117        # find out where the cursor will be displayed based on118        # the text layout119        if size:120            (maxcol,) = size121        else:122            maxcol, _ = self.pack()123        trans = self.get_line_translation(maxcol)124        x, y = calc_coords(self.text, trans, self._cursor_position)125        if maxcol <= x:126            return None127        return x, y128 129    def keypress(130        self,131        size: tuple[int] | tuple[()],  # type: ignore[override]132        key: str,133    ) -> str:134        """135        No keys are handled by this widget.  This method is136        required for selectable widgets.137        """138        return key139 140 141class CheckBoxError(WidgetError):142    pass143 144 145class CheckBox(WidgetWrap[Columns]):146    states: typing.ClassVar[dict[bool | Literal["mixed"], SelectableIcon]] = {147        True: SelectableIcon("[X]", 1),148        False: SelectableIcon("[ ]", 1),149        "mixed": SelectableIcon("[#]", 1),150    }151    reserve_columns = 4152 153    # allow users of this class to listen for change events154    # sent when the state of this widget is modified155    # (this variable is picked up by the MetaSignals metaclass)156    signals: typing.ClassVar[list[str]] = ["change", "postchange"]157 158    @typing.overload159    def __init__(160        self,161        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],162        state: bool = False,163        has_mixed: typing.Literal[False] = False,164        on_state_change: Callable[[Self, bool, _T], typing.Any] | None = None,165        user_data: _T = ...,166        checked_symbol: str | None = ...,167    ) -> None: ...168 169    @typing.overload170    def __init__(171        self,172        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],173        state: typing.Literal["mixed"] | bool = False,174        has_mixed: typing.Literal[True] = True,175        on_state_change: Callable[[Self, bool | typing.Literal["mixed"], _T], typing.Any] | None = None,176        user_data: _T = ...,177        checked_symbol: str | None = ...,178    ) -> None: ...179 180    @typing.overload181    def __init__(182        self,183        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],184        state: bool = False,185        has_mixed: typing.Literal[False] = False,186        on_state_change: Callable[[Self, bool], typing.Any] | None = None,187        user_data: None = None,188        checked_symbol: str | None = ...,189    ) -> None: ...190 191    @typing.overload192    def __init__(193        self,194        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],195        state: typing.Literal["mixed"] | bool = False,196        has_mixed: typing.Literal[True] = True,197        on_state_change: Callable[[Self, bool | typing.Literal["mixed"]], typing.Any] | None = None,198        user_data: None = None,199        checked_symbol: str | None = ...,200    ) -> None: ...201 202    def __init__(203        self,204        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],205        state: bool | Literal["mixed"] = False,206        has_mixed: typing.Literal[False, True] = False,  # MyPy issue: Literal[True, False] is not equal `bool`207        on_state_change: (208            Callable[[Self, bool, _T], typing.Any]209            | Callable[[Self, bool | typing.Literal["mixed"], _T], typing.Any]210            | Callable[[Self, bool], typing.Any]211            | Callable[[Self, bool | typing.Literal["mixed"]], typing.Any]212            | None213        ) = None,214        user_data: _T | None = None,215        checked_symbol: str | None = None,216    ):217        """218        :param label: markup for check box label219        :param state: False, True or "mixed"220        :param has_mixed: True if "mixed" is a state to cycle through221        :param on_state_change: shorthand for connect_signal()222                                function call for a single callback223        :param user_data: user_data for on_state_change224 225        ..note:: `pack` method expect, that `Columns` backend widget is not modified from outside226 227        Signals supported: ``'change'``, ``"postchange"``228 229        Register signal handler with::230 231          urwid.connect_signal(check_box, "change", callback, user_data)232 233        where callback is callback(check_box, new_state [,user_data])234        Unregister signal handlers with::235 236          urwid.disconnect_signal(check_box, "change", callback, user_data)237 238        >>> CheckBox("Confirm")239        <CheckBox selectable fixed/flow widget 'Confirm' state=False>240        >>> CheckBox("Yogourt", "mixed", True)241        <CheckBox selectable fixed/flow widget 'Yogourt' state='mixed'>242        >>> cb = CheckBox("Extra onions", True)243        >>> cb244        <CheckBox selectable fixed/flow widget 'Extra onions' state=True>245        >>> cb.render((20,), focus=True).text246        [b'[X] Extra onions    ']247        >>> CheckBox("Test", None)248        Traceback (most recent call last):249        ...250        ValueError: None not in (True, False, 'mixed')251        """252        if state not in self.states:253            raise ValueError(f"{state!r} not in {tuple(self.states.keys())}")254 255        self._label = Text(label)256        self.has_mixed = has_mixed257 258        self._state = state259        if checked_symbol:260            self.states[True] = SelectableIcon(f"[{checked_symbol}]", 1)261        # The old way of listening for a change was to pass the callback262        # in to the constructor.  Just convert it to the new way:263        if on_state_change:264            connect_signal(self, "change", on_state_change, user_data)265 266        # Initial create expect no callbacks call, create explicit267        super().__init__(268            Columns(269                [(self.reserve_columns, self.states[state]), self._label],270                focus_column=0,271            ),272        )273 274    def pack(self, size: tuple[()] | tuple[int] | None = None, focus: bool = False) -> tuple[str, str]:275        """Pack for widget.276 277        :param size: size data. Special case: None - get minimal widget size to fit278        :param focus: widget is focused279 280        >>> cb = CheckBox("test")281        >>> cb.pack((10,))282        (10, 1)283        >>> cb.pack()284        (8, 1)285        >>> ml_cb = CheckBox("Multi\\nline\\ncheckbox")286        >>> ml_cb.pack()287        (12, 3)288        >>> ml_cb.pack((), True)289        (12, 3)290        """291        return super().pack(size or (), focus)292 293    def _repr_words(self) -> list[str]:294        return [*super()._repr_words(), repr(self.label)]295 296    def _repr_attrs(self) -> dict[str, typing.Any]:297        return {**super()._repr_attrs(), "state": self.state}298 299    def set_label(self, label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]):300        """301        Change the check box label.302 303        label -- markup for label.  See Text widget for description304        of text markup.305 306        >>> cb = CheckBox("foo")307        >>> cb308        <CheckBox selectable fixed/flow widget 'foo' state=False>309        >>> cb.set_label(("bright_attr", "bar"))310        >>> cb311        <CheckBox selectable fixed/flow widget 'bar' state=False>312        """313        self._label.set_text(label)314        # no need to call self._invalidate(). WidgetWrap takes care of315        # that when self.w changes316 317    def get_label(self):318        """319        Return label text.320 321        >>> cb = CheckBox("Seriously")322        >>> print(cb.get_label())323        Seriously324        >>> print(cb.label)325        Seriously326        >>> cb.set_label([("bright_attr", "flashy"), " normal"])327        >>> print(cb.label)  #  only text is returned328        flashy normal329        """330        return self._label.text331 332    label = property(get_label)333 334    def set_state(335        self,336        state: bool | Literal["mixed"],337        do_callback: bool = True,338    ) -> None:339        """340        Set the CheckBox state.341 342        state -- True, False or "mixed"343        do_callback -- False to suppress signal from this change344 345        >>> from urwid import disconnect_signal346        >>> changes = []347        >>> def callback_a(user_data, cb, state):348        ...     changes.append("A %r %r" % (state, user_data))349        >>> def callback_b(cb, state):350        ...     changes.append("B %r" % state)351        >>> cb = CheckBox("test", False, False)352        >>> key1 = connect_signal(cb, "change", callback_a, user_args=("user_a",))353        >>> key2 = connect_signal(cb, "change", callback_b)354        >>> cb.set_state(True)  # both callbacks will be triggered355        >>> cb.state356        True357        >>> disconnect_signal(cb, "change", callback_a, user_args=("user_a",))358        >>> cb.state = False359        >>> cb.state360        False361        >>> cb.set_state(True)362        >>> cb.state363        True364        >>> cb.set_state(False, False)  # don't send signal365        >>> changes366        ["A True 'user_a'", 'B True', 'B False', 'B True']367        """368        if self._state == state:369            return370 371        if state not in self.states:372            raise CheckBoxError(f"{self!r} Invalid state: {state!r}")373 374        # self._state is None is a special case when the CheckBox375        # has just been created376        old_state = self._state377        if do_callback:378            self._emit("change", state)379        self._state = state380        # rebuild the display widget with the new state381        self._w = Columns([(self.reserve_columns, self.states[state]), self._label], focus_column=0)382        if do_callback:383            self._emit("postchange", old_state)384 385    def get_state(self) -> bool | Literal["mixed"]:386        """Return the state of the checkbox."""387        return self._state388 389    state = property(get_state, set_state)390 391    def keypress(self, size: tuple[int], key: str) -> str | None:392        """393        Toggle state on 'activate' command.394 395        >>> assert CheckBox._command_map[" "] == "activate"396        >>> assert CheckBox._command_map["enter"] == "activate"397        >>> size = (10,)398        >>> cb = CheckBox("press me")399        >>> cb.state400        False401        >>> cb.keypress(size, " ")402        >>> cb.state403        True404        >>> cb.keypress(size, " ")405        >>> cb.state406        False407        """408        if self._command_map[key] != Command.ACTIVATE:409            return key410 411        self.toggle_state()412        return None413 414    def toggle_state(self) -> None:415        """416        Cycle to the next valid state.417 418        >>> cb = CheckBox("3-state", has_mixed=True)419        >>> cb.state420        False421        >>> cb.toggle_state()422        >>> cb.state423        True424        >>> cb.toggle_state()425        >>> cb.state426        'mixed'427        >>> cb.toggle_state()428        >>> cb.state429        False430        """431        if self.state is False:432            self.set_state(True)433        elif self.state is True:434            if self.has_mixed:435                self.set_state("mixed")436            else:437                self.set_state(False)438        elif self.state == "mixed":439            self.set_state(False)440 441    def mouse_event(self, size: tuple[int], event: str, button: int, x: int, y: int, focus: bool) -> bool:442        """443        Toggle state on button 1 press.444 445        >>> size = (20,)446        >>> cb = CheckBox("clickme")447        >>> cb.state448        False449        >>> cb.mouse_event(size, "mouse press", 1, 2, 0, True)450        True451        >>> cb.state452        True453        """454        if button != 1 or not is_mouse_press(event):455            return False456        self.toggle_state()457        return True458 459 460class RadioButton(CheckBox):461    states: typing.ClassVar[dict[bool | Literal["mixed"], SelectableIcon]] = {462        True: SelectableIcon("(X)", 1),463        False: SelectableIcon("( )", 1),464        "mixed": SelectableIcon("(#)", 1),465    }466    reserve_columns = 4467 468    @typing.overload469    def __init__(470        self,471        group: MutableSequence[RadioButton],472        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],473        state: bool | Literal["first True"] = ...,474        on_state_change: Callable[[Self, bool, _T], typing.Any] | None = None,475        user_data: _T = ...,476    ) -> None: ...477 478    @typing.overload479    def __init__(480        self,481        group: MutableSequence[RadioButton],482        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],483        state: bool | Literal["first True"] = ...,484        on_state_change: Callable[[Self, bool], typing.Any] | None = None,485        user_data: None = None,486    ) -> None: ...487 488    def __init__(489        self,490        group: MutableSequence[RadioButton],491        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],492        state: bool | Literal["first True"] = "first True",493        on_state_change: Callable[[Self, bool, _T], typing.Any] | Callable[[Self, bool], typing.Any] | None = None,494        user_data: _T | None = None,495    ) -> None:496        """497        :param group: list for radio buttons in same group498        :param label: markup for radio button label499        :param state: False, True, "mixed" or "first True"500        :param on_state_change: shorthand for connect_signal()501                                function call for a single 'change' callback502        :param user_data: user_data for on_state_change503 504        ..note:: `pack` method expect, that `Columns` backend widget is not modified from outside505 506        This function will append the new radio button to group.507        "first True" will set to True if group is empty.508 509        Signals supported: ``'change'``, ``"postchange"``510 511        Register signal handler with::512 513          urwid.connect_signal(radio_button, "change", callback, user_data)514 515        where callback is callback(radio_button, new_state [,user_data])516        Unregister signal handlers with::517 518          urwid.disconnect_signal(radio_button, "change", callback, user_data)519 520        >>> bgroup = []  # button group521        >>> b1 = RadioButton(bgroup, "Agree")522        >>> b2 = RadioButton(bgroup, "Disagree")523        >>> len(bgroup)524        2525        >>> b1526        <RadioButton selectable fixed/flow widget 'Agree' state=True>527        >>> b2528        <RadioButton selectable fixed/flow widget 'Disagree' state=False>529        >>> b2.render((15,), focus=True).text  # ... = b in Python 3530        [...'( ) Disagree   ']531        """532        if state == "first True":533            state = not group534 535        self.group = group536        super().__init__(label, state, False, on_state_change, user_data)  # type: ignore[call-overload]537        group.append(self)538 539    def set_state(self, state: bool | Literal["mixed"], do_callback: bool = True) -> None:540        """541        Set the RadioButton state.542 543        state -- True, False or "mixed"544 545        do_callback -- False to suppress signal from this change546 547        If state is True all other radio buttons in the same button548        group will be set to False.549 550        >>> bgroup = []  # button group551        >>> b1 = RadioButton(bgroup, "Agree")552        >>> b2 = RadioButton(bgroup, "Disagree")553        >>> b3 = RadioButton(bgroup, "Unsure")554        >>> b1.state, b2.state, b3.state555        (True, False, False)556        >>> b2.set_state(True)557        >>> b1.state, b2.state, b3.state558        (False, True, False)559        >>> def relabel_button(radio_button, new_state):560        ...     radio_button.set_label("Think Harder!")561        >>> key = connect_signal(b3, "change", relabel_button)562        >>> b3563        <RadioButton selectable fixed/flow widget 'Unsure' state=False>564        >>> b3.set_state(True)  # this will trigger the callback565        >>> b3566        <RadioButton selectable fixed/flow widget 'Think Harder!' state=True>567        """568        if self._state == state:569            return570 571        super().set_state(state, do_callback)572 573        # if we're clearing the state we don't have to worry about574        # other buttons in the button group575        if state is not True:576            return577 578        # clear the state of each other radio button579        for cb in self.group:580            if cb is self:581                continue582            if cb.state:583                cb.state = False584 585    def toggle_state(self) -> None:586        """587        Set state to True.588 589        >>> bgroup = []  # button group590        >>> b1 = RadioButton(bgroup, "Agree")591        >>> b2 = RadioButton(bgroup, "Disagree")592        >>> b1.state, b2.state593        (True, False)594        >>> b2.toggle_state()595        >>> b1.state, b2.state596        (False, True)597        >>> b2.toggle_state()598        >>> b1.state, b2.state599        (False, True)600        """601        self.set_state(True)602 603 604class Button(WidgetWrap[Columns]):605    button_left = Text("<")606    button_right = Text(">")607 608    signals: typing.ClassVar[list[str]] = ["click"]609 610    @typing.overload611    def __init__(612        self,613        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],614        on_press: Callable[[Self, _T], typing.Any] | None = None,615        user_data: _T = ...,616        *,617        align: Literal["left", "center", "right"] | Align = ...,618        wrap: Literal["space", "any", "clip", "ellipsis"] | WrapMode = ...,619        layout: TextLayout | None = ...,620    ) -> None: ...621 622    @typing.overload623    def __init__(624        self,625        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],626        on_press: Callable[[Self], typing.Any] | None = None,627        user_data: None = None,628        *,629        align: Literal["left", "center", "right"] | Align = ...,630        wrap: Literal["space", "any", "clip", "ellipsis"] | WrapMode = ...,631        layout: TextLayout | None = ...,632    ) -> None: ...633 634    def __init__(635        self,636        label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]],637        on_press: Callable[[Self, _T], typing.Any] | Callable[[Self], typing.Any] | None = None,638        user_data: _T | None = None,639        *,640        align: Literal["left", "center", "right"] | Align = Align.LEFT,641        wrap: Literal["space", "any", "clip", "ellipsis"] | WrapMode = WrapMode.SPACE,642        layout: TextLayout | None = None,643    ) -> None:644        """645        :param label: markup for button label646        :param on_press: shorthand for connect_signal()647                         function call for a single callback648        :param user_data: user_data for on_press649        :param align: typically ``'left'``, ``'center'`` or ``'right'``650        :type align: label alignment mode651        :param wrap: typically ``'space'``, ``'any'``, ``'clip'`` or ``'ellipsis'``652        :type wrap: label wrapping mode653        :param layout: defaults to a shared :class:`StandardTextLayout` instance654        :type layout: text layout instance655 656        ..note:: `pack` method expect, that `Columns` backend widget is not modified from outside657 658        Signals supported: ``'click'``659 660        Register signal handler with::661 662          urwid.connect_signal(button, "click", callback, user_data)663 664        where callback is callback(button [,user_data])665        Unregister signal handlers with::666 667          urwid.disconnect_signal(button, "click", callback, user_data)668 669        >>> from urwid.util import set_temporary_encoding670        >>> Button("Ok")671        <Button selectable fixed/flow widget 'Ok'>672        >>> b = Button("Cancel")673        >>> b.render((15,), focus=True).text  # ... = b in Python 3674        [b'< Cancel      >']675        >>> aligned_button = Button("Test", align=Align.CENTER)676        >>> aligned_button.render((10,), focus=True).text677        [b'<  Test  >']678        >>> wrapped_button = Button("Long label", wrap=WrapMode.ELLIPSIS)679        >>> with set_temporary_encoding("utf-8"):680        ...     wrapped_button.render((7,), focus=False).text[0].decode("utf-8")681        '< Lo… >'682        """683        self._label = SelectableIcon(label, 0, align=align, wrap=wrap, layout=layout)684        cols = Columns(685            [(1, self.button_left), self._label, (1, self.button_right)],686            dividechars=1,687        )688        super().__init__(cols)689 690        # The old way of listening for a change was to pass the callback691        # in to the constructor.  Just convert it to the new way:692        if on_press:693            connect_signal(self, "click", on_press, user_data)694 695    def pack(self, size: tuple[()] | tuple[int] | None = None, focus: bool = False) -> tuple[int, int]:696        """Pack for widget.697 698        :param size: size data. Special case: None - get minimal widget size to fit699        :param focus: widget is focused700 701        >>> btn = Button("Some button")702        >>> btn.pack((10,))703        (10, 2)704        >>> btn.pack()705        (15, 1)706        >>> btn.pack((), True)707        (15, 1)708        """709        return super().pack(size or (), focus)710 711    def _repr_words(self) -> list[str]:712        # include button.label in repr(button)713        return [*super()._repr_words(), repr(self.label)]714 715    def set_label(self, label: str | tuple[Hashable, str] | list[str | tuple[Hashable, str]]) -> None:716        """717        Change the button label.718 719        label -- markup for button label720 721        >>> b = Button("Ok")722        >>> b.set_label("Yup yup")723        >>> b724        <Button selectable fixed/flow widget 'Yup yup'>725        """726        self._label.set_text(label)727 728    def get_label(self) -> str | bytes:729        """730        Return label text.731 732        >>> b = Button("Ok")733        >>> print(b.get_label())734        Ok735        >>> print(b.label)736        Ok737        """738        return self._label.text739 740    label = property(get_label)741 742    def keypress(self, size: tuple[int], key: str) -> str | None:743        """744        Send 'click' signal on 'activate' command.745 746        >>> assert Button._command_map[" "] == "activate"747        >>> assert Button._command_map["enter"] == "activate"748        >>> size = (15,)749        >>> b = Button("Cancel")750        >>> clicked_buttons = []751        >>> def handle_click(button):752        ...     clicked_buttons.append(button.label)753        >>> key = connect_signal(b, "click", handle_click)754        >>> b.keypress(size, "enter")755        >>> b.keypress(size, " ")756        >>> clicked_buttons  # ... = u in Python 2757        [...'Cancel', ...'Cancel']758        """759        if self._command_map[key] != Command.ACTIVATE:760            return key761 762        self._emit("click")763        return None764 765    def mouse_event(self, size: tuple[int], event: str, button: int, x: int, y: int, focus: bool) -> bool:766        """767        Send 'click' signal on button 1 press.768 769        >>> size = (15,)770        >>> b = Button("Ok")771        >>> clicked_buttons = []772        >>> def handle_click(button):773        ...     clicked_buttons.append(button.label)774        >>> key = connect_signal(b, "click", handle_click)775        >>> b.mouse_event(size, "mouse press", 1, 4, 0, True)776        True777        >>> b.mouse_event(size, "mouse press", 2, 4, 0, True)  # ignored778        False779        >>> clicked_buttons  # ... = u in Python 2780        [...'Ok']781        """782        if button != 1 or not is_mouse_press(event):783            return False784 785        self._emit("click")786        return True787 788 789def _test():790    import doctest791 792    doctest.testmod()793 794 795if __name__ == "__main__":796    _test()797 
codekingpro/portable-devtools · Team Ai