urwid 4.0.4__tar.gz → 4.0.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {urwid-4.0.4/urwid.egg-info → urwid-4.0.6}/PKG-INFO +1 -1
- {urwid-4.0.4 → urwid-4.0.6}/docs/changelog.rst +51 -0
- urwid-4.0.6/ruff-requirements.txt +1 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_columns.py +109 -23
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_graphics.py +1 -1
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_listbox.py +1 -1
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_main_loop.py +1 -1
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_overlay.py +2 -2
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_pile.py +114 -28
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_scrollable.py +66 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_signals.py +24 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_widget.py +52 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/__init__.py +2 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/canvas.py +24 -17
- {urwid-4.0.4 → urwid-4.0.6}/urwid/display/_posix_raw_display.py +1 -1
- {urwid-4.0.4 → urwid-4.0.6}/urwid/display/_raw_display_base.py +2 -4
- {urwid-4.0.4 → urwid-4.0.6}/urwid/display/web.py +14 -9
- {urwid-4.0.4 → urwid-4.0.6}/urwid/event_loop/main_loop.py +4 -4
- {urwid-4.0.4 → urwid-4.0.6}/urwid/event_loop/tornado_loop.py +1 -1
- {urwid-4.0.4 → urwid-4.0.6}/urwid/numedit.py +1 -1
- {urwid-4.0.4 → urwid-4.0.6}/urwid/signals.py +1 -1
- {urwid-4.0.4 → urwid-4.0.6}/urwid/split_repr.py +1 -1
- {urwid-4.0.4 → urwid-4.0.6}/urwid/util.py +12 -7
- {urwid-4.0.4 → urwid-4.0.6}/urwid/version.py +3 -3
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/__init__.py +8 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/attr_map.py +8 -4
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/attr_wrap.py +4 -4
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/box_adapter.py +3 -2
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/columns.py +50 -33
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/container.py +15 -20
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/edit.py +115 -53
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/filler.py +28 -9
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/frame.py +46 -24
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/grid_flow.py +31 -27
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/line_box.py +4 -4
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/listbox.py +15 -13
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/overlay.py +6 -6
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/padding.py +8 -5
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/pile.py +41 -33
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/popup.py +25 -13
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/scrollable.py +119 -72
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/widget.py +235 -34
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/widget_decoration.py +16 -11
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/wimp.py +4 -4
- {urwid-4.0.4 → urwid-4.0.6/urwid.egg-info}/PKG-INFO +1 -1
- {urwid-4.0.4 → urwid-4.0.6}/urwid.egg-info/scm_file_list.json +265 -265
- urwid-4.0.6/urwid.egg-info/scm_version.json +8 -0
- urwid-4.0.4/ruff-requirements.txt +0 -1
- urwid-4.0.4/urwid.egg-info/scm_version.json +0 -8
- {urwid-4.0.4 → urwid-4.0.6}/.coveralls.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.devcontainer/dev.dockerfile +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.devcontainer/devcontainer.json +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.dockerignore +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.editorconfig +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.github/CODE_OF_CONDUCT.md +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.github/ISSUE_TEMPLATE/docs.md +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.github/ISSUE_TEMPLATE/question.md +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.github/dependabot.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.github/labeler.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.github/release.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.github/workflows/documentation.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.github/workflows/isolated_static_check.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.github/workflows/labels.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.github/workflows/pythonpackage.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.github/workflows/yamllint.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.gitignore +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.pre-commit-config.yaml +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/.yamllint.yml +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/COPYING +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/MANIFEST.in +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/README.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/SECURITY.md +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/classifiers.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/Makefile +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/conf.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/bigtext.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/bigtext.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/bigtext1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/bigtext2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/bigtext3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/browse.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/browse.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/browse1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/browse2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/edit.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/edit.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/edit1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/edit2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/edit_text.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/graph.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/graph.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/graph1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/graph2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/index.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/palette_test.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/palette_test.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/palette_test1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/palette_test2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/pop_up.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/pop_up.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/pop_up1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/pop_up2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/real_browse.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/real_edit.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/subproc.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/subproc.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/subproc1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/subproc2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/subproc2.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/tour.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/tour.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/tour1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/examples/tour2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/index.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/bright_combinations.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/bright_combinations.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/bright_combinations1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/canvascache.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/displayattributes.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/displaymodules.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/encodings.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/images/display_modules.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/images/introduction.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/images/urwid_widgets.svgz +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/images/urwid_widgets_1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/images/urwid_widgets_1.xcf +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/images/urwid_widgets_2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/images/urwid_widgets_2.xcf +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/images/widget_layout.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/index.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/mainloop.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/overview.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/safe_combinations.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/safe_combinations.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/safe_combinations1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/textlayout.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/userinput.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/wanat.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/wanat_multi.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/wcur1.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/wcur2.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/widgets.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/wmod.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/manual/wsel.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/attrspec.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/canvas.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/command_map.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/constants.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/deprecated.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/display_modules.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/exceptions.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/global_settings.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/index.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/list_walkers.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/main_loop.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/meta.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/signals.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/text_layout.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/reference/widget.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tools/compile_pngs.sh +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tools/screenshots.sh +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tools/static/.placeholder +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tools/templates/indexcontent.html +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tools/templates/indexsidebar.html +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tools/templates/localtoc.html +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/adventure.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/adventure.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/adventure1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/adventure2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/adventure3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/adventure4.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/cmenu.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/cmenu.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/cmenu1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/cmenu2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/cmenu3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/cmenu4.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/highcolors.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/highcolors.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/highcolors1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/hmenu.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/hmenu.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/hmenu1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/hmenu2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/hmenu3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/hmenu4.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/index.rst +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/input.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/input.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/input1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/input2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/input3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/input4.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/input5.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/menu25.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/minimal.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/minimal.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/minimal1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/multiple.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/multiple.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/multiple1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/multiple2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/multiple3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/multiple4.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/qa.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/qa.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/qa1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/qa2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/qa3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/sig.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/sig.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/sig1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/sig2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/sig3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/sig4.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/smenu.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/smenu.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/smenu1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/smenu2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/smenu3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/urwid_attr.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/urwid_attr.py.xdotool +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/urwid_attr1.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/urwid_attr2.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/urwid_attr3.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/tutorial/urwid_attr4.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/docs/urwid-logo.png +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/asyncio_socket_server.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/bigtext.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/browse.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/calc.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/dialog.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/edit.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/fib.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/graph.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/input_test.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/lcd_cf635.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/palette_test.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/pop_up.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/subproc.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/subproc2.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/terminal.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/tour.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/treesample.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/twisted_serve_ssh.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/examples/twisted_serve_ssh.tac +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/pylint-requirements.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/pyproject.toml +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/requirements.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/setup.cfg +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/test_requirements.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/__init__.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_canvas.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_container.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_doctests.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_escapes.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_event_loops.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_filler.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_floatedit.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_font.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_frame.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_grapheme_clusters.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_grid_flow.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_line_box.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_padding.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_raw_display.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_str_util.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_text_layout.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_tree.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_util.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/test_vterm.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tests/util.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/tox.ini +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/command_map.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/display/__init__.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/display/_web.css +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/display/_web.js +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/display/_win32.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/display/_win32_raw_display.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/display/common.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/display/curses.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/display/escape.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/display/html_fragment.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/display/lcd.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/display/raw.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/event_loop/__init__.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/event_loop/abstract_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/event_loop/asyncio_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/event_loop/glib_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/event_loop/select_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/event_loop/trio_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/event_loop/twisted_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/event_loop/zmq_loop.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/font.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/graphics.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/py.typed +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/str_util.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/text_layout.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/vterm.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/bar_graph.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/big_text.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/constants.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/divider.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/monitored_list.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/progress_bar.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/solid_fill.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/text.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid/widget/treetools.py +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid.egg-info/SOURCES.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid.egg-info/dependency_links.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid.egg-info/not-zip-safe +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid.egg-info/requires.txt +0 -0
- {urwid-4.0.4 → urwid-4.0.6}/urwid.egg-info/top_level.txt +0 -0
|
@@ -2,6 +2,57 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
---------
|
|
4
4
|
|
|
5
|
+
Urwid 4.0.6
|
|
6
|
+
===========
|
|
7
|
+
|
|
8
|
+
2026-07-23
|
|
9
|
+
|
|
10
|
+
Implemented protocol base classes `AbstractWidget` and internal `AbstractBoxWidget`, `AbstractFlowWidget` and `AbstractFixedWidget` for protocol based typing.
|
|
11
|
+
All classes implementing `AbstractWidget` are considered valid widget (as all required APIs available).
|
|
12
|
+
Container widgets as `Columns`, `Pile`, `GridFlow`, `Frame`, `Overlay` and `Padding` now declare expected APIs from widgets depends on sizing parameter in `__init__`.
|
|
13
|
+
|
|
14
|
+
Bug fixes 🕷
|
|
15
|
+
++++++++++++
|
|
16
|
+
* Fix web display broken by token_urlsafe session ids by @2018302345 in https://github.com/urwid/urwid/pull/1195
|
|
17
|
+
Documentation 🕮
|
|
18
|
+
++++++++++++++++
|
|
19
|
+
* Typing: fix scrollable module annotations by @penguinolog in https://github.com/urwid/urwid/pull/1194
|
|
20
|
+
* Typing: introduce `AbstractWidget` abstract base class/protocol by @penguinolog in https://github.com/urwid/urwid/pull/1196
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
New Contributors
|
|
24
|
+
++++++++++++++++
|
|
25
|
+
* @2018302345 made their first contribution in https://github.com/urwid/urwid/pull/1195
|
|
26
|
+
|
|
27
|
+
**Full Changelog**: https://github.com/urwid/urwid/compare/4.0.5...4.0.6
|
|
28
|
+
|
|
29
|
+
Urwid 4.0.5
|
|
30
|
+
===========
|
|
31
|
+
|
|
32
|
+
2026-07-20
|
|
33
|
+
|
|
34
|
+
New features 🗹
|
|
35
|
+
+++++++++++++++
|
|
36
|
+
* Copy signal handlers before emission by @Sanjays2402 in https://github.com/urwid/urwid/pull/1187
|
|
37
|
+
* Implement `_scrollbar_layout` and left click handling for `Scrollable` by @penguinolog in https://github.com/urwid/urwid/pull/1192
|
|
38
|
+
Bug fixes 🕷
|
|
39
|
+
++++++++++++
|
|
40
|
+
* Fix rle_product for zero-len elements by @penguinolog in https://github.com/urwid/urwid/pull/1191
|
|
41
|
+
Documentation 🕮
|
|
42
|
+
++++++++++++++++
|
|
43
|
+
* typing: fix `Filler` typing annotations by @penguinolog in https://github.com/urwid/urwid/pull/1183
|
|
44
|
+
* typing: extend widget_decoration module typing coverage by @penguinolog in https://github.com/urwid/urwid/pull/1184
|
|
45
|
+
* Typing: annotate and fix attr_map by @penguinolog in https://github.com/urwid/urwid/pull/1186
|
|
46
|
+
Refactoring 🛠
|
|
47
|
+
++++++++++++++
|
|
48
|
+
* Migrate `Edit` widget to use `WidgetWrap` as base class and update methods by @penguinolog in https://github.com/urwid/urwid/pull/1180
|
|
49
|
+
|
|
50
|
+
New Contributors
|
|
51
|
+
++++++++++++++++
|
|
52
|
+
* @Sanjays2402 made their first contribution in https://github.com/urwid/urwid/pull/1187
|
|
53
|
+
|
|
54
|
+
**Full Changelog**: https://github.com/urwid/urwid/compare/4.0.4...4.0.5
|
|
55
|
+
|
|
5
56
|
Urwid 4.0.4
|
|
6
57
|
===========
|
|
7
58
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruff==0.15.22
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import typing
|
|
4
4
|
import unittest
|
|
5
|
+
import warnings
|
|
5
6
|
|
|
6
7
|
import urwid
|
|
7
8
|
from tests.util import SelectableText
|
|
@@ -12,6 +13,85 @@ if typing.TYPE_CHECKING:
|
|
|
12
13
|
from typing_extensions import Literal
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
class NotAWidget:
|
|
17
|
+
__slots__ = ("name", "symbol")
|
|
18
|
+
|
|
19
|
+
def __init__(self, name: str, symbol: bytes) -> None:
|
|
20
|
+
self.name = name
|
|
21
|
+
self.symbol = symbol
|
|
22
|
+
|
|
23
|
+
def __repr__(self) -> str:
|
|
24
|
+
return f"{self.__class__.__name__}(name={self.name!r}, symbol={self.symbol!r})"
|
|
25
|
+
|
|
26
|
+
def selectable(self) -> bool:
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
def rows(self, max_col_row: tuple[int], focus: bool = False) -> int:
|
|
30
|
+
return 1
|
|
31
|
+
|
|
32
|
+
def render(self, max_col_row: tuple[int, int] | tuple[int], focus: bool = False) -> urwid.Canvas:
|
|
33
|
+
maxcol = max_col_row[0]
|
|
34
|
+
line = self.symbol * maxcol
|
|
35
|
+
if len(max_col_row) == 1:
|
|
36
|
+
return urwid.TextCanvas((line,), maxcol=maxcol)
|
|
37
|
+
return urwid.TextCanvas((line,) * max_col_row[1], maxcol=maxcol)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ImplementWidget:
|
|
41
|
+
__slots__ = ("name", "symbol")
|
|
42
|
+
|
|
43
|
+
def sizing(self) -> frozenset[urwid.Sizing]:
|
|
44
|
+
return frozenset((urwid.BOX, urwid.FLOW))
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def base_widget(self) -> urwid.AbstractWidget:
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
def __init__(self, name: str, symbol: bytes) -> None:
|
|
51
|
+
self.name = name
|
|
52
|
+
self.symbol = symbol
|
|
53
|
+
|
|
54
|
+
def __repr__(self) -> str:
|
|
55
|
+
return f"{self.__class__.__name__}(name={self.name!r}, symbol={self.symbol!r})"
|
|
56
|
+
|
|
57
|
+
def selectable(self) -> bool:
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def focus(self) -> None:
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
def _invalidate(self) -> None:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
def rows(self, max_col_row: tuple[int], focus: bool = False) -> int:
|
|
68
|
+
return 1
|
|
69
|
+
|
|
70
|
+
def pack(self, size: typing.Any, focus: bool = False) -> tuple[int, int]:
|
|
71
|
+
return self.rows(size, focus)
|
|
72
|
+
|
|
73
|
+
def render(self, max_col_row: tuple[int, int] | tuple[int], focus: bool = False) -> urwid.Canvas:
|
|
74
|
+
maxcol = max_col_row[0]
|
|
75
|
+
line = self.symbol * maxcol
|
|
76
|
+
if len(max_col_row) == 1:
|
|
77
|
+
return urwid.TextCanvas((line,), maxcol=maxcol)
|
|
78
|
+
return urwid.TextCanvas((line,) * max_col_row[1], maxcol=maxcol)
|
|
79
|
+
|
|
80
|
+
def keypress(self, size: tuple[int, int] | tuple[int], key: str) -> str | None:
|
|
81
|
+
return key
|
|
82
|
+
|
|
83
|
+
def mouse_event(
|
|
84
|
+
self,
|
|
85
|
+
size: tuple[int, int] | tuple[int],
|
|
86
|
+
event: str,
|
|
87
|
+
button: int,
|
|
88
|
+
col: int,
|
|
89
|
+
row: int,
|
|
90
|
+
focus: bool,
|
|
91
|
+
) -> bool | None:
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
|
|
15
95
|
class ColumnsTest(unittest.TestCase):
|
|
16
96
|
def test_basic_sizing(self) -> None:
|
|
17
97
|
box_only = urwid.SolidFill("#")
|
|
@@ -320,29 +400,6 @@ class ColumnsTest(unittest.TestCase):
|
|
|
320
400
|
self.assertEqual((" ",), canvas.decoded_text)
|
|
321
401
|
|
|
322
402
|
def test_not_a_widget(self):
|
|
323
|
-
class NotAWidget:
|
|
324
|
-
__slots__ = ("name", "symbol")
|
|
325
|
-
|
|
326
|
-
def __init__(self, name: str, symbol: bytes) -> None:
|
|
327
|
-
self.name = name
|
|
328
|
-
self.symbol = symbol
|
|
329
|
-
|
|
330
|
-
def __repr__(self) -> str:
|
|
331
|
-
return f"{self.__class__.__name__}(name={self.name!r}, symbol={self.symbol!r})"
|
|
332
|
-
|
|
333
|
-
def selectable(self) -> bool:
|
|
334
|
-
return False
|
|
335
|
-
|
|
336
|
-
def rows(self, max_col_row: tuple[int], focus: bool = False) -> int:
|
|
337
|
-
return 1
|
|
338
|
-
|
|
339
|
-
def render(self, max_col_row: tuple[int, int] | tuple[int], focus: bool = False) -> urwid.Canvas:
|
|
340
|
-
maxcol = max_col_row[0]
|
|
341
|
-
line = self.symbol * maxcol
|
|
342
|
-
if len(max_col_row) == 1:
|
|
343
|
-
return urwid.TextCanvas((line,), maxcol=maxcol)
|
|
344
|
-
return urwid.TextCanvas((line,) * max_col_row[1], maxcol=maxcol)
|
|
345
|
-
|
|
346
403
|
with self.subTest("Box"), self.assertWarns(urwid.widget.ColumnsWarning) as ctx:
|
|
347
404
|
items = (NotAWidget("First", b"*"), NotAWidget("Second", b"^"))
|
|
348
405
|
widget = urwid.Columns(items)
|
|
@@ -359,6 +416,35 @@ class ColumnsTest(unittest.TestCase):
|
|
|
359
416
|
self.assertEqual(f"{items[0]!r} is not a Widget", str(ctx.warnings[0].message))
|
|
360
417
|
self.assertEqual(f"{items[1]!r} is not a Widget", str(ctx.warnings[1].message))
|
|
361
418
|
|
|
419
|
+
def test_implement_widget_interface(self):
|
|
420
|
+
with (
|
|
421
|
+
warnings.catch_warnings(record=True) as collected_w,
|
|
422
|
+
self.subTest("Box"),
|
|
423
|
+
):
|
|
424
|
+
items = (ImplementWidget("First", b"*"), ImplementWidget("Second", b"^"))
|
|
425
|
+
widget = urwid.Columns(items)
|
|
426
|
+
|
|
427
|
+
self.assertEqual(("**^^", "**^^"), widget.render((4, 2)).decoded_text)
|
|
428
|
+
|
|
429
|
+
columns_warnings = tuple(
|
|
430
|
+
warning.message for warning in collected_w if warning.category == urwid.widget.ColumnsWarning
|
|
431
|
+
)
|
|
432
|
+
self.assertTrue(len(columns_warnings) == 0, "No warnings expected")
|
|
433
|
+
|
|
434
|
+
with (
|
|
435
|
+
warnings.catch_warnings(record=True) as collected_w,
|
|
436
|
+
self.subTest("Flow"),
|
|
437
|
+
):
|
|
438
|
+
items = (ImplementWidget("First", b"*"), ImplementWidget("Second", b"^"))
|
|
439
|
+
widget = urwid.Columns(items)
|
|
440
|
+
|
|
441
|
+
self.assertEqual(("***^^^",), widget.render((6,)).decoded_text)
|
|
442
|
+
|
|
443
|
+
columns_warnings = tuple(
|
|
444
|
+
warning.message for warning in collected_w if warning.category == urwid.widget.ColumnsWarning
|
|
445
|
+
)
|
|
446
|
+
self.assertTrue(len(columns_warnings) == 0, "No warnings expected")
|
|
447
|
+
|
|
362
448
|
def test_zero_width_column(self):
|
|
363
449
|
elem_1 = urwid.BoxAdapter(urwid.SolidFill("#"), 2)
|
|
364
450
|
elem_2 = urwid.BoxAdapter(urwid.SolidFill("*"), 4)
|
|
@@ -13,7 +13,7 @@ class BarGraphTest(unittest.TestCase):
|
|
|
13
13
|
assert rval == exp, f"{desc} expected {exp!r}, got {rval!r}"
|
|
14
14
|
|
|
15
15
|
def test1(self):
|
|
16
|
-
self.bgtest("simplest", [[0]], 5, [1], 1, [(1, [(0, 1)])])
|
|
16
|
+
self.bgtest("simplest", [[0]], 5, [1], 1, [(1, [(0, 1)])])
|
|
17
17
|
self.bgtest(
|
|
18
18
|
"simpler",
|
|
19
19
|
[[0], [0]],
|
|
@@ -1965,7 +1965,7 @@ class PageDownAboveTopTest(unittest.TestCase):
|
|
|
1965
1965
|
# Two-row rows so the four-item list does not fit the viewport at once.
|
|
1966
1966
|
lb = urwid.ListBox(
|
|
1967
1967
|
urwid.SimpleListWalker(
|
|
1968
|
-
|
|
1968
|
+
[
|
|
1969
1969
|
urwid.Text("a0\nb0"),
|
|
1970
1970
|
SelectableText("a1\nb1"),
|
|
1971
1971
|
urwid.Text("a2\nb2"),
|
|
@@ -22,7 +22,7 @@ class ClosingTemporaryFilesPair(typing.ContextManager[tuple[typing.TextIO, typin
|
|
|
22
22
|
Since `sys.stdout` is TextIO, tests have to use compatible api for the proper behavior imitation.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
__slots__ = ("
|
|
25
|
+
__slots__ = ("rd_f", "rd_s", "wr_f", "wr_s")
|
|
26
26
|
|
|
27
27
|
def __init__(self) -> None:
|
|
28
28
|
self.rd_s: socket.socket | None = None
|
|
@@ -154,7 +154,7 @@ class OverlayTest(unittest.TestCase):
|
|
|
154
154
|
self.assertEqual(rows, canvas.rows())
|
|
155
155
|
|
|
156
156
|
self.assertEqual(
|
|
157
|
-
"##########################\n
|
|
157
|
+
"##########################\n#Flow and Fixed widget ##\n##########################",
|
|
158
158
|
str(canvas),
|
|
159
159
|
)
|
|
160
160
|
|
|
@@ -182,7 +182,7 @@ class OverlayTest(unittest.TestCase):
|
|
|
182
182
|
self.assertEqual(rows, canvas.rows())
|
|
183
183
|
|
|
184
184
|
self.assertEqual(
|
|
185
|
-
"#########################\n
|
|
185
|
+
"#########################\n#Flow and Fixed widget #\n#########################",
|
|
186
186
|
str(canvas),
|
|
187
187
|
)
|
|
188
188
|
|
|
@@ -1,11 +1,96 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import unittest
|
|
4
|
+
import warnings
|
|
4
5
|
|
|
5
6
|
import urwid
|
|
6
7
|
from tests.util import SelectableText
|
|
7
8
|
|
|
8
9
|
|
|
10
|
+
class NotAWidget:
|
|
11
|
+
__slots__ = ("name", "symbol")
|
|
12
|
+
|
|
13
|
+
def __init__(self, name: str, symbol: bytes) -> None:
|
|
14
|
+
self.name = name
|
|
15
|
+
self.symbol = symbol
|
|
16
|
+
|
|
17
|
+
def __repr__(self) -> str:
|
|
18
|
+
return f"{self.__class__.__name__}(name={self.name!r}, symbol={self.symbol!r})"
|
|
19
|
+
|
|
20
|
+
def selectable(self) -> bool:
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
def pack(self, max_col_row: tuple[int, int] | tuple[int], focus: bool = False) -> int:
|
|
24
|
+
if len(max_col_row) == 2:
|
|
25
|
+
return max_col_row
|
|
26
|
+
return max_col_row[0], self.rows(max_col_row)
|
|
27
|
+
|
|
28
|
+
def rows(self, max_col_row: tuple[int], focus=False) -> int:
|
|
29
|
+
return 1
|
|
30
|
+
|
|
31
|
+
def render(self, max_col_row: tuple[int, int] | tuple[int], focus: bool = False) -> urwid.Canvas:
|
|
32
|
+
maxcol = max_col_row[0]
|
|
33
|
+
line = self.symbol * maxcol
|
|
34
|
+
if len(max_col_row) == 1:
|
|
35
|
+
return urwid.TextCanvas((line,), maxcol=maxcol)
|
|
36
|
+
return urwid.TextCanvas((line,) * max_col_row[1], maxcol=maxcol)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ImplementWidget:
|
|
40
|
+
__slots__ = ("name", "symbol")
|
|
41
|
+
|
|
42
|
+
def sizing(self) -> frozenset[urwid.Sizing]:
|
|
43
|
+
return frozenset((urwid.BOX, urwid.FLOW))
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def base_widget(self) -> urwid.AbstractWidget:
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
def __init__(self, name: str, symbol: bytes) -> None:
|
|
50
|
+
self.name = name
|
|
51
|
+
self.symbol = symbol
|
|
52
|
+
|
|
53
|
+
def __repr__(self) -> str:
|
|
54
|
+
return f"{self.__class__.__name__}(name={self.name!r}, symbol={self.symbol!r})"
|
|
55
|
+
|
|
56
|
+
def selectable(self) -> bool:
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def focus(self) -> None:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
def _invalidate(self) -> None:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
def rows(self, max_col_row: tuple[int], focus: bool = False) -> int:
|
|
67
|
+
return 1
|
|
68
|
+
|
|
69
|
+
def pack(self, size: typing.Any, focus: bool = False) -> tuple[int, int]:
|
|
70
|
+
return self.rows(size, focus)
|
|
71
|
+
|
|
72
|
+
def render(self, max_col_row: tuple[int, int] | tuple[int], focus: bool = False) -> urwid.Canvas:
|
|
73
|
+
maxcol = max_col_row[0]
|
|
74
|
+
line = self.symbol * maxcol
|
|
75
|
+
if len(max_col_row) == 1:
|
|
76
|
+
return urwid.TextCanvas((line,), maxcol=maxcol)
|
|
77
|
+
return urwid.TextCanvas((line,) * max_col_row[1], maxcol=maxcol)
|
|
78
|
+
|
|
79
|
+
def keypress(self, size: tuple[int, int] | tuple[int], key: str) -> str | None:
|
|
80
|
+
return key
|
|
81
|
+
|
|
82
|
+
def mouse_event(
|
|
83
|
+
self,
|
|
84
|
+
size: tuple[int, int] | tuple[int],
|
|
85
|
+
event: str,
|
|
86
|
+
button: int,
|
|
87
|
+
col: int,
|
|
88
|
+
row: int,
|
|
89
|
+
focus: bool,
|
|
90
|
+
) -> bool | None:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
|
|
9
94
|
class PileTest(unittest.TestCase):
|
|
10
95
|
def test_basic_sizing(self) -> None:
|
|
11
96
|
box_only = urwid.SolidFill("#")
|
|
@@ -247,34 +332,6 @@ class PileTest(unittest.TestCase):
|
|
|
247
332
|
self.assertEqual("Cancel", widget.focus.focus.label)
|
|
248
333
|
|
|
249
334
|
def test_not_a_widget(self):
|
|
250
|
-
class NotAWidget:
|
|
251
|
-
__slots__ = ("name", "symbol")
|
|
252
|
-
|
|
253
|
-
def __init__(self, name: str, symbol: bytes) -> None:
|
|
254
|
-
self.name = name
|
|
255
|
-
self.symbol = symbol
|
|
256
|
-
|
|
257
|
-
def __repr__(self) -> str:
|
|
258
|
-
return f"{self.__class__.__name__}(name={self.name!r}, symbol={self.symbol!r})"
|
|
259
|
-
|
|
260
|
-
def selectable(self) -> bool:
|
|
261
|
-
return False
|
|
262
|
-
|
|
263
|
-
def pack(self, max_col_row: tuple[int, int] | tuple[int], focus: bool = False) -> int:
|
|
264
|
-
if len(max_col_row) == 2:
|
|
265
|
-
return max_col_row
|
|
266
|
-
return max_col_row[0], self.rows(max_col_row)
|
|
267
|
-
|
|
268
|
-
def rows(self, max_col_row: tuple[int], focus=False) -> int:
|
|
269
|
-
return 1
|
|
270
|
-
|
|
271
|
-
def render(self, max_col_row: tuple[int, int] | tuple[int], focus: bool = False) -> urwid.Canvas:
|
|
272
|
-
maxcol = max_col_row[0]
|
|
273
|
-
line = self.symbol * maxcol
|
|
274
|
-
if len(max_col_row) == 1:
|
|
275
|
-
return urwid.TextCanvas((line,), maxcol=maxcol)
|
|
276
|
-
return urwid.TextCanvas((line,) * max_col_row[1], maxcol=maxcol)
|
|
277
|
-
|
|
278
335
|
with self.subTest("Box"), self.assertWarns(urwid.widget.PileWarning) as ctx:
|
|
279
336
|
items = (NotAWidget("First", b"*"), NotAWidget("Second", b"^"))
|
|
280
337
|
widget = urwid.Pile(items)
|
|
@@ -291,6 +348,35 @@ class PileTest(unittest.TestCase):
|
|
|
291
348
|
self.assertEqual(f"{items[0]!r} is not a Widget", str(ctx.warnings[0].message))
|
|
292
349
|
self.assertEqual(f"{items[1]!r} is not a Widget", str(ctx.warnings[1].message))
|
|
293
350
|
|
|
351
|
+
def test_implement_widget_interface(self):
|
|
352
|
+
with (
|
|
353
|
+
warnings.catch_warnings(record=True) as collected_w,
|
|
354
|
+
self.subTest("Box"),
|
|
355
|
+
):
|
|
356
|
+
items = (ImplementWidget("First", b"*"), ImplementWidget("Second", b"^"))
|
|
357
|
+
widget = urwid.Pile(items)
|
|
358
|
+
|
|
359
|
+
self.assertEqual(("****", "^^^^"), widget.render((4, 2)).decoded_text)
|
|
360
|
+
|
|
361
|
+
pile_warnings = tuple(
|
|
362
|
+
warning.message for warning in collected_w if warning.category == urwid.widget.PileWarning
|
|
363
|
+
)
|
|
364
|
+
self.assertTrue(len(pile_warnings) == 0, "No warnings expected")
|
|
365
|
+
|
|
366
|
+
with (
|
|
367
|
+
warnings.catch_warnings(record=True) as collected_w,
|
|
368
|
+
self.subTest("Flow"),
|
|
369
|
+
):
|
|
370
|
+
items = (ImplementWidget("First", b"*"), ImplementWidget("Second", b"^"))
|
|
371
|
+
widget = urwid.Pile(items)
|
|
372
|
+
|
|
373
|
+
self.assertEqual(("******", "^^^^^^"), widget.render((6,)).decoded_text)
|
|
374
|
+
|
|
375
|
+
pile_warnings = tuple(
|
|
376
|
+
warning.message for warning in collected_w if warning.category == urwid.widget.PileWarning
|
|
377
|
+
)
|
|
378
|
+
self.assertTrue(len(pile_warnings) == 0, "No warnings expected")
|
|
379
|
+
|
|
294
380
|
def ktest(self, desc, contents, focus_item, key, rkey, rfocus, rpref_col):
|
|
295
381
|
p = urwid.Pile(contents, focus_item)
|
|
296
382
|
rval = p.keypress((20,), key)
|
|
@@ -142,6 +142,72 @@ class TestScrollBarScrollable(unittest.TestCase):
|
|
|
142
142
|
|
|
143
143
|
self.assertEqual(top_position_rendered, widget.render(reduced_size).decoded_text)
|
|
144
144
|
|
|
145
|
+
def test_mouse_left_click_scrollbar(self):
|
|
146
|
+
"""Left click on the rendered scrollbar jumps to the clicked position."""
|
|
147
|
+
content = urwid.Text("\n".join(string.ascii_letters)) # 52 single-char lines
|
|
148
|
+
reduced_size = (3, 5)
|
|
149
|
+
widget = urwid.ScrollBar(urwid.Scrollable(content))
|
|
150
|
+
scrollable = widget.original_widget
|
|
151
|
+
scrollbar_col = reduced_size[0] - 1 # right side scrollbar
|
|
152
|
+
|
|
153
|
+
self.assertEqual(0, scrollable.get_scrollpos())
|
|
154
|
+
|
|
155
|
+
# posmax == 52 - 5 == 47, thumb_height == 1, thumb travel == 5 - 1 == 4.
|
|
156
|
+
# Clicking the bottom row moves past the end and is clamped to posmax.
|
|
157
|
+
self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 1, scrollbar_col, 4, False))
|
|
158
|
+
self.assertEqual(47, scrollable.get_scrollpos())
|
|
159
|
+
|
|
160
|
+
# Clicking the top row scrolls back to the top.
|
|
161
|
+
self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 1, scrollbar_col, 0, False))
|
|
162
|
+
self.assertEqual(0, scrollable.get_scrollpos())
|
|
163
|
+
|
|
164
|
+
# Intermediate rows map proportionally: round(row * posmax / travel).
|
|
165
|
+
self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 1, scrollbar_col, 1, False))
|
|
166
|
+
self.assertEqual(12, scrollable.get_scrollpos())
|
|
167
|
+
|
|
168
|
+
self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 1, scrollbar_col, 3, False))
|
|
169
|
+
self.assertEqual(35, scrollable.get_scrollpos())
|
|
170
|
+
|
|
171
|
+
def test_mouse_left_click_content_ignored(self):
|
|
172
|
+
"""Left click outside of the scrollbar columns does not scroll."""
|
|
173
|
+
content = urwid.Text("\n".join(string.ascii_letters))
|
|
174
|
+
reduced_size = (3, 5)
|
|
175
|
+
widget = urwid.ScrollBar(urwid.Scrollable(content))
|
|
176
|
+
scrollable = widget.original_widget
|
|
177
|
+
|
|
178
|
+
self.assertEqual(0, scrollable.get_scrollpos())
|
|
179
|
+
|
|
180
|
+
# Column 0 belongs to the wrapped content, not the scrollbar.
|
|
181
|
+
self.assertFalse(widget.mouse_event(reduced_size, "mouse press", 1, 0, 4, False))
|
|
182
|
+
self.assertEqual(0, scrollable.get_scrollpos())
|
|
183
|
+
|
|
184
|
+
def test_mouse_left_click_left_side_scrollbar(self):
|
|
185
|
+
"""Left click detection honours a left-aligned scrollbar."""
|
|
186
|
+
content = urwid.Text("\n".join(string.ascii_letters))
|
|
187
|
+
reduced_size = (3, 5)
|
|
188
|
+
widget = urwid.ScrollBar(urwid.Scrollable(content), side="left")
|
|
189
|
+
scrollable = widget.original_widget
|
|
190
|
+
|
|
191
|
+
self.assertEqual(0, scrollable.get_scrollpos())
|
|
192
|
+
|
|
193
|
+
# The scrollbar occupies column 0 when aligned to the left.
|
|
194
|
+
self.assertTrue(widget.mouse_event(reduced_size, "mouse press", 1, 0, 4, False))
|
|
195
|
+
self.assertEqual(47, scrollable.get_scrollpos())
|
|
196
|
+
|
|
197
|
+
# A click on the content columns is ignored.
|
|
198
|
+
self.assertFalse(widget.mouse_event(reduced_size, "mouse press", 1, 1, 0, False))
|
|
199
|
+
self.assertEqual(47, scrollable.get_scrollpos())
|
|
200
|
+
|
|
201
|
+
def test_mouse_left_click_without_scrollbar(self):
|
|
202
|
+
"""Without a rendered scrollbar a left click is not handled."""
|
|
203
|
+
content = urwid.Text("a\nb") # fits into the available height
|
|
204
|
+
reduced_size = (3, 5)
|
|
205
|
+
widget = urwid.ScrollBar(urwid.Scrollable(content))
|
|
206
|
+
scrollable = widget.original_widget
|
|
207
|
+
|
|
208
|
+
self.assertFalse(widget.mouse_event(reduced_size, "mouse press", 1, 2, 2, False))
|
|
209
|
+
self.assertEqual(0, scrollable.get_scrollpos())
|
|
210
|
+
|
|
145
211
|
def test_alt_symbols(self):
|
|
146
212
|
long_content = urwid.Text(LGPL_HEADER)
|
|
147
213
|
reduced_size = (40, 5)
|
|
@@ -25,6 +25,30 @@ class SiglnalsTest(unittest.TestCase):
|
|
|
25
25
|
edit.set_edit_text("another text")
|
|
26
26
|
handler.assert_not_called()
|
|
27
27
|
|
|
28
|
+
def test_handler_mutation_during_emit(self):
|
|
29
|
+
emitter = self.EmClass()
|
|
30
|
+
calls = []
|
|
31
|
+
|
|
32
|
+
def third():
|
|
33
|
+
calls.append("third")
|
|
34
|
+
|
|
35
|
+
def first():
|
|
36
|
+
calls.append("first")
|
|
37
|
+
disconnect_signal(emitter, "test", first)
|
|
38
|
+
connect_signal(emitter, "test", third)
|
|
39
|
+
|
|
40
|
+
def second():
|
|
41
|
+
calls.append("second")
|
|
42
|
+
|
|
43
|
+
connect_signal(emitter, "test", first)
|
|
44
|
+
connect_signal(emitter, "test", second)
|
|
45
|
+
|
|
46
|
+
emit_signal(emitter, "test")
|
|
47
|
+
self.assertEqual(calls, ["first", "second"])
|
|
48
|
+
|
|
49
|
+
emit_signal(emitter, "test")
|
|
50
|
+
self.assertEqual(calls, ["first", "second", "second", "third"])
|
|
51
|
+
|
|
28
52
|
@unittest.skipIf(sys.implementation.name == "pypy", "WeakRef works differently on PyPy")
|
|
29
53
|
def test_weak_del(self):
|
|
30
54
|
emitter = SiglnalsTest.EmClass()
|
|
@@ -39,6 +39,58 @@ class TextTest(unittest.TestCase):
|
|
|
39
39
|
got = urwid.Text("û").render((3,))._text
|
|
40
40
|
assert got == expected, f"got: {got!r} expected: {expected!r}"
|
|
41
41
|
|
|
42
|
+
def test_empty_markup_segment_middle(self) -> None:
|
|
43
|
+
"""An empty markup segment in the middle must not truncate the rendered row."""
|
|
44
|
+
widget = urwid.Text([("a", "X" * 10), ("b", ""), ("c", " END")])
|
|
45
|
+
|
|
46
|
+
self.assertEqual(("XXXXXXXXXX END", [("a", 10), ("b", 0), ("c", 4)]), widget.get_text())
|
|
47
|
+
|
|
48
|
+
canvas = widget.render(())
|
|
49
|
+
self.assertEqual([b"XXXXXXXXXX END"], canvas.text)
|
|
50
|
+
self.assertEqual(("XXXXXXXXXX END",), canvas.decoded_text)
|
|
51
|
+
|
|
52
|
+
def test_empty_markup_segment_start(self) -> None:
|
|
53
|
+
"""An empty markup segment at the start must keep the whole rendered row."""
|
|
54
|
+
widget = urwid.Text([("b", ""), ("a", "X" * 10), ("c", " END")])
|
|
55
|
+
|
|
56
|
+
self.assertEqual(("XXXXXXXXXX END", [("b", 0), ("a", 10), ("c", 4)]), widget.get_text())
|
|
57
|
+
|
|
58
|
+
canvas = widget.render(())
|
|
59
|
+
self.assertEqual([b"XXXXXXXXXX END"], canvas.text)
|
|
60
|
+
self.assertEqual(("XXXXXXXXXX END",), canvas.decoded_text)
|
|
61
|
+
|
|
62
|
+
def test_empty_markup_segment_end(self) -> None:
|
|
63
|
+
"""An empty markup segment at the end must keep the whole rendered row."""
|
|
64
|
+
widget = urwid.Text([("a", "X" * 10), ("c", " END"), ("b", "")])
|
|
65
|
+
|
|
66
|
+
self.assertEqual(("XXXXXXXXXX END", [("a", 10), ("c", 4), ("b", 0)]), widget.get_text())
|
|
67
|
+
|
|
68
|
+
canvas = widget.render(())
|
|
69
|
+
self.assertEqual([b"XXXXXXXXXX END"], canvas.text)
|
|
70
|
+
self.assertEqual(("XXXXXXXXXX END",), canvas.decoded_text)
|
|
71
|
+
|
|
72
|
+
def test_multiple_empty_markup_segments(self) -> None:
|
|
73
|
+
"""Several consecutive empty markup segments must not drop following content."""
|
|
74
|
+
widget = urwid.Text([("a", "AB"), ("b", ""), ("c", ""), ("d", "CD")])
|
|
75
|
+
|
|
76
|
+
self.assertEqual(("ABCD", [("a", 2), ("b", 0), ("c", 0), ("d", 2)]), widget.get_text())
|
|
77
|
+
|
|
78
|
+
canvas = widget.render(())
|
|
79
|
+
self.assertEqual([b"ABCD"], canvas.text)
|
|
80
|
+
self.assertEqual(("ABCD",), canvas.decoded_text)
|
|
81
|
+
|
|
82
|
+
def test_empty_markup_segment_render_content(self) -> None:
|
|
83
|
+
"""The canvas content rows must expose the display attribute of each segment."""
|
|
84
|
+
widget = urwid.Text([("a", "X" * 10), ("b", ""), ("c", " END")])
|
|
85
|
+
|
|
86
|
+
content = list(widget.render(()).content())
|
|
87
|
+
attrs = [attr for row in content for attr, _cs, _text in row]
|
|
88
|
+
rendered = b"".join(text for row in content for _attr, _cs, text in row)
|
|
89
|
+
|
|
90
|
+
self.assertEqual(b"XXXXXXXXXX END", rendered)
|
|
91
|
+
self.assertIn("a", attrs)
|
|
92
|
+
self.assertIn("c", attrs)
|
|
93
|
+
|
|
42
94
|
|
|
43
95
|
class EditTest(unittest.TestCase):
|
|
44
96
|
def setUp(self):
|
|
@@ -140,6 +140,7 @@ from .widget import (
|
|
|
140
140
|
SPACE,
|
|
141
141
|
TOP,
|
|
142
142
|
WEIGHT,
|
|
143
|
+
AbstractWidget,
|
|
143
144
|
Align,
|
|
144
145
|
AttrMap,
|
|
145
146
|
AttrMapError,
|
|
@@ -262,6 +263,7 @@ __all__: tuple[str, ...] = (
|
|
|
262
263
|
"WEIGHT",
|
|
263
264
|
"WHITE",
|
|
264
265
|
"YELLOW",
|
|
266
|
+
"AbstractWidget",
|
|
265
267
|
"Align",
|
|
266
268
|
"AsyncioEventLoop",
|
|
267
269
|
"AttrMap",
|