Kivy-examples 2.3.1.dev0__py3-none-any.whl

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.
Files changed (624) hide show
  1. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/3Drendering/main.py +77 -0
  2. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/3Drendering/monkey.obj +7966 -0
  3. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/3Drendering/objloader.py +147 -0
  4. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/3Drendering/simple.glsl +47 -0
  5. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/RST_Editor/editor.kv +72 -0
  6. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/RST_Editor/main.py +64 -0
  7. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/android/compass/android.txt +3 -0
  8. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/android/compass/compass.kv +25 -0
  9. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/android/compass/main.py +77 -0
  10. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/android/compass/needle.png +0 -0
  11. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/android/compass/rose.png +0 -0
  12. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/android/takepicture/android.txt +3 -0
  13. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/android/takepicture/main.py +80 -0
  14. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/android/takepicture/shadow32.png +0 -0
  15. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/android/takepicture/takepicture.kv +40 -0
  16. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/animation/animate.py +42 -0
  17. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/application/app_suite.py +152 -0
  18. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/application/app_suite_data/testkvdir.kv +4 -0
  19. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/application/app_with_build.py +24 -0
  20. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/application/app_with_config.py +49 -0
  21. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/application/app_with_kv.py +25 -0
  22. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/application/app_with_kv_in_template1.py +27 -0
  23. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/application/template1/test.kv +4 -0
  24. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/application/test.kv +4 -0
  25. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/application/testkvfile.kv +4 -0
  26. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/async/asyncio_advanced.py +90 -0
  27. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/async/asyncio_basic.py +59 -0
  28. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/async/trio_advanced.py +93 -0
  29. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/async/trio_basic.py +58 -0
  30. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12908_sweet_trip_mm_clap_hi.wav +0 -0
  31. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12909_sweet_trip_mm_clap_lo.wav +0 -0
  32. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12910_sweet_trip_mm_clap_mid.wav +0 -0
  33. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12911_sweet_trip_mm_hat_cl.wav +0 -0
  34. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12913_sweet_trip_mm_kick_hi.wav +0 -0
  35. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12914_sweet_trip_mm_kick_lo.wav +0 -0
  36. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12915_sweet_trip_mm_kick_mid.wav +0 -0
  37. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12916_sweet_trip_mm_kwik_mod_01.wav +0 -0
  38. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12917_sweet_trip_mm_kwik_mod_02.wav +0 -0
  39. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12918_sweet_trip_mm_kwik_mod_03.wav +0 -0
  40. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12919_sweet_trip_mm_kwik_mod_04.wav +0 -0
  41. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12920_sweet_trip_mm_kwik_mod_05.wav +0 -0
  42. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12921_sweet_trip_mm_kwik_mod_06.wav +0 -0
  43. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12922_sweet_trip_mm_kwik_mod_07.wav +0 -0
  44. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12923_sweet_trip_mm_metal_clave.wav +0 -0
  45. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12925_sweet_trip_mm_sweep_x.wav +0 -0
  46. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12926_sweet_trip_mm_sweep_y.wav +0 -0
  47. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/12927_sweet_trip_mm_sweep_z.wav +0 -0
  48. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/audio.kv +59 -0
  49. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/main.py +81 -0
  50. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/audio/pitch.py +42 -0
  51. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/camera/main.py +59 -0
  52. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/bezier.py +105 -0
  53. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/canvas_stress.py +72 -0
  54. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/circle.py +84 -0
  55. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/fbo_canvas.py +91 -0
  56. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/kiwi.jpg +0 -0
  57. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/lines.py +296 -0
  58. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/lines_extended.py +183 -0
  59. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/mesh.py +57 -0
  60. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/mesh_manipulation.py +101 -0
  61. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/mtexture1.png +0 -0
  62. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/mtexture2.png +0 -0
  63. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/multitexture.py +110 -0
  64. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/repeat_texture.py +65 -0
  65. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/rotation.py +35 -0
  66. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/rounded_rectangle.py +153 -0
  67. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/scale.py +37 -0
  68. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/stencil_canvas.py +85 -0
  69. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/tesselate.py +146 -0
  70. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/texture.py +143 -0
  71. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/canvas/texture_example_image.png +0 -0
  72. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/container/kv/1.kv +28 -0
  73. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/container/kv/2.kv +28 -0
  74. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/container/kv/3.kv +28 -0
  75. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/container/kv/root.kv +32 -0
  76. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/container/main.py +63 -0
  77. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/cover/cover_image.py +25 -0
  78. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/cover/cover_video.py +31 -0
  79. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/camera_puzzle.py +98 -0
  80. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/AnchorLayoutContainer.kv +15 -0
  81. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/BoxLayoutContainer.kv +14 -0
  82. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/ButtonContainer.kv +27 -0
  83. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/CheckBoxContainer.kv +28 -0
  84. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/FileChooserContainer.kv +19 -0
  85. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/FloatLayoutContainer.kv +16 -0
  86. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/GridLayoutContainer.kv +20 -0
  87. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/LabelContainer.kv +62 -0
  88. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/MediaContainer.kv +9 -0
  89. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/PlaygroundContainer.kv +4 -0
  90. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/PopupContainer.kv +39 -0
  91. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/ProgressBarContainer.kv +17 -0
  92. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/RestContainer.kv +5 -0
  93. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/ScatterContainer.kv +16 -0
  94. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/SelectorsContainer.kv +10 -0
  95. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/StackLayoutContainer.kv +25 -0
  96. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/container_kvs/TextContainer.kv +20 -0
  97. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/kivycatalog.kv +144 -0
  98. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/kivycatalog/main.py +192 -0
  99. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/multistroke/gesturedatabase.kv +117 -0
  100. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/multistroke/gesturedatabase.py +180 -0
  101. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/multistroke/helpers.py +30 -0
  102. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/multistroke/historymanager.kv +164 -0
  103. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/multistroke/historymanager.py +276 -0
  104. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/multistroke/main.py +144 -0
  105. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/multistroke/multistroke.kv +101 -0
  106. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/multistroke/settings.kv +110 -0
  107. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/multistroke/settings.py +97 -0
  108. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/pictures/android.txt +3 -0
  109. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/pictures/images/Bubbles.jpg +0 -0
  110. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/pictures/images/Ill1.jpg +0 -0
  111. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/pictures/images/Wall.jpg +0 -0
  112. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/pictures/images/faust_github.jpg +0 -0
  113. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/pictures/main.py +73 -0
  114. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/pictures/pictures.kv +57 -0
  115. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/pictures/shadow32.png +0 -0
  116. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/shadereditor/main.py +147 -0
  117. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/shadereditor/shadereditor.kv +39 -0
  118. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/README.txt +11 -0
  119. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/android.txt +3 -0
  120. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/background.png +0 -0
  121. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/faust_github.jpg +0 -0
  122. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/icons/README +4 -0
  123. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/icons/bug.png +0 -0
  124. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/icons/chevron-left.png +0 -0
  125. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/icons/chevron-right.png +0 -0
  126. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/accordions.kv +39 -0
  127. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/bubbles.kv +33 -0
  128. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/buttons.kv +26 -0
  129. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/carousel.kv +51 -0
  130. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/checkboxes.kv +33 -0
  131. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/codeinput.kv +9 -0
  132. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/dropdown.kv +40 -0
  133. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/filechoosers.kv +24 -0
  134. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/popups.kv +17 -0
  135. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/progressbar.kv +15 -0
  136. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/rstdocument.kv +24 -0
  137. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/scatter.kv +14 -0
  138. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/screenmanager.kv +46 -0
  139. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/sliders.kv +55 -0
  140. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/spinner.kv +11 -0
  141. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/splitter.kv +13 -0
  142. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/switches.kv +39 -0
  143. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/tabbedpanel + layouts.kv +69 -0
  144. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/textinputs.kv +48 -0
  145. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/data/screens/togglebutton.kv +39 -0
  146. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/main.py +243 -0
  147. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/showcase/showcase.kv +100 -0
  148. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/touchtracer/README.txt +12 -0
  149. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/touchtracer/android.txt +3 -0
  150. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/touchtracer/icon.png +0 -0
  151. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/touchtracer/main.py +170 -0
  152. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/touchtracer/particle.png +0 -0
  153. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/demo/touchtracer/touchtracer.kv +29 -0
  154. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/frameworks/twisted/echo_client_app.py +82 -0
  155. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/frameworks/twisted/echo_server_app.py +49 -0
  156. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/frameworks/twisted/twistd_app.py +66 -0
  157. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/gestures/gesture_board.py +95 -0
  158. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/gestures/my_gestures.py +90 -0
  159. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/guide/designwithkv/controller.kv +16 -0
  160. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/guide/designwithkv/main.py +29 -0
  161. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/guide/firstwidget/1_skeleton.py +15 -0
  162. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/guide/firstwidget/2_print_touch.py +16 -0
  163. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/guide/firstwidget/3_draw_ellipse.py +22 -0
  164. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/guide/firstwidget/4_draw_line.py +26 -0
  165. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/guide/firstwidget/5_random_colors.py +28 -0
  166. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/guide/firstwidget/6_button.py +38 -0
  167. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/guide/quickstart/main.py +15 -0
  168. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/includes/button.kv +9 -0
  169. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/includes/layout.kv +6 -0
  170. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/includes/main.py +19 -0
  171. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/includes/test.kv +5 -0
  172. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/keyboard/android.txt +3 -0
  173. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/keyboard/main.py +226 -0
  174. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/keyboard/numeric.json +38 -0
  175. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kinect/README.txt +13 -0
  176. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kinect/kinectviewer.py +283 -0
  177. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/app_button.kv +9 -0
  178. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/app_camera.kv +25 -0
  179. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/app_fbo.kv +11 -0
  180. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/app_layout.kv +23 -0
  181. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/app_logo.kv +36 -0
  182. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/app_scatter.kv +6 -0
  183. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/app_stencil.kv +27 -0
  184. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/app_video.kv +42 -0
  185. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/builder_template.py +28 -0
  186. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/ids/id_in_kv/id_in_kv.py +20 -0
  187. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/ids/id_in_kv/test.kv +11 -0
  188. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/ids/kv_and_py/kv_and_py.py +34 -0
  189. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/ids/kv_and_py/test.kv +11 -0
  190. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/kivy.jpg +0 -0
  191. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/kv/kvrun.py +38 -0
  192. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/miscellaneous/clipboard.py +63 -0
  193. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/miscellaneous/custom_titlebar.py +56 -0
  194. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/miscellaneous/imagesave.py +14 -0
  195. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/miscellaneous/joystick.py +83 -0
  196. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/miscellaneous/multiple_dropfile.py +51 -0
  197. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/miscellaneous/on_textedit_event.py +104 -0
  198. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/miscellaneous/opacitywindow.py +28 -0
  199. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/miscellaneous/shapecollisions.py +397 -0
  200. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/miscellaneous/shapedwindow.py +98 -0
  201. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/miscellaneous/two_panes.py +47 -0
  202. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/miscellaneous/urlrequest.py +128 -0
  203. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/settings/android.txt +3 -0
  204. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/settings/main.py +122 -0
  205. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/shader/plasma.kv +9 -0
  206. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/shader/plasma.py +86 -0
  207. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/shader/rotated.kv +91 -0
  208. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/shader/rotated.py +102 -0
  209. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/shader/shadertree.kv +29 -0
  210. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/shader/shadertree.py +193 -0
  211. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/shader/tex3.jpg +0 -0
  212. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/svg/benchmark.py +19 -0
  213. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/svg/cloud.svg +25 -0
  214. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/svg/main-smaa.py +155 -0
  215. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/svg/main.py +50 -0
  216. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/svg/music.svg +68 -0
  217. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/svg/rotated.svg +23 -0
  218. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/svg/ship.svg +181 -0
  219. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/svg/sun.svg +35 -0
  220. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/svg/tiger.svg +813 -0
  221. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/text/pango_demo.py +296 -0
  222. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/notes/final/data/icon.png +0 -0
  223. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/notes/final/main.py +163 -0
  224. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/notes/final/note.kv +154 -0
  225. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/pong/main.py +82 -0
  226. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/pong/pong.kv +53 -0
  227. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/pong/steps/step1/main.py +15 -0
  228. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/pong/steps/step1/pong.kv +2 -0
  229. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/pong/steps/step2/main.py +15 -0
  230. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/pong/steps/step2/pong.kv +21 -0
  231. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/pong/steps/step3/main.py +26 -0
  232. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/pong/steps/step3/pong.kv +31 -0
  233. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/pong/steps/step4/main.py +48 -0
  234. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/pong/steps/step4/pong.kv +34 -0
  235. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/pong/steps/step5/main.py +75 -0
  236. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/tutorials/pong/steps/step5/pong.kv +53 -0
  237. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/accordion_1.py +17 -0
  238. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/actionbar.py +32 -0
  239. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/asyncimage.py +35 -0
  240. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/boxlayout_poshint.py +54 -0
  241. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/bubble_test.py +63 -0
  242. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/camera.py +33 -0
  243. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/carousel_buttons.py +50 -0
  244. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/cityCC0.mpg +0 -0
  245. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/cityCC0.png +0 -0
  246. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/codeinput.py +225 -0
  247. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/codeinputtest.kv +53 -0
  248. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/colorpicker.py +234 -0
  249. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/colorusage.py +62 -0
  250. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/compound_selection.py +83 -0
  251. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/customcollide.py +82 -0
  252. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/effectwidget.py +193 -0
  253. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/effectwidget2.py +47 -0
  254. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/effectwidget3_advanced.py +62 -0
  255. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/fbowidget.py +111 -0
  256. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/focus_behavior.py +98 -0
  257. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/image_mipmap.py +31 -0
  258. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/keyboardlistener.py +42 -0
  259. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/label_mipmap.py +28 -0
  260. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/label_sizing.py +193 -0
  261. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/label_text_size.py +49 -0
  262. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/label_with_markup.py +21 -0
  263. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/lang_dynamic_classes.py +18 -0
  264. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/pagelayout.py +64 -0
  265. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/popup_with_kv.py +35 -0
  266. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/recycleview/basic_data.py +124 -0
  267. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/recycleview/infinite_scrolling.py +143 -0
  268. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/recycleview/key_viewclass.py +149 -0
  269. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/recycleview/messenger.py +196 -0
  270. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/recycleview/pull_to_refresh.py +95 -0
  271. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/recycleview/rv_animate_items.py +109 -0
  272. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/rstexample.py +128 -0
  273. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/scatter.kv +53 -0
  274. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/scatter.py +16 -0
  275. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/screenmanager.py +106 -0
  276. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/scrollview.kv +16 -0
  277. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/scrollview.py +40 -0
  278. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/sequenced_images/android.txt +3 -0
  279. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/sequenced_images/data/images/bird.zip +0 -0
  280. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/sequenced_images/data/images/button_white.png +0 -0
  281. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/sequenced_images/data/images/button_white_animated.zip +0 -0
  282. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/sequenced_images/data/images/cube.zip +0 -0
  283. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/sequenced_images/data/images/info.png +0 -0
  284. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/sequenced_images/data/images/info.zip +0 -0
  285. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/sequenced_images/data/images/simple_cv_joint_animated.gif +0 -0
  286. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/sequenced_images/main.kv +63 -0
  287. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/sequenced_images/main.py +150 -0
  288. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/sequenced_images/uix/__init__.py +7 -0
  289. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/sequenced_images/uix/custom_button.py +98 -0
  290. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/settings.py +93 -0
  291. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/shorten_text.py +80 -0
  292. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/spinner.py +17 -0
  293. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/splitter.py +74 -0
  294. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/tabbed_panel_showcase.py +316 -0
  295. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/tabbedpanel.py +50 -0
  296. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/textalign.kv +49 -0
  297. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/textalign.py +48 -0
  298. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/textinput.py +59 -0
  299. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/unicode_textinput.py +244 -0
  300. Kivy_examples-2.3.1.dev0.data/data/share/kivy-examples/widgets/videoplayer.py +26 -0
  301. Kivy_examples-2.3.1.dev0.dist-info/AUTHORS +66 -0
  302. Kivy_examples-2.3.1.dev0.dist-info/LICENSE +21 -0
  303. Kivy_examples-2.3.1.dev0.dist-info/METADATA +382 -0
  304. Kivy_examples-2.3.1.dev0.dist-info/RECORD +624 -0
  305. Kivy_examples-2.3.1.dev0.dist-info/WHEEL +5 -0
  306. Kivy_examples-2.3.1.dev0.dist-info/dependency_links.txt +1 -0
  307. Kivy_examples-2.3.1.dev0.dist-info/top_level.txt +1 -0
  308. kivy/__init__.py +519 -0
  309. kivy/_version.py +18 -0
  310. kivy/animation.py +831 -0
  311. kivy/app.py +1213 -0
  312. kivy/atlas.py +456 -0
  313. kivy/base.py +617 -0
  314. kivy/cache.py +262 -0
  315. kivy/clock.py +1172 -0
  316. kivy/compat.py +82 -0
  317. kivy/config.py +1004 -0
  318. kivy/context.py +102 -0
  319. kivy/core/__init__.py +247 -0
  320. kivy/core/audio/__init__.py +223 -0
  321. kivy/core/audio/audio_android.py +104 -0
  322. kivy/core/audio/audio_avplayer.py +82 -0
  323. kivy/core/audio/audio_ffpyplayer.py +185 -0
  324. kivy/core/audio/audio_gstplayer.py +101 -0
  325. kivy/core/audio/audio_pygame.py +127 -0
  326. kivy/core/camera/__init__.py +151 -0
  327. kivy/core/camera/camera_android.py +206 -0
  328. kivy/core/camera/camera_gi.py +170 -0
  329. kivy/core/camera/camera_opencv.py +163 -0
  330. kivy/core/camera/camera_picamera.py +96 -0
  331. kivy/core/clipboard/__init__.py +157 -0
  332. kivy/core/clipboard/_clipboard_ext.py +36 -0
  333. kivy/core/clipboard/clipboard_android.py +91 -0
  334. kivy/core/clipboard/clipboard_dbusklipper.py +41 -0
  335. kivy/core/clipboard/clipboard_dummy.py +26 -0
  336. kivy/core/clipboard/clipboard_gtk3.py +47 -0
  337. kivy/core/clipboard/clipboard_nspaste.py +44 -0
  338. kivy/core/clipboard/clipboard_pygame.py +67 -0
  339. kivy/core/clipboard/clipboard_sdl2.py +36 -0
  340. kivy/core/clipboard/clipboard_winctypes.py +107 -0
  341. kivy/core/clipboard/clipboard_xclip.py +29 -0
  342. kivy/core/clipboard/clipboard_xsel.py +26 -0
  343. kivy/core/gl/__init__.py +90 -0
  344. kivy/core/image/__init__.py +996 -0
  345. kivy/core/image/img_dds.py +40 -0
  346. kivy/core/image/img_ffpyplayer.py +87 -0
  347. kivy/core/image/img_pil.py +123 -0
  348. kivy/core/image/img_pygame.py +119 -0
  349. kivy/core/image/img_sdl2.py +66 -0
  350. kivy/core/image/img_tex.py +58 -0
  351. kivy/core/spelling/__init__.py +135 -0
  352. kivy/core/spelling/spelling_enchant.py +50 -0
  353. kivy/core/spelling/spelling_osxappkit.py +64 -0
  354. kivy/core/text/__init__.py +1090 -0
  355. kivy/core/text/markup.py +893 -0
  356. kivy/core/text/text_pango.py +145 -0
  357. kivy/core/text/text_pil.py +84 -0
  358. kivy/core/text/text_pygame.py +117 -0
  359. kivy/core/text/text_sdl2.py +50 -0
  360. kivy/core/video/__init__.py +219 -0
  361. kivy/core/video/video_ffmpeg.py +106 -0
  362. kivy/core/video/video_ffpyplayer.py +449 -0
  363. kivy/core/video/video_gstplayer.py +140 -0
  364. kivy/core/video/video_null.py +12 -0
  365. kivy/core/window/__init__.py +2515 -0
  366. kivy/core/window/window_egl_rpi.py +86 -0
  367. kivy/core/window/window_pygame.py +441 -0
  368. kivy/core/window/window_sdl2.py +999 -0
  369. kivy/deps/__init__.py +6 -0
  370. kivy/effects/__init__.py +23 -0
  371. kivy/effects/dampedscroll.py +114 -0
  372. kivy/effects/kinetic.py +193 -0
  373. kivy/effects/opacityscroll.py +27 -0
  374. kivy/effects/scroll.py +126 -0
  375. kivy/event.py +12 -0
  376. kivy/eventmanager/__init__.py +185 -0
  377. kivy/extras/__init__.py +0 -0
  378. kivy/extras/highlight.py +52 -0
  379. kivy/factory.py +190 -0
  380. kivy/factory_registers.py +201 -0
  381. kivy/garden/__init__.py +186 -0
  382. kivy/geometry.py +121 -0
  383. kivy/gesture.py +424 -0
  384. kivy/graphics/__init__.py +126 -0
  385. kivy/graphics/cgl_backend/__init__.py +0 -0
  386. kivy/input/__init__.py +39 -0
  387. kivy/input/factory.py +35 -0
  388. kivy/input/motionevent.py +587 -0
  389. kivy/input/postproc/__init__.py +27 -0
  390. kivy/input/postproc/calibration.py +200 -0
  391. kivy/input/postproc/dejitter.py +74 -0
  392. kivy/input/postproc/doubletap.py +101 -0
  393. kivy/input/postproc/ignorelist.py +47 -0
  394. kivy/input/postproc/retaintouch.py +93 -0
  395. kivy/input/postproc/tripletap.py +106 -0
  396. kivy/input/provider.py +40 -0
  397. kivy/input/providers/__init__.py +68 -0
  398. kivy/input/providers/androidjoystick.py +117 -0
  399. kivy/input/providers/hidinput.py +778 -0
  400. kivy/input/providers/leapfinger.py +113 -0
  401. kivy/input/providers/linuxwacom.py +396 -0
  402. kivy/input/providers/mactouch.py +220 -0
  403. kivy/input/providers/mouse.py +424 -0
  404. kivy/input/providers/mtdev.py +380 -0
  405. kivy/input/providers/probesysfs.py +254 -0
  406. kivy/input/providers/tuio.py +326 -0
  407. kivy/input/providers/wm_common.py +162 -0
  408. kivy/input/providers/wm_pen.py +121 -0
  409. kivy/input/providers/wm_touch.py +157 -0
  410. kivy/input/recorder.py +335 -0
  411. kivy/input/shape.py +27 -0
  412. kivy/interactive.py +335 -0
  413. kivy/lang/__init__.py +883 -0
  414. kivy/lang/builder.py +1004 -0
  415. kivy/lang/parser.py +822 -0
  416. kivy/lib/__init__.py +21 -0
  417. kivy/lib/ddsfile.py +419 -0
  418. kivy/lib/gstplayer/__init__.py +27 -0
  419. kivy/lib/mtdev.py +254 -0
  420. kivy/lib/vidcore_lite/__init__.py +0 -0
  421. kivy/loader.py +579 -0
  422. kivy/logger.py +646 -0
  423. kivy/metrics.py +421 -0
  424. kivy/modules/__init__.py +300 -0
  425. kivy/modules/_webdebugger.py +200 -0
  426. kivy/modules/console.py +1059 -0
  427. kivy/modules/cursor.py +75 -0
  428. kivy/modules/inspector.py +755 -0
  429. kivy/modules/joycursor.py +299 -0
  430. kivy/modules/keybinding.py +64 -0
  431. kivy/modules/monitor.py +90 -0
  432. kivy/modules/recorder.py +94 -0
  433. kivy/modules/screen.py +183 -0
  434. kivy/modules/showborder.py +33 -0
  435. kivy/modules/touchring.py +97 -0
  436. kivy/modules/webdebugger.py +30 -0
  437. kivy/multistroke.py +1477 -0
  438. kivy/network/__init__.py +7 -0
  439. kivy/network/urlrequest.py +794 -0
  440. kivy/parser.py +154 -0
  441. kivy/resources.py +102 -0
  442. kivy/storage/__init__.py +405 -0
  443. kivy/storage/dictstore.py +98 -0
  444. kivy/storage/jsonstore.py +92 -0
  445. kivy/storage/redisstore.py +103 -0
  446. kivy/support.py +267 -0
  447. kivy/tests/__init__.py +11 -0
  448. kivy/tests/async_common.py +568 -0
  449. kivy/tests/common.py +524 -0
  450. kivy/tests/conftest.py +33 -0
  451. kivy/tests/fixtures.py +169 -0
  452. kivy/tests/perf_test_textinput.py +195 -0
  453. kivy/tests/pyinstaller/simple_widget/main.py +12 -0
  454. kivy/tests/pyinstaller/simple_widget/project/__init__.py +0 -0
  455. kivy/tests/pyinstaller/simple_widget/project/widget.py +12 -0
  456. kivy/tests/pyinstaller/test_pyinstaller.py +115 -0
  457. kivy/tests/pyinstaller/video_widget/main.py +6 -0
  458. kivy/tests/pyinstaller/video_widget/project/__init__.py +38 -0
  459. kivy/tests/test_animations.py +415 -0
  460. kivy/tests/test_app.py +216 -0
  461. kivy/tests/test_audio.py +69 -0
  462. kivy/tests/test_benchmark.py +292 -0
  463. kivy/tests/test_clipboard.py +48 -0
  464. kivy/tests/test_clock.py +292 -0
  465. kivy/tests/test_compat.py +6 -0
  466. kivy/tests/test_config.py +35 -0
  467. kivy/tests/test_coverage.py +83 -0
  468. kivy/tests/test_doc_gallery.py +38 -0
  469. kivy/tests/test_environ_cli.py +50 -0
  470. kivy/tests/test_fbo_py2py3.py +74 -0
  471. kivy/tests/test_filechooser.py +11 -0
  472. kivy/tests/test_filechooser_unicode.py +82 -0
  473. kivy/tests/test_fonts.py +42 -0
  474. kivy/tests/test_garden.py +23 -0
  475. kivy/tests/test_graphics.py +1149 -0
  476. kivy/tests/test_graphics_svg.py +84 -0
  477. kivy/tests/test_image.py +74 -0
  478. kivy/tests/test_imageloader.py +485 -0
  479. kivy/tests/test_invalid_lang.py +42 -0
  480. kivy/tests/test_issues/test_6315.py +55 -0
  481. kivy/tests/test_issues/test_issue_1084.py +47 -0
  482. kivy/tests/test_issues/test_issue_1091.py +19 -0
  483. kivy/tests/test_issues/test_issue_599.py +21 -0
  484. kivy/tests/test_issues/test_issue_609.py +19 -0
  485. kivy/tests/test_issues/test_issue_6909.py +28 -0
  486. kivy/tests/test_issues/test_issue_883.py +12 -0
  487. kivy/tests/test_kivy_init.py +30 -0
  488. kivy/tests/test_knspace.py +247 -0
  489. kivy/tests/test_lang.py +431 -0
  490. kivy/tests/test_lang_complex.py +87 -0
  491. kivy/tests/test_lang_pre_process_and_post_process.py +394 -0
  492. kivy/tests/test_logger.py +516 -0
  493. kivy/tests/test_metrics.py +30 -0
  494. kivy/tests/test_module_inspector.py +382 -0
  495. kivy/tests/test_motion_event.py +44 -0
  496. kivy/tests/test_mouse_hover_event.py +249 -0
  497. kivy/tests/test_mouse_multitouchsim.py +554 -0
  498. kivy/tests/test_multistroke.py +389 -0
  499. kivy/tests/test_properties.py +1332 -0
  500. kivy/tests/test_resources.py +79 -0
  501. kivy/tests/test_rst_replace.py +63 -0
  502. kivy/tests/test_screen.py +38 -0
  503. kivy/tests/test_storage.py +135 -0
  504. kivy/tests/test_uix_actionbar.py +363 -0
  505. kivy/tests/test_uix_anchorlayout.py +79 -0
  506. kivy/tests/test_uix_asyncimage.py +133 -0
  507. kivy/tests/test_uix_boxlayout.py +94 -0
  508. kivy/tests/test_uix_bubble.py +425 -0
  509. kivy/tests/test_uix_carousel.py +93 -0
  510. kivy/tests/test_uix_colorpicker.py +77 -0
  511. kivy/tests/test_uix_dropdown.py +91 -0
  512. kivy/tests/test_uix_gridlayout.py +244 -0
  513. kivy/tests/test_uix_layout.py +16 -0
  514. kivy/tests/test_uix_modal.py +101 -0
  515. kivy/tests/test_uix_recyclegridlayout.py +383 -0
  516. kivy/tests/test_uix_relativelayout.py +30 -0
  517. kivy/tests/test_uix_scrollview.py +358 -0
  518. kivy/tests/test_uix_settings.py +33 -0
  519. kivy/tests/test_uix_slider.py +156 -0
  520. kivy/tests/test_uix_stacklayout.py +120 -0
  521. kivy/tests/test_uix_textinput.py +577 -0
  522. kivy/tests/test_uix_translate_coordinates.py +72 -0
  523. kivy/tests/test_uix_videoplayer.py +112 -0
  524. kivy/tests/test_uix_widget.py +137 -0
  525. kivy/tests/test_urlrequest/test_urlrequest_requests.py +378 -0
  526. kivy/tests/test_urlrequest/test_urlrequest_urllib.py +155 -0
  527. kivy/tests/test_utils.py +237 -0
  528. kivy/tests/test_vector.py +256 -0
  529. kivy/tests/test_video.py +25 -0
  530. kivy/tests/test_weakmethod.py +44 -0
  531. kivy/tests/test_widget.py +127 -0
  532. kivy/tests/test_widget_walk.py +70 -0
  533. kivy/tests/test_window_base.py +60 -0
  534. kivy/tests/test_window_info.py +29 -0
  535. kivy/tests/visual_test_label.py +291 -0
  536. kivy/tools/__init__.py +44 -0
  537. kivy/tools/benchmark.py +286 -0
  538. kivy/tools/changelog_parser.py +120 -0
  539. kivy/tools/coverage.py +135 -0
  540. kivy/tools/gallery.py +323 -0
  541. kivy/tools/generate-icons.py +117 -0
  542. kivy/tools/gles_compat/subset_gles.py +125 -0
  543. kivy/tools/highlight/__init__.py +9 -0
  544. kivy/tools/kviewer.py +80 -0
  545. kivy/tools/packaging/__init__.py +12 -0
  546. kivy/tools/packaging/cython_cfg.py +82 -0
  547. kivy/tools/packaging/factory.py +105 -0
  548. kivy/tools/packaging/pyinstaller_hooks/__init__.py +381 -0
  549. kivy/tools/packaging/pyinstaller_hooks/__main__.py +29 -0
  550. kivy/tools/packaging/pyinstaller_hooks/hook-kivy.py +9 -0
  551. kivy/tools/packaging/pyinstaller_hooks/pyi_rth_kivy.py +17 -0
  552. kivy/tools/pep8checker/pep8.py +2316 -0
  553. kivy/tools/pep8checker/pep8kivy.py +109 -0
  554. kivy/tools/report.py +223 -0
  555. kivy/tools/stub-gl-debug.py +201 -0
  556. kivy/tools/texturecompress.py +233 -0
  557. kivy/uix/__init__.py +56 -0
  558. kivy/uix/accordion.py +484 -0
  559. kivy/uix/actionbar.py +934 -0
  560. kivy/uix/anchorlayout.py +122 -0
  561. kivy/uix/behaviors/__init__.py +95 -0
  562. kivy/uix/behaviors/button.py +212 -0
  563. kivy/uix/behaviors/codenavigation.py +167 -0
  564. kivy/uix/behaviors/compoundselection.py +688 -0
  565. kivy/uix/behaviors/cover.py +160 -0
  566. kivy/uix/behaviors/drag.py +234 -0
  567. kivy/uix/behaviors/emacs.py +140 -0
  568. kivy/uix/behaviors/focus.py +595 -0
  569. kivy/uix/behaviors/knspace.py +590 -0
  570. kivy/uix/behaviors/togglebutton.py +156 -0
  571. kivy/uix/behaviors/touchripple.py +318 -0
  572. kivy/uix/boxlayout.py +331 -0
  573. kivy/uix/bubble.py +576 -0
  574. kivy/uix/button.py +137 -0
  575. kivy/uix/camera.py +118 -0
  576. kivy/uix/carousel.py +695 -0
  577. kivy/uix/checkbox.py +197 -0
  578. kivy/uix/codeinput.py +238 -0
  579. kivy/uix/colorpicker.py +486 -0
  580. kivy/uix/dropdown.py +391 -0
  581. kivy/uix/effectwidget.py +772 -0
  582. kivy/uix/filechooser.py +1134 -0
  583. kivy/uix/floatlayout.py +148 -0
  584. kivy/uix/gesturesurface.py +625 -0
  585. kivy/uix/gridlayout.py +629 -0
  586. kivy/uix/image.py +528 -0
  587. kivy/uix/label.py +1200 -0
  588. kivy/uix/layout.py +322 -0
  589. kivy/uix/modalview.py +339 -0
  590. kivy/uix/pagelayout.py +233 -0
  591. kivy/uix/popup.py +266 -0
  592. kivy/uix/progressbar.py +95 -0
  593. kivy/uix/recycleboxlayout.py +183 -0
  594. kivy/uix/recyclegridlayout.py +255 -0
  595. kivy/uix/recyclelayout.py +446 -0
  596. kivy/uix/recycleview/__init__.py +624 -0
  597. kivy/uix/recycleview/datamodel.py +194 -0
  598. kivy/uix/recycleview/layout.py +253 -0
  599. kivy/uix/recycleview/views.py +423 -0
  600. kivy/uix/relativelayout.py +324 -0
  601. kivy/uix/rst.py +1492 -0
  602. kivy/uix/sandbox.py +198 -0
  603. kivy/uix/scatter.py +645 -0
  604. kivy/uix/scatterlayout.py +93 -0
  605. kivy/uix/screenmanager.py +1275 -0
  606. kivy/uix/scrollview.py +1288 -0
  607. kivy/uix/settings.py +1343 -0
  608. kivy/uix/slider.py +421 -0
  609. kivy/uix/spinner.py +221 -0
  610. kivy/uix/splitter.py +430 -0
  611. kivy/uix/stacklayout.py +331 -0
  612. kivy/uix/stencilview.py +40 -0
  613. kivy/uix/switch.py +122 -0
  614. kivy/uix/tabbedpanel.py +880 -0
  615. kivy/uix/textinput.py +4049 -0
  616. kivy/uix/togglebutton.py +37 -0
  617. kivy/uix/treeview.py +665 -0
  618. kivy/uix/video.py +302 -0
  619. kivy/uix/videoplayer.py +697 -0
  620. kivy/uix/vkeyboard.py +879 -0
  621. kivy/uix/widget.py +1629 -0
  622. kivy/utils.py +550 -0
  623. kivy/vector.py +412 -0
  624. kivy/weakmethod.py +72 -0
kivy/uix/textinput.py ADDED
@@ -0,0 +1,4049 @@
1
+ '''
2
+ Text Input
3
+ ==========
4
+
5
+ .. versionadded:: 1.0.4
6
+
7
+ .. image:: images/textinput-mono.jpg
8
+ .. image:: images/textinput-multi.jpg
9
+
10
+ The :class:`TextInput` widget provides a box for editable plain text.
11
+
12
+ Unicode, multiline, cursor navigation, selection and clipboard features
13
+ are supported.
14
+
15
+ The :class:`TextInput` uses two different coordinate systems:
16
+
17
+ * (x, y) - coordinates in pixels, mostly used for rendering on screen.
18
+ * (col, row) - cursor index in characters / lines, used for selection
19
+ and cursor movement.
20
+
21
+
22
+ Usage example
23
+ -------------
24
+
25
+ To create a multiline :class:`TextInput` (the 'enter' key adds a new line)::
26
+
27
+ from kivy.uix.textinput import TextInput
28
+ textinput = TextInput(text='Hello world')
29
+
30
+ To create a singleline :class:`TextInput`, set the :class:`TextInput.multiline`
31
+ property to False (the 'enter' key will defocus the TextInput and emit an
32
+ :meth:`TextInput.on_text_validate` event)::
33
+
34
+ def on_enter(instance, value):
35
+ print('User pressed enter in', instance)
36
+
37
+ textinput = TextInput(text='Hello world', multiline=False)
38
+ textinput.bind(on_text_validate=on_enter)
39
+
40
+ The textinput's text is stored in its :attr:`TextInput.text` property. To run a
41
+ callback when the text changes::
42
+
43
+ def on_text(instance, value):
44
+ print('The widget', instance, 'have:', value)
45
+
46
+ textinput = TextInput()
47
+ textinput.bind(text=on_text)
48
+
49
+ You can set the :class:`focus <kivy.uix.behaviors.FocusBehavior>` to a
50
+ Textinput, meaning that the input box will be highlighted and keyboard focus
51
+ will be requested::
52
+
53
+ textinput = TextInput(focus=True)
54
+
55
+ The textinput is defocused if the 'escape' key is pressed, or if another
56
+ widget requests the keyboard. You can bind a callback to the focus property to
57
+ get notified of focus changes::
58
+
59
+ def on_focus(instance, value):
60
+ if value:
61
+ print('User focused', instance)
62
+ else:
63
+ print('User defocused', instance)
64
+
65
+ textinput = TextInput()
66
+ textinput.bind(focus=on_focus)
67
+
68
+ See :class:`~kivy.uix.behaviors.FocusBehavior`, from which the
69
+ :class:`TextInput` inherits, for more details.
70
+
71
+
72
+ Selection
73
+ ---------
74
+
75
+ The selection is automatically updated when the cursor position changes.
76
+ You can get the currently selected text from the
77
+ :attr:`TextInput.selection_text` property.
78
+
79
+ Handles
80
+ -------
81
+
82
+ One can enable :attr:`TextInput.use_handles` property to enable or disable
83
+ the usage of selection handles. This property is True by default on mobiles.
84
+
85
+ Selection Handles uses the class :class:`Selector` as the base class for
86
+ the selection handles. You can customize the color for selection handles
87
+ like so ::
88
+
89
+ <Selector>
90
+ color: 0, 1, 0, 1
91
+ # or <Textinput_instance>.selection_color or app.selection_color
92
+
93
+ TextInput instantiates the selection handles and stores it in the following
94
+ properties. :attr:`TextInput._handle_middle`,
95
+ :attr:`TextInput._handle_left`, :attr:`TextInput._handle_right`.
96
+
97
+ You should set the selection template before the Instantiating TextInput,
98
+ so as to get the selection handles to take the changes you set to apply.
99
+
100
+ Filtering
101
+ ---------
102
+
103
+ You can control which text can be added to the :class:`TextInput` by
104
+ overwriting :meth:`TextInput.insert_text`. Every string that is typed, pasted
105
+ or inserted by any other means into the :class:`TextInput` is passed through
106
+ this function. By overwriting it you can reject or change unwanted characters.
107
+
108
+ For example, to write only in capitalized characters::
109
+
110
+ class CapitalInput(TextInput):
111
+
112
+ def insert_text(self, substring, from_undo=False):
113
+ s = substring.upper()
114
+ return super().insert_text(s, from_undo=from_undo)
115
+
116
+ Or to only allow floats (0 - 9 and a single period)::
117
+
118
+ class FloatInput(TextInput):
119
+
120
+ pat = re.compile('[^0-9]')
121
+ def insert_text(self, substring, from_undo=False):
122
+ pat = self.pat
123
+ if '.' in self.text:
124
+ s = re.sub(pat, '', substring)
125
+ else:
126
+ s = '.'.join(
127
+ re.sub(pat, '', s)
128
+ for s in substring.split('.', 1)
129
+ )
130
+ return super().insert_text(s, from_undo=from_undo)
131
+
132
+ Default shortcuts
133
+ -----------------
134
+
135
+ =============== ========================================================
136
+ Shortcuts Description
137
+ --------------- --------------------------------------------------------
138
+ Left Move cursor to left
139
+ Right Move cursor to right
140
+ Up Move cursor to up
141
+ Down Move cursor to down
142
+ Home Move cursor at the beginning of the line
143
+ End Move cursor at the end of the line
144
+ PageUp Move cursor to 3 lines before
145
+ PageDown Move cursor to 3 lines after
146
+ Backspace Delete the selection or character before the cursor
147
+ Del Delete the selection of character after the cursor
148
+ Shift + <dir> Start a text selection. Dir can be Up, Down, Left or
149
+ Right
150
+ Control + c Copy selection
151
+ Control + x Cut selection
152
+ Control + v Paste clipboard content
153
+ Control + a Select all the content
154
+ Control + z undo
155
+ Control + r redo
156
+ =============== ========================================================
157
+
158
+ .. note::
159
+ To enable Emacs-style keyboard shortcuts, you can use
160
+ :class:`~kivy.uix.behaviors.emacs.EmacsBehavior`.
161
+
162
+ '''
163
+
164
+
165
+ import re
166
+ import sys
167
+ import math
168
+ from os import environ
169
+ from weakref import ref
170
+ from itertools import chain, islice
171
+
172
+ from kivy.animation import Animation
173
+ from kivy.base import EventLoop
174
+ from kivy.cache import Cache
175
+ from kivy.clock import Clock
176
+ from kivy.config import Config
177
+ from kivy.core.window import Window
178
+ from kivy.metrics import inch
179
+ from kivy.utils import boundary, platform
180
+ from kivy.uix.behaviors import FocusBehavior
181
+
182
+ from kivy.core.text import Label, DEFAULT_FONT
183
+ from kivy.graphics import Color, Rectangle, PushMatrix, PopMatrix, Callback
184
+ from kivy.graphics.context_instructions import Transform
185
+ from kivy.graphics.texture import Texture
186
+
187
+ from kivy.uix.widget import Widget
188
+ from kivy.uix.bubble import Bubble
189
+ from kivy.uix.behaviors import ButtonBehavior
190
+ from kivy.uix.image import Image
191
+
192
+ from kivy.properties import StringProperty, NumericProperty, \
193
+ BooleanProperty, AliasProperty, OptionProperty, \
194
+ ListProperty, ObjectProperty, VariableListProperty, ColorProperty, \
195
+ BoundedNumericProperty
196
+
197
+ __all__ = ('TextInput', )
198
+
199
+
200
+ if 'KIVY_DOC' in environ:
201
+ def triggered(*_, **__):
202
+ def decorator_func(func):
203
+ def decorated_func(*args, **kwargs):
204
+ return func(*args, **kwargs)
205
+ return decorated_func
206
+ return decorator_func
207
+ else:
208
+ from kivy.clock import triggered
209
+
210
+
211
+ Cache_register = Cache.register
212
+ Cache_append = Cache.append
213
+ Cache_get = Cache.get
214
+ Cache_remove = Cache.remove
215
+ Cache_register('textinput.label', timeout=60.)
216
+ Cache_register('textinput.width', timeout=60.)
217
+
218
+ FL_IS_LINEBREAK = 0x01
219
+ FL_IS_WORDBREAK = 0x02
220
+ FL_IS_NEWLINE = FL_IS_LINEBREAK | FL_IS_WORDBREAK
221
+
222
+ # late binding
223
+ Clipboard = None
224
+ CutBuffer = None
225
+ MarkupLabel = None
226
+ _platform = platform
227
+
228
+ # for reloading, we need to keep a list of textinput to retrigger the rendering
229
+ _textinput_list = []
230
+
231
+ # cache the result
232
+ _is_osx = sys.platform == 'darwin'
233
+
234
+ # When we are generating documentation, Config doesn't exist
235
+ _is_desktop = False
236
+ _scroll_timeout = _scroll_distance = 0
237
+ if Config:
238
+ _is_desktop = Config.getboolean('kivy', 'desktop')
239
+ _scroll_timeout = Config.getint('widgets', 'scroll_timeout')
240
+ _scroll_distance = '{}sp'.format(Config.getint('widgets',
241
+ 'scroll_distance'))
242
+
243
+ # register an observer to clear the textinput cache when OpenGL will reload
244
+ if 'KIVY_DOC' not in environ:
245
+
246
+ def _textinput_clear_cache(*l):
247
+ Cache_remove('textinput.label')
248
+ Cache_remove('textinput.width')
249
+ for wr in _textinput_list[:]:
250
+ textinput = wr()
251
+ if textinput is None:
252
+ _textinput_list.remove(wr)
253
+ else:
254
+ textinput._trigger_refresh_text()
255
+ textinput._refresh_hint_text()
256
+
257
+ from kivy.graphics.context import get_context
258
+ get_context().add_reload_observer(_textinput_clear_cache, True)
259
+
260
+
261
+ class Selector(ButtonBehavior, Image):
262
+ '''Default template for the selection Handles
263
+
264
+ In order to customize the look of the Selection Handles,
265
+ you should adjust its template like so ::
266
+
267
+ <Selector>
268
+ color: 1, 0, 1, 1
269
+ '''
270
+
271
+ window = ObjectProperty()
272
+ target = ObjectProperty()
273
+ matrix = ObjectProperty()
274
+
275
+ def __init__(self, **kwargs):
276
+ super().__init__(**kwargs)
277
+ self.always_release = True
278
+ self.matrix = self.target.get_window_matrix()
279
+
280
+ with self.canvas.before:
281
+ Callback(self.update_transform)
282
+ PushMatrix()
283
+ self.transform = Transform()
284
+
285
+ with self.canvas.after:
286
+ PopMatrix()
287
+
288
+ def update_transform(self, cb):
289
+ matrix = self.target.get_window_matrix()
290
+ if self.matrix != matrix:
291
+ self.matrix = matrix
292
+ self.transform.identity()
293
+ self.transform.transform(self.matrix)
294
+
295
+ def transform_touch(self, touch):
296
+ matrix = self.matrix.inverse()
297
+ touch.apply_transform_2d(
298
+ lambda x, y: matrix.transform_point(x, y, 0)[:2]
299
+ )
300
+
301
+ def on_touch_down(self, touch):
302
+ if self.parent is not EventLoop.window:
303
+ return
304
+
305
+ try:
306
+ touch.push()
307
+ self.transform_touch(touch)
308
+ self._touch_diff = self.top - touch.y
309
+ if self.collide_point(*touch.pos):
310
+ FocusBehavior.ignored_touch.append(touch)
311
+ return super().on_touch_down(touch)
312
+ finally:
313
+ touch.pop()
314
+
315
+
316
+ class TextInputCutCopyPaste(Bubble):
317
+ # Internal class used for showing the little bubble popup when
318
+ # copy/cut/paste happen.
319
+
320
+ textinput = ObjectProperty(None)
321
+ ''' Holds a reference to the TextInput this Bubble belongs to.
322
+ '''
323
+
324
+ but_cut = ObjectProperty(None)
325
+ but_copy = ObjectProperty(None)
326
+ but_paste = ObjectProperty(None)
327
+ but_selectall = ObjectProperty(None)
328
+
329
+ matrix = ObjectProperty(None)
330
+
331
+ _check_parent_ev = None
332
+
333
+ def __init__(self, **kwargs):
334
+ self.mode = 'normal'
335
+ super().__init__(**kwargs)
336
+ self._check_parent_ev = Clock.schedule_interval(self._check_parent, .5)
337
+ self.matrix = self.textinput.get_window_matrix()
338
+
339
+ with self.canvas.before:
340
+ Callback(self.update_transform)
341
+ PushMatrix()
342
+ self.transform = Transform()
343
+
344
+ with self.canvas.after:
345
+ PopMatrix()
346
+
347
+ def update_transform(self, cb):
348
+ m = self.textinput.get_window_matrix()
349
+ if self.matrix != m:
350
+ self.matrix = m
351
+ self.transform.identity()
352
+ self.transform.transform(self.matrix)
353
+
354
+ def transform_touch(self, touch):
355
+ matrix = self.matrix.inverse()
356
+ touch.apply_transform_2d(
357
+ lambda x, y: matrix.transform_point(x, y, 0)[:2])
358
+
359
+ def on_touch_down(self, touch):
360
+ try:
361
+ touch.push()
362
+ self.transform_touch(touch)
363
+ if self.collide_point(*touch.pos):
364
+ FocusBehavior.ignored_touch.append(touch)
365
+ return super().on_touch_down(touch)
366
+ finally:
367
+ touch.pop()
368
+
369
+ def on_touch_up(self, touch):
370
+ try:
371
+ touch.push()
372
+ self.transform_touch(touch)
373
+ for child in self.content.children:
374
+ if ref(child) in touch.grab_list:
375
+ touch.grab_current = child
376
+ break
377
+ return super().on_touch_up(touch)
378
+ finally:
379
+ touch.pop()
380
+
381
+ def on_textinput(self, instance, value):
382
+ global Clipboard
383
+ if value and not Clipboard and not _is_desktop:
384
+ value._ensure_clipboard()
385
+
386
+ def _check_parent(self, dt):
387
+ # this is a prevention to get the Bubble staying on the screen, if the
388
+ # attached textinput is not on the screen anymore.
389
+ parent = self.textinput
390
+ while parent is not None:
391
+ if parent == parent.parent:
392
+ break
393
+ parent = parent.parent
394
+ if parent is None:
395
+ self._check_parent_ev.cancel()
396
+ if self.textinput:
397
+ self.textinput._hide_cut_copy_paste()
398
+
399
+ def on_parent(self, instance, value):
400
+ parent = self.textinput
401
+ mode = self.mode
402
+
403
+ if parent:
404
+ self.content.clear_widgets()
405
+ if mode == 'paste':
406
+ # show only paste on long touch
407
+ self.but_selectall.opacity = 1
408
+ widget_list = [self.but_selectall, ]
409
+ if not parent.readonly:
410
+ widget_list.append(self.but_paste)
411
+ elif parent.readonly:
412
+ # show only copy for read only text input
413
+ widget_list = (self.but_copy, )
414
+ else:
415
+ # normal mode
416
+ widget_list = (self.but_cut, self.but_copy, self.but_paste)
417
+
418
+ for widget in widget_list:
419
+ self.content.add_widget(widget)
420
+
421
+ def do(self, action):
422
+ textinput = self.textinput
423
+
424
+ if action == 'cut':
425
+ textinput._cut(textinput.selection_text)
426
+ elif action == 'copy':
427
+ textinput.copy()
428
+ elif action == 'paste':
429
+ textinput.paste()
430
+ elif action == 'selectall':
431
+ textinput.select_all()
432
+ self.mode = ''
433
+ anim = Animation(opacity=0, d=.333)
434
+ anim.bind(on_complete=lambda *args:
435
+ self.on_parent(self, self.parent))
436
+ anim.start(self.but_selectall)
437
+ return
438
+
439
+ self.hide()
440
+
441
+ def hide(self):
442
+ parent = self.parent
443
+ if not parent:
444
+ return
445
+
446
+ anim = Animation(opacity=0, d=.225)
447
+ anim.bind(on_complete=lambda *args: parent.remove_widget(self))
448
+ anim.start(self)
449
+
450
+
451
+ class TextInput(FocusBehavior, Widget):
452
+ '''TextInput class. See module documentation for more information.
453
+
454
+ :Events:
455
+ `on_text_validate`
456
+ Fired only in multiline=False mode when the user hits 'enter'.
457
+ This will also unfocus the textinput.
458
+ `on_double_tap`
459
+ Fired when a double tap happens in the text input. The default
460
+ behavior selects the text around the cursor position. More info at
461
+ :meth:`on_double_tap`.
462
+ `on_triple_tap`
463
+ Fired when a triple tap happens in the text input. The default
464
+ behavior selects the line around the cursor position. More info at
465
+ :meth:`on_triple_tap`.
466
+ `on_quad_touch`
467
+ Fired when four fingers are touching the text input. The default
468
+ behavior selects the whole text. More info at
469
+ :meth:`on_quad_touch`.
470
+
471
+ .. warning::
472
+ When changing a :class:`TextInput` property that requires re-drawing,
473
+ e.g. modifying the :attr:`text`, the updates occur on the next
474
+ clock cycle and not instantly. This might cause any changes to the
475
+ :class:`TextInput` that occur between the modification and the next
476
+ cycle to be ignored, or to use previous values. For example, after
477
+ a update to the :attr:`text`, changing the cursor in the same clock
478
+ frame will move it using the previous text and will likely end up in an
479
+ incorrect position. The solution is to schedule any updates to occur
480
+ on the next clock cycle using
481
+ :meth:`~kivy.clock.ClockBase.schedule_once`.
482
+
483
+ .. Note::
484
+ Selection is cancelled when TextInput is focused. If you need to
485
+ show selection when TextInput is focused, you should delay
486
+ (use Clock.schedule) the call to the functions for selecting
487
+ text (select_all, select_text).
488
+
489
+ .. versionchanged:: 1.10.0
490
+ `background_disabled_active` has been removed.
491
+
492
+ .. versionchanged:: 1.9.0
493
+
494
+ :class:`TextInput` now inherits from
495
+ :class:`~kivy.uix.behaviors.FocusBehavior`.
496
+ :attr:`~kivy.uix.behaviors.FocusBehavior.keyboard_mode`,
497
+ :meth:`~kivy.uix.behaviors.FocusBehavior.show_keyboard`,
498
+ :meth:`~kivy.uix.behaviors.FocusBehavior.hide_keyboard`,
499
+ :meth:`~kivy.uix.behaviors.FocusBehavior.focus`,
500
+ and :attr:`~kivy.uix.behaviors.FocusBehavior.input_type`
501
+ have been removed since they are now inherited
502
+ from :class:`~kivy.uix.behaviors.FocusBehavior`.
503
+
504
+ .. versionchanged:: 1.7.0
505
+ `on_double_tap`, `on_triple_tap` and `on_quad_touch` events added.
506
+
507
+ .. versionchanged:: 2.1.0
508
+ :attr:`~kivy.uix.behaviors.FocusBehavior.keyboard_suggestions`
509
+ is now inherited from :class:`~kivy.uix.behaviors.FocusBehavior`.
510
+ '''
511
+
512
+ __events__ = ('on_text_validate', 'on_double_tap', 'on_triple_tap',
513
+ 'on_quad_touch')
514
+
515
+ _resolved_base_dir = None
516
+
517
+ def __init__(self, **kwargs):
518
+ self._update_graphics_ev = Clock.create_trigger(
519
+ self._update_graphics, -1)
520
+ self.is_focusable = kwargs.get('is_focusable', True)
521
+ self._cursor = [0, 0]
522
+ self._selection = False
523
+ self._selection_finished = True
524
+ self._selection_touch = None
525
+ self.selection_text = u''
526
+ self._selection_from = None
527
+ self._selection_to = None
528
+ self._selection_callback = None
529
+ self._handle_left = None
530
+ self._handle_right = None
531
+ self._handle_middle = None
532
+ self._bubble = None
533
+ self._lines_flags = []
534
+ self._lines_labels = []
535
+ self._lines_rects = []
536
+ self._hint_text_flags = []
537
+ self._hint_text_labels = []
538
+ self._hint_text_rects = []
539
+ self._label_cached = None
540
+ self._line_options = None
541
+ self._keyboard_mode = Config.get('kivy', 'keyboard_mode')
542
+ self._command_mode = False
543
+ self._command = ''
544
+ self.reset_undo()
545
+ self._touch_count = 0
546
+ self._ctrl_l = False
547
+ self._ctrl_r = False
548
+ self._alt_l = False
549
+ self._alt_r = False
550
+ self._refresh_text_from_property_ev = None
551
+ self._long_touch_ev = None
552
+ self._do_blink_cursor_ev = Clock.create_trigger(
553
+ self._do_blink_cursor, .5, interval=True)
554
+ self._refresh_line_options_ev = None
555
+ self._scroll_distance_x = 0
556
+ self._scroll_distance_y = 0
557
+ self._enable_scroll = True
558
+ self._have_scrolled = False
559
+
560
+ # [from; to) range of lines being partially or fully rendered
561
+ # in TextInput's viewport
562
+ self._visible_lines_range = 0, 0
563
+
564
+ self.interesting_keys = {
565
+ 8: 'backspace',
566
+ 13: 'enter',
567
+ 127: 'del',
568
+ 271: 'enter',
569
+ 273: 'cursor_up',
570
+ 274: 'cursor_down',
571
+ 275: 'cursor_right',
572
+ 276: 'cursor_left',
573
+ 278: 'cursor_home',
574
+ 279: 'cursor_end',
575
+ 280: 'cursor_pgup',
576
+ 281: 'cursor_pgdown',
577
+ 303: 'shift_L',
578
+ 304: 'shift_R',
579
+ 305: 'ctrl_L',
580
+ 306: 'ctrl_R',
581
+ 308: 'alt_L',
582
+ 307: 'alt_R'
583
+ }
584
+
585
+ super().__init__(**kwargs)
586
+
587
+ fbind = self.fbind
588
+ refresh_line_options = self._trigger_refresh_line_options
589
+ update_text_options = self._update_text_options
590
+ trigger_update_graphics = self._trigger_update_graphics
591
+
592
+ fbind('font_size', refresh_line_options)
593
+ fbind('font_name', refresh_line_options)
594
+ fbind('font_context', refresh_line_options)
595
+ fbind('font_family', refresh_line_options)
596
+ fbind('base_direction', refresh_line_options)
597
+ fbind('text_language', refresh_line_options)
598
+
599
+ def handle_readonly(instance, value):
600
+ if value and (not _is_desktop or not self.allow_copy):
601
+ self.is_focusable = False
602
+ if (not (value or self.disabled) or _is_desktop and
603
+ self._keyboard_mode == 'system'):
604
+ self._editable = True
605
+ else:
606
+ self._editable = False
607
+
608
+ fbind('padding', update_text_options)
609
+ fbind('tab_width', update_text_options)
610
+ fbind('font_size', update_text_options)
611
+ fbind('font_name', update_text_options)
612
+ fbind('size', update_text_options)
613
+ fbind('password', update_text_options)
614
+ fbind('password_mask', update_text_options)
615
+
616
+ fbind('pos', trigger_update_graphics)
617
+ fbind('halign', trigger_update_graphics)
618
+ fbind('readonly', handle_readonly)
619
+ fbind('focus', self._on_textinput_focused)
620
+ handle_readonly(self, self.readonly)
621
+
622
+ handles = self._trigger_position_handles = Clock.create_trigger(
623
+ self._position_handles)
624
+ self._trigger_show_handles = Clock.create_trigger(
625
+ self._show_handles, .05)
626
+ self._trigger_cursor_reset = Clock.create_trigger(
627
+ self._reset_cursor_blink)
628
+ self._trigger_update_cutbuffer = Clock.create_trigger(
629
+ self._update_cutbuffer)
630
+ refresh_line_options()
631
+ self._trigger_refresh_text()
632
+
633
+ fbind('pos', handles)
634
+ fbind('size', handles)
635
+
636
+ # when the gl context is reloaded, trigger the text rendering again.
637
+ _textinput_list.append(ref(self, TextInput._reload_remove_observer))
638
+
639
+ if platform == 'linux':
640
+ self._ensure_clipboard()
641
+
642
+ def on_text_validate(self):
643
+ pass
644
+
645
+ def cursor_index(self, cursor=None):
646
+ '''Return the cursor index in the text/value.
647
+ '''
648
+ if not cursor:
649
+ cursor = self.cursor
650
+ try:
651
+ lines = self._lines
652
+ if not lines:
653
+ return 0
654
+
655
+ flags = self._lines_flags
656
+ index, cursor_row = cursor
657
+
658
+ for _, line, flag in zip(
659
+ range(min(cursor_row, len(lines))),
660
+ lines,
661
+ flags
662
+ ):
663
+ index += len(line)
664
+ if flag & FL_IS_LINEBREAK:
665
+ index += 1
666
+
667
+ if flags[cursor_row] & FL_IS_LINEBREAK:
668
+ index += 1
669
+ return index
670
+
671
+ except IndexError:
672
+ return 0
673
+
674
+ def cursor_offset(self):
675
+ '''Get the cursor x offset on the current line.
676
+ '''
677
+ offset = 0
678
+ row = int(self.cursor_row)
679
+ col = int(self.cursor_col)
680
+ lines = self._lines
681
+ if col and row < len(lines):
682
+ offset = self._get_text_width(
683
+ lines[row][:col],
684
+ self.tab_width,
685
+ self._label_cached
686
+ )
687
+ return offset
688
+
689
+ def get_cursor_from_index(self, index):
690
+ '''Return the (col, row) of the cursor from text index.
691
+ '''
692
+ index = boundary(index, 0, len(self.text))
693
+ if index <= 0:
694
+ return 0, 0
695
+ flags = self._lines_flags
696
+ lines = self._lines
697
+ if not lines:
698
+ return 0, 0
699
+
700
+ i = 0
701
+ for row, line in enumerate(lines):
702
+ count = i + len(line)
703
+ if flags[row] & FL_IS_LINEBREAK:
704
+ count += 1
705
+ i += 1
706
+ if count >= index:
707
+ return index - i, row
708
+ i = count
709
+ return int(index), int(row)
710
+
711
+ def select_text(self, start, end):
712
+ ''' Select a portion of text displayed in this TextInput.
713
+
714
+ .. versionadded:: 1.4.0
715
+
716
+ :Parameters:
717
+ `start`
718
+ Index of textinput.text from where to start selection
719
+ `end`
720
+ Index of textinput.text till which the selection should be
721
+ displayed
722
+ '''
723
+ if end < start:
724
+ raise Exception('end must be superior to start')
725
+ text_length = len(self.text)
726
+ self._selection_from = boundary(start, 0, text_length)
727
+ self._selection_to = boundary(end, 0, text_length)
728
+ self._selection_finished = True
729
+ self._update_selection(True)
730
+ self._update_graphics_selection()
731
+
732
+ def select_all(self):
733
+ ''' Select all of the text displayed in this TextInput.
734
+
735
+ .. versionadded:: 1.4.0
736
+ '''
737
+ self.select_text(0, len(self.text))
738
+
739
+ re_indent = re.compile(r'^(\s*|)')
740
+
741
+ def _auto_indent(self, substring):
742
+ index = self.cursor_index()
743
+ if index > 0:
744
+ _text = self.text
745
+ line_start = _text.rfind('\n', 0, index)
746
+ if line_start > -1:
747
+ line = _text[line_start + 1:index]
748
+ indent = self.re_indent.match(line).group()
749
+ substring += indent
750
+ return substring
751
+
752
+ def insert_text(self, substring, from_undo=False):
753
+ '''Insert new text at the current cursor position. Override this
754
+ function in order to pre-process text for input validation.
755
+ '''
756
+ _lines = self._lines
757
+ _lines_flags = self._lines_flags
758
+
759
+ if self.readonly or not substring or not self._lines:
760
+ return
761
+
762
+ if isinstance(substring, bytes):
763
+ substring = substring.decode('utf8')
764
+
765
+ if self.replace_crlf:
766
+ substring = substring.replace(u'\r\n', u'\n')
767
+
768
+ self._hide_handles(EventLoop.window)
769
+
770
+ if not from_undo and self.multiline and self.auto_indent \
771
+ and substring == u'\n':
772
+ substring = self._auto_indent(substring)
773
+
774
+ mode = self.input_filter
775
+ if mode not in (None, 'int', 'float'):
776
+ substring = mode(substring, from_undo)
777
+ if not substring:
778
+ return
779
+
780
+ col, row = self.cursor
781
+ cindex = self.cursor_index()
782
+ text = _lines[row]
783
+ len_str = len(substring)
784
+ new_text = text[:col] + substring + text[col:]
785
+ if mode is not None:
786
+ if mode == 'int':
787
+ if not re.match(self._insert_int_pat, new_text):
788
+ return
789
+ elif mode == 'float':
790
+ if not re.match(self._insert_float_pat, new_text):
791
+ return
792
+ self._set_line_text(row, new_text)
793
+
794
+ if len_str > 1 or substring == u'\n' or\
795
+ (substring == u' ' and _lines_flags[row] != FL_IS_LINEBREAK) or\
796
+ (row + 1 < len(_lines) and
797
+ _lines_flags[row + 1] != FL_IS_LINEBREAK) or\
798
+ (self._get_text_width(
799
+ new_text,
800
+ self.tab_width,
801
+ self._label_cached) > (self.width - self.padding[0] -
802
+ self.padding[2])):
803
+ # Avoid refreshing text on every keystroke.
804
+ # Allows for faster typing of text when the amount of text in
805
+ # TextInput gets large.
806
+
807
+ (
808
+ start, finish, lines, lines_flags, len_lines
809
+ ) = self._get_line_from_cursor(row, new_text)
810
+
811
+ # calling trigger here could lead to wrong cursor positioning
812
+ # and repeating of text when keys are added rapidly in a automated
813
+ # fashion. From Android Keyboard for example.
814
+ self._refresh_text_from_property(
815
+ 'insert', start, finish, lines, lines_flags, len_lines
816
+ )
817
+
818
+ self.cursor = self.get_cursor_from_index(cindex + len_str)
819
+ # handle undo and redo
820
+ self._set_unredo_insert(cindex, cindex + len_str, substring, from_undo)
821
+
822
+ def _get_line_from_cursor(self, start, new_text, lines=None,
823
+ lines_flags=None):
824
+ # get current paragraph from cursor position
825
+ if lines is None:
826
+ lines = self._lines
827
+ if lines_flags is None:
828
+ lines_flags = self._lines_flags
829
+ finish = start
830
+ _next = start + 1
831
+ if start > 0 and lines_flags[start] != FL_IS_LINEBREAK:
832
+ start -= 1
833
+ new_text = lines[start] + new_text
834
+ i = _next
835
+ for i in range(_next, len(lines_flags)):
836
+ if lines_flags[i] == FL_IS_LINEBREAK:
837
+ finish = i - 1
838
+ break
839
+ else:
840
+ finish = i
841
+
842
+ new_text = new_text + u''.join(lines[_next:finish + 1])
843
+ lines, lines_flags = self._split_smart(new_text)
844
+
845
+ len_lines = max(1, len(lines))
846
+ return start, finish, lines, lines_flags, len_lines
847
+
848
+ def _set_unredo_insert(self, ci, sci, substring, from_undo):
849
+ # handle undo and redo
850
+ if from_undo:
851
+ return
852
+ self._undo.append({
853
+ 'undo_command': ('insert', ci, sci),
854
+ 'redo_command': (ci, substring)
855
+ })
856
+ # reset redo when undo is appended to
857
+ self._redo = []
858
+
859
+ def reset_undo(self):
860
+ '''Reset undo and redo lists from memory.
861
+
862
+ .. versionadded:: 1.3.0
863
+
864
+ '''
865
+ self._redo = self._undo = []
866
+
867
+ def do_redo(self):
868
+ '''Do redo operation.
869
+
870
+ .. versionadded:: 1.3.0
871
+
872
+ This action re-does any command that has been un-done by
873
+ do_undo/ctrl+z. This function is automatically called when
874
+ `ctrl+r` keys are pressed.
875
+ '''
876
+ try:
877
+ x_item = self._redo.pop()
878
+ undo_type = x_item['undo_command'][0]
879
+ _get_cusror_from_index = self.get_cursor_from_index
880
+
881
+ if undo_type == 'insert':
882
+ cindex, substring = x_item['redo_command']
883
+ self.cursor = _get_cusror_from_index(cindex)
884
+ self.insert_text(substring, True)
885
+ elif undo_type == 'bkspc':
886
+ self.cursor = _get_cusror_from_index(x_item['redo_command'])
887
+ self.do_backspace(from_undo=True)
888
+ elif undo_type == 'shiftln':
889
+ direction, rows, cursor = x_item['redo_command'][1:]
890
+ self._shift_lines(direction, rows, cursor, True)
891
+ else:
892
+ # delsel
893
+ cindex, scindex = x_item['redo_command']
894
+ self._selection_from = cindex
895
+ self._selection_to = scindex
896
+ self._selection = True
897
+ self.delete_selection(True)
898
+ self.cursor = _get_cusror_from_index(cindex)
899
+ self._undo.append(x_item)
900
+ except IndexError:
901
+ # reached at top of undo list
902
+ pass
903
+
904
+ def do_undo(self):
905
+ '''Do undo operation.
906
+
907
+ .. versionadded:: 1.3.0
908
+
909
+ This action un-does any edits that have been made since the last
910
+ call to reset_undo().
911
+ This function is automatically called when `ctrl+z` keys are pressed.
912
+ '''
913
+ try:
914
+ x_item = self._undo.pop()
915
+ undo_type = x_item['undo_command'][0]
916
+ self.cursor = self.get_cursor_from_index(x_item['undo_command'][1])
917
+
918
+ if undo_type == 'insert':
919
+ cindex, scindex = x_item['undo_command'][1:]
920
+ self._selection_from = cindex
921
+ self._selection_to = scindex
922
+ self._selection = True
923
+ self.delete_selection(True)
924
+ elif undo_type == 'bkspc':
925
+ substring = x_item['undo_command'][2][0]
926
+ mode = x_item['undo_command'][3]
927
+ self.insert_text(substring, True)
928
+ if mode == 'del':
929
+ self.cursor = self.get_cursor_from_index(
930
+ self.cursor_index() - 1)
931
+ elif undo_type == 'shiftln':
932
+ direction, rows, cursor = x_item['undo_command'][1:]
933
+ self._shift_lines(direction, rows, cursor, True)
934
+ else:
935
+ # delsel
936
+ substring = x_item['undo_command'][2:][0]
937
+ self.insert_text(substring, True)
938
+ self._redo.append(x_item)
939
+ self.scroll_x = self.get_max_scroll_x()
940
+ except IndexError:
941
+ # reached at top of undo list
942
+ pass
943
+
944
+ def do_backspace(self, from_undo=False, mode='bkspc'):
945
+ '''Do backspace operation from the current cursor position.
946
+ This action might do several things:
947
+
948
+ - removing the current selection if available.
949
+ - removing the previous char and move the cursor back.
950
+ - do nothing, if we are at the start.
951
+
952
+ '''
953
+ # IME system handles its own backspaces
954
+ if self.readonly or self._ime_composition:
955
+ return
956
+ col, row = self.cursor
957
+ _lines = self._lines
958
+ _lines_flags = self._lines_flags
959
+ text = _lines[row]
960
+ cursor_index = self.cursor_index()
961
+
962
+ if col == 0 and row == 0:
963
+ return
964
+ start = row
965
+ if col == 0:
966
+ if _lines_flags[row] == FL_IS_LINEBREAK:
967
+ substring = u'\n'
968
+ new_text = _lines[row - 1] + text
969
+ else:
970
+ substring = _lines[row - 1][-1] if len(_lines[row - 1]) > 0 \
971
+ else u''
972
+ new_text = _lines[row - 1][:-1] + text
973
+ self._set_line_text(row - 1, new_text)
974
+ self._delete_line(row)
975
+ start = row - 1
976
+ else:
977
+ # ch = text[col-1]
978
+ substring = text[col - 1]
979
+ new_text = text[:col - 1] + text[col:]
980
+ self._set_line_text(row, new_text)
981
+
982
+ # refresh just the current line instead of the whole text
983
+ start, finish, lines, lineflags, len_lines = (
984
+ self._get_line_from_cursor(start, new_text)
985
+ )
986
+ # avoid trigger refresh, leads to issue with
987
+ # keys/text send rapidly through code.
988
+ self._refresh_text_from_property(
989
+ 'insert' if col == 0 else 'del', start, finish,
990
+ lines, lineflags, len_lines
991
+ )
992
+
993
+ self.cursor = self.get_cursor_from_index(cursor_index - 1)
994
+ # handle undo and redo
995
+ self._set_unredo_bkspc(
996
+ cursor_index,
997
+ cursor_index - 1,
998
+ substring, from_undo, mode)
999
+ self.scroll_x = self.get_max_scroll_x()
1000
+
1001
+ def _set_unredo_bkspc(self, ol_index, new_index, substring, from_undo,
1002
+ mode):
1003
+ # handle undo and redo for backspace
1004
+ if from_undo:
1005
+ return
1006
+ self._undo.append({
1007
+ 'undo_command': ('bkspc', new_index, substring, mode),
1008
+ 'redo_command': ol_index})
1009
+ # reset redo when undo is appended to
1010
+ self._redo = []
1011
+
1012
+ _re_whitespace = re.compile(r'\s+')
1013
+
1014
+ def _move_cursor_word_left(self, index=None):
1015
+ pos = index or self.cursor_index()
1016
+ if pos == 0:
1017
+ return self.cursor
1018
+ lines = self._lines
1019
+ col, row = self.get_cursor_from_index(pos)
1020
+ if col == 0:
1021
+ row -= 1
1022
+ col = len(lines[row])
1023
+
1024
+ while True:
1025
+ matches = list(self._re_whitespace.finditer(lines[row], 0, col))
1026
+ if not matches:
1027
+ if col == 0:
1028
+ if row == 0:
1029
+ return 0, 0
1030
+ row -= 1
1031
+ col = len(lines[row])
1032
+ continue
1033
+ return 0, row
1034
+
1035
+ match = matches[-1]
1036
+ mpos = match.end()
1037
+ if mpos == col:
1038
+ if len(matches) > 1:
1039
+ match = matches[-2]
1040
+ mpos = match.end()
1041
+ else:
1042
+ if match.start() == 0:
1043
+ if row == 0:
1044
+ return 0, 0
1045
+ row -= 1
1046
+ col = len(lines[row])
1047
+ continue
1048
+ return 0, row
1049
+ col = mpos
1050
+ return col, row
1051
+
1052
+ def _move_cursor_word_right(self, index=None):
1053
+ pos = index or self.cursor_index()
1054
+ col, row = self.get_cursor_from_index(pos)
1055
+ lines = self._lines
1056
+ mrow = len(lines) - 1
1057
+ if row == mrow and col == len(lines[row]):
1058
+ return col, row
1059
+ if col == len(lines[row]):
1060
+ row += 1
1061
+ col = 0
1062
+
1063
+ while True:
1064
+ matches = list(self._re_whitespace.finditer(lines[row], col))
1065
+ if not matches:
1066
+ if col == len(lines[row]):
1067
+ if row == mrow:
1068
+ return col, row
1069
+ row += 1
1070
+ col = 0
1071
+ continue
1072
+ return len(lines[row]), row
1073
+
1074
+ match = matches[0]
1075
+ mpos = match.start()
1076
+ if mpos == col:
1077
+ if len(matches) > 1:
1078
+ match = matches[1]
1079
+ mpos = match.start()
1080
+ else:
1081
+ if match.end() == len(lines[row]):
1082
+ if row == mrow:
1083
+ return col, row
1084
+ row += 1
1085
+ col = 0
1086
+ continue
1087
+ return len(lines[row]), row
1088
+ col = mpos
1089
+ return col, row
1090
+
1091
+ def _expand_range(self, ifrom, ito=None):
1092
+ if ito is None:
1093
+ ito = ifrom
1094
+ rfrom = self.get_cursor_from_index(ifrom)[1]
1095
+ rtcol, rto = self.get_cursor_from_index(ito)
1096
+ rfrom, rto = self._expand_rows(rfrom, rto + 1 if rtcol else rto)
1097
+
1098
+ return (self.cursor_index((0, rfrom)),
1099
+ self.cursor_index((0, rto)))
1100
+
1101
+ def _expand_rows(self, rfrom, rto=None):
1102
+ if rto is None or rto == rfrom:
1103
+ rto = rfrom + 1
1104
+ lines = self._lines
1105
+ flags = list(reversed(self._lines_flags))
1106
+ while rfrom > 0 and not (flags[rfrom - 1] & FL_IS_NEWLINE):
1107
+ rfrom -= 1
1108
+ rmax = len(lines) - 1
1109
+ while 0 < rto < rmax and not (flags[rto - 1] & FL_IS_NEWLINE):
1110
+ rto += 1
1111
+ return max(0, rfrom), min(rmax, rto)
1112
+
1113
+ def _shift_lines(
1114
+ self, direction, rows=None, old_cursor=None, from_undo=False
1115
+ ):
1116
+ if self._selection_callback:
1117
+ if from_undo:
1118
+ self._selection_callback.cancel()
1119
+ else:
1120
+ return
1121
+
1122
+ lines = self._lines
1123
+ flags = list(reversed(self._lines_flags))
1124
+ labels = self._lines_labels
1125
+ rects = self._lines_rects
1126
+ orig_cursor = self.cursor
1127
+ sel = None
1128
+ if old_cursor is not None:
1129
+ self.cursor = old_cursor
1130
+
1131
+ if not rows:
1132
+ sindex = self.selection_from
1133
+ eindex = self.selection_to
1134
+ if (sindex or eindex) and sindex != eindex:
1135
+ sindex, eindex = tuple(sorted((sindex, eindex)))
1136
+ sindex, eindex = self._expand_range(sindex, eindex)
1137
+ else:
1138
+ sindex, eindex = self._expand_range(self.cursor_index())
1139
+ srow = self.get_cursor_from_index(sindex)[1]
1140
+ erow = self.get_cursor_from_index(eindex)[1]
1141
+ sel = sindex, eindex
1142
+
1143
+ if direction < 0 and srow > 0:
1144
+ psrow, perow = self._expand_rows(srow - 1)
1145
+ rows = ((srow, erow), (psrow, perow))
1146
+ elif direction > 0 and erow < len(lines) - 1:
1147
+ psrow, perow = self._expand_rows(erow)
1148
+ rows = ((srow, erow), (psrow, perow))
1149
+
1150
+ else:
1151
+ (srow, erow), (psrow, perow) = rows
1152
+ if direction < 0:
1153
+ m1srow, m1erow = psrow, perow
1154
+ m2srow, m2erow = srow, erow
1155
+ cdiff = psrow - perow
1156
+ xdiff = srow - erow
1157
+ else:
1158
+ m1srow, m1erow = srow, erow
1159
+ m2srow, m2erow = psrow, perow
1160
+ cdiff = perow - psrow
1161
+ xdiff = erow - srow
1162
+
1163
+ self._lines_flags = list(reversed(chain(
1164
+ flags[:m1srow],
1165
+ flags[m2srow:m2erow],
1166
+ flags[m1srow:m1erow],
1167
+ flags[m2erow:],
1168
+ )))
1169
+ self._lines[:] = (
1170
+ lines[:m1srow]
1171
+ + lines[m2srow:m2erow]
1172
+ + lines[m1srow:m1erow]
1173
+ + lines[m2erow:]
1174
+ )
1175
+ self._lines_labels = (
1176
+ labels[:m1srow]
1177
+ + labels[m2srow:m2erow]
1178
+ + labels[m1srow:m1erow]
1179
+ + labels[m2erow:]
1180
+ )
1181
+ self._lines_rects = (
1182
+ rects[:m1srow]
1183
+ + rects[m2srow:m2erow]
1184
+ + rects[m1srow:m1erow]
1185
+ + rects[m2erow:]
1186
+ )
1187
+ self._trigger_update_graphics()
1188
+ csrow = srow + cdiff
1189
+ cerow = erow + cdiff
1190
+ sel = (
1191
+ self.cursor_index((0, csrow)),
1192
+ self.cursor_index((0, cerow))
1193
+ )
1194
+ self.cursor = self.cursor_col, self.cursor_row + cdiff
1195
+
1196
+ if not from_undo:
1197
+ undo_rows = ((srow + cdiff, erow + cdiff),
1198
+ (psrow - xdiff, perow - xdiff))
1199
+ self._undo.append({
1200
+ 'undo_command': ('shiftln', direction * -1, undo_rows,
1201
+ self.cursor),
1202
+ 'redo_command': ('shiftln', direction, rows, orig_cursor),
1203
+ })
1204
+ self._redo = []
1205
+
1206
+ if sel:
1207
+ def cb(dt):
1208
+ self.select_text(*sel)
1209
+ self._selection_callback = None
1210
+ self._selection_callback = Clock.schedule_once(cb)
1211
+
1212
+ @property
1213
+ def pgmove_speed(self):
1214
+ """how much vertical distance hitting pg_up or pg_down will move
1215
+ """
1216
+ return int(
1217
+ self.height
1218
+ / (self.line_height + self.line_spacing) - 1
1219
+ )
1220
+
1221
+ def _move_cursor_up(self, col, row, control=False, alt=False):
1222
+ if self.multiline and control:
1223
+ self.scroll_y = max(0, self.scroll_y - self.line_height)
1224
+ elif not self.readonly and self.multiline and alt:
1225
+ self._shift_lines(-1)
1226
+ return
1227
+ else:
1228
+ row = max(row - 1, 0)
1229
+ col = min(len(self._lines[row]), col)
1230
+
1231
+ return col, row
1232
+
1233
+ def _move_cursor_down(self, col, row, control, alt):
1234
+ if self.multiline and control:
1235
+ maxy = self.minimum_height - self.height
1236
+ self.scroll_y = max(
1237
+ 0,
1238
+ min(maxy, self.scroll_y + self.line_height)
1239
+ )
1240
+ elif not self.readonly and self.multiline and alt:
1241
+ self._shift_lines(1)
1242
+ return
1243
+ else:
1244
+ row = min(row + 1, len(self._lines) - 1)
1245
+ col = min(len(self._lines[row]), col)
1246
+
1247
+ return col, row
1248
+
1249
+ def do_cursor_movement(self, action, control=False, alt=False):
1250
+ '''Move the cursor relative to its current position.
1251
+ Action can be one of :
1252
+
1253
+ - cursor_left: move the cursor to the left
1254
+ - cursor_right: move the cursor to the right
1255
+ - cursor_up: move the cursor on the previous line
1256
+ - cursor_down: move the cursor on the next line
1257
+ - cursor_home: move the cursor at the start of the current line
1258
+ - cursor_end: move the cursor at the end of current line
1259
+ - cursor_pgup: move one "page" before
1260
+ - cursor_pgdown: move one "page" after
1261
+
1262
+ In addition, the behavior of certain actions can be modified:
1263
+
1264
+ - control + cursor_left: move the cursor one word to the left
1265
+ - control + cursor_right: move the cursor one word to the right
1266
+ - control + cursor_up: scroll up one line
1267
+ - control + cursor_down: scroll down one line
1268
+ - control + cursor_home: go to beginning of text
1269
+ - control + cursor_end: go to end of text
1270
+ - alt + cursor_up: shift line(s) up
1271
+ - alt + cursor_down: shift line(s) down
1272
+
1273
+ .. versionchanged:: 1.9.1
1274
+
1275
+ '''
1276
+ if not self._lines:
1277
+ return
1278
+
1279
+ col, row = self.cursor
1280
+ if action == 'cursor_up':
1281
+ result = self._move_cursor_up(col, row, control, alt)
1282
+ if result:
1283
+ col, row = result
1284
+ else:
1285
+ return
1286
+
1287
+ elif action == 'cursor_down':
1288
+ result = self._move_cursor_down(col, row, control, alt)
1289
+ if result:
1290
+ col, row = result
1291
+ else:
1292
+ return
1293
+
1294
+ elif action == 'cursor_home':
1295
+ col = 0
1296
+ if control:
1297
+ row = 0
1298
+
1299
+ elif action == 'cursor_end':
1300
+ if control:
1301
+ row = len(self._lines) - 1
1302
+ col = len(self._lines[row])
1303
+
1304
+ elif action == 'cursor_pgup':
1305
+ row = max(0, row - self.pgmove_speed)
1306
+ col = min(len(self._lines[row]), col)
1307
+
1308
+ elif action == 'cursor_pgdown':
1309
+ row = min(row + self.pgmove_speed, len(self._lines) - 1)
1310
+ col = min(len(self._lines[row]), col)
1311
+
1312
+ elif (
1313
+ self._selection and self._selection_finished
1314
+ and self._selection_from < self._selection_to
1315
+ and action == 'cursor_left'
1316
+ ):
1317
+ current_selection_to = self._selection_to
1318
+ while self._selection_from != current_selection_to:
1319
+ current_selection_to -= 1
1320
+ if col:
1321
+ col -= 1
1322
+ else:
1323
+ row -= 1
1324
+ col = len(self._lines[row])
1325
+
1326
+ elif (
1327
+ self._selection and self._selection_finished
1328
+ and self._selection_from > self._selection_to
1329
+ and action == 'cursor_right'
1330
+ ):
1331
+ current_selection_to = self._selection_to
1332
+ while self._selection_from != current_selection_to:
1333
+ current_selection_to += 1
1334
+ if len(self._lines[row]) > col:
1335
+ col += 1
1336
+ else:
1337
+ row += 1
1338
+ col = 0
1339
+
1340
+ elif action == 'cursor_left':
1341
+ if not self.password and control:
1342
+ col, row = self._move_cursor_word_left()
1343
+ else:
1344
+ if col == 0:
1345
+ if row:
1346
+ row -= 1
1347
+ col = len(self._lines[row])
1348
+ else:
1349
+ col, row = col - 1, row
1350
+
1351
+ elif action == 'cursor_right':
1352
+ if not self.password and control:
1353
+ col, row = self._move_cursor_word_right()
1354
+ else:
1355
+ if col == len(self._lines[row]):
1356
+ if row < len(self._lines) - 1:
1357
+ col = 0
1358
+ row += 1
1359
+ else:
1360
+ col, row = col + 1, row
1361
+
1362
+ dont_move_cursor = control and action in ['cursor_up', 'cursor_down']
1363
+ if dont_move_cursor:
1364
+ self._trigger_update_graphics()
1365
+ else:
1366
+ self.cursor = col, row
1367
+
1368
+ def get_cursor_from_xy(self, x, y):
1369
+ '''Return the (col, row) of the cursor from an (x, y) position.
1370
+ '''
1371
+ padding_left, padding_top, padding_right, padding_bottom = self.padding
1372
+
1373
+ lines = self._lines
1374
+ dy = self.line_height + self.line_spacing
1375
+ cursor_x = x - self.x
1376
+ scroll_y = self.scroll_y
1377
+ scroll_x = self.scroll_x
1378
+ scroll_y = scroll_y / dy if scroll_y > 0 else 0
1379
+
1380
+ cursor_y = (self.top - padding_top + scroll_y * dy) - y
1381
+ cursor_y = int(boundary(
1382
+ round(cursor_y / dy - 0.5),
1383
+ 0,
1384
+ len(lines) - 1
1385
+ ))
1386
+
1387
+ get_text_width = self._get_text_width
1388
+ tab_width = self.tab_width
1389
+ label_cached = self._label_cached
1390
+
1391
+ # Offset for horizontal text alignment
1392
+ xoff = 0
1393
+ halign = self.halign
1394
+ base_dir = self.base_direction or self._resolved_base_dir
1395
+ auto_halign_r = halign == 'auto' and base_dir and 'rtl' in base_dir
1396
+
1397
+ if halign == 'center':
1398
+ viewport_width = self.width - padding_left - padding_right
1399
+ xoff = max(
1400
+ 0, int((viewport_width - self._get_row_width(cursor_y)) / 2)
1401
+ )
1402
+
1403
+ elif halign == 'right' or auto_halign_r:
1404
+ viewport_width = self.width - padding_left - padding_right
1405
+ xoff = max(
1406
+ 0, int(viewport_width - self._get_row_width(cursor_y))
1407
+ )
1408
+
1409
+ for i in range(0, len(lines[cursor_y])):
1410
+ line_y = lines[cursor_y]
1411
+
1412
+ if cursor_x + scroll_x < (
1413
+ xoff
1414
+ + get_text_width(line_y[:i], tab_width, label_cached)
1415
+ + get_text_width(line_y[i], tab_width, label_cached) * 0.6
1416
+ + padding_left
1417
+ ):
1418
+ cursor_x = i
1419
+ break
1420
+ else:
1421
+ cursor_x = len(lines[cursor_y])
1422
+
1423
+ return cursor_x, cursor_y
1424
+
1425
+ def get_max_scroll_x(self):
1426
+ '''
1427
+ Return how many pixels it needs to scroll to the right
1428
+ to reveal the remaining content of a text that extends
1429
+ beyond the visible width of a TextInput
1430
+ '''
1431
+ minimum_width = self._get_row_width(0) + self.padding[0] + \
1432
+ self.padding[2]
1433
+ max_scroll_x = max(0, minimum_width - self.width)
1434
+ return max_scroll_x
1435
+
1436
+ #
1437
+ # Selection control
1438
+ #
1439
+ def cancel_selection(self):
1440
+ '''Cancel current selection (if any).
1441
+ '''
1442
+ self._selection_from = self._selection_to = self.cursor_index()
1443
+ self._selection = False
1444
+ self._selection_finished = True
1445
+ self._selection_touch = None
1446
+ self.selection_text = u''
1447
+ self._trigger_update_graphics()
1448
+
1449
+ def delete_selection(self, from_undo=False):
1450
+ '''Delete the current text selection (if any).
1451
+ '''
1452
+ if self.readonly:
1453
+ return
1454
+ self._hide_handles(EventLoop.window)
1455
+ scroll_x = self.scroll_x
1456
+ scroll_y = self.scroll_y
1457
+ cc, cr = self.cursor
1458
+ if not self._selection:
1459
+ return
1460
+ text = self.text
1461
+ a, b = sorted((self._selection_from, self._selection_to))
1462
+
1463
+ start = self.get_cursor_from_index(a)
1464
+ finish = self.get_cursor_from_index(b)
1465
+ cur_line = self._lines[start[1]][:start[0]] +\
1466
+ self._lines[finish[1]][finish[0]:]
1467
+
1468
+ self._set_line_text(start[1], cur_line)
1469
+ start_del, finish_del, lines, lines_flags, len_lines = \
1470
+ self._get_line_from_cursor(start[1], cur_line,
1471
+ lines=(self._lines[:(start[1] + 1)] +
1472
+ self._lines[(finish[1] + 1):]),
1473
+ lines_flags=(
1474
+ self._lines_flags[:(start[1] + 1)] +
1475
+ self._lines_flags[(finish[1] + 1):])
1476
+ )
1477
+ self._refresh_text_from_property('del', start_del,
1478
+ finish_del + (finish[1] - start[1]),
1479
+ lines, lines_flags, len_lines)
1480
+
1481
+ self.scroll_x = scroll_x
1482
+ self.scroll_y = scroll_y
1483
+ # handle undo and redo for delete selection
1484
+ if text[a:b]:
1485
+ self._set_unredo_delsel(a, b, text[a:b], from_undo)
1486
+ self.cancel_selection()
1487
+ self.cursor = self.get_cursor_from_index(a)
1488
+
1489
+ def _set_unredo_delsel(self, a, b, substring, from_undo):
1490
+ # handle undo and redo for backspace
1491
+ if from_undo:
1492
+ return
1493
+
1494
+ self._undo.append({
1495
+ 'undo_command': ('delsel', a, substring),
1496
+ 'redo_command': (a, b)})
1497
+ # reset redo when undo is appended to
1498
+ self._redo = []
1499
+
1500
+ def _update_selection(self, finished=False):
1501
+ '''Update selection text and order of from/to if finished is True.
1502
+ Can be called multiple times until finished is True.
1503
+ '''
1504
+ a, b = int(self._selection_from), int(self._selection_to)
1505
+ if a > b:
1506
+ a, b = b, a
1507
+ self._selection_finished = finished
1508
+ _selection_text = self.text[a:b]
1509
+ self.selection_text = ("" if not self.allow_copy else
1510
+ ((self.password_mask * (b - a)) if
1511
+ self.password else _selection_text))
1512
+ if not finished:
1513
+ self._selection = True
1514
+ else:
1515
+ self._selection = bool(len(_selection_text))
1516
+ self._selection_touch = None
1517
+ if a == 0:
1518
+ # update graphics only on new line
1519
+ # allows smoother scrolling, noticeably
1520
+ # faster when dealing with large text.
1521
+ self._update_graphics_selection()
1522
+ # self._trigger_update_graphics()
1523
+
1524
+ #
1525
+ # Touch control
1526
+ #
1527
+ def long_touch(self, dt):
1528
+ self._long_touch_ev = None
1529
+ if self._selection_to == self._selection_from:
1530
+ pos = self.to_local(*self._touch_down.pos, relative=False)
1531
+ self._show_cut_copy_paste(
1532
+ pos, EventLoop.window, mode='paste')
1533
+
1534
+ def cancel_long_touch_event(self):
1535
+ # schedule long touch for paste
1536
+ if self._long_touch_ev is not None:
1537
+ self._long_touch_ev.cancel()
1538
+ self._long_touch_ev = None
1539
+
1540
+ def _select_word(self, delimiters=u' .,:;!?\'"<>()[]{}'):
1541
+ cindex = self.cursor_index()
1542
+ col = self.cursor_col
1543
+ line = self._lines[self.cursor_row]
1544
+ start = max(0, len(line[:col]) -
1545
+ max(line[:col].rfind(s) for s in delimiters) - 1)
1546
+ end = min((line[col:].find(s) if line[col:].find(s) > -1
1547
+ else (len(line) - col)) for s in delimiters)
1548
+ Clock.schedule_once(lambda dt: self.select_text(cindex - start,
1549
+ cindex + end))
1550
+
1551
+ def on_double_tap(self):
1552
+ '''This event is dispatched when a double tap happens
1553
+ inside TextInput. The default behavior is to select the
1554
+ word around the current cursor position. Override this to provide
1555
+ different behavior. Alternatively, you can bind to this
1556
+ event to provide additional functionality.
1557
+ '''
1558
+ self._select_word()
1559
+
1560
+ def on_triple_tap(self):
1561
+ '''This event is dispatched when a triple tap happens
1562
+ inside TextInput. The default behavior is to select the
1563
+ line around current cursor position. Override this to provide
1564
+ different behavior. Alternatively, you can bind to this
1565
+ event to provide additional functionality.
1566
+ '''
1567
+ ci = self.cursor_index()
1568
+ sindex, eindex = self._expand_range(ci)
1569
+ Clock.schedule_once(lambda dt: self.select_text(sindex, eindex))
1570
+
1571
+ def on_quad_touch(self):
1572
+ '''This event is dispatched when four fingers are touching
1573
+ inside TextInput. The default behavior is to select all text.
1574
+ Override this to provide different behavior. Alternatively,
1575
+ you can bind to this event to provide additional functionality.
1576
+ '''
1577
+ Clock.schedule_once(lambda dt: self.select_all())
1578
+
1579
+ def on_touch_down(self, touch):
1580
+ if self.disabled:
1581
+ return
1582
+
1583
+ touch_pos = touch.pos
1584
+ if not self.collide_point(*touch_pos):
1585
+ return False
1586
+ if super().on_touch_down(touch):
1587
+ return True
1588
+
1589
+ if self.focus:
1590
+ self._trigger_cursor_reset()
1591
+
1592
+ # Check for scroll wheel
1593
+ if 'button' in touch.profile and touch.button.startswith('scroll'):
1594
+ # TODO: implement 'scrollleft' and 'scrollright'
1595
+ scroll_type = touch.button[6:]
1596
+ if scroll_type == 'down':
1597
+ if self.multiline:
1598
+ if self.scroll_y > 0:
1599
+ self.scroll_y = max(0,
1600
+ self.scroll_y - self.line_height *
1601
+ self.lines_to_scroll)
1602
+ self._trigger_update_graphics()
1603
+ else:
1604
+ if self.scroll_x > 0:
1605
+ self.scroll_x = max(0, self.scroll_x -
1606
+ self.line_height)
1607
+ self._trigger_update_graphics()
1608
+ if scroll_type == 'up':
1609
+ if self.multiline:
1610
+ max_scroll_y = max(0, self.minimum_height - self.height)
1611
+ if self.scroll_y < max_scroll_y:
1612
+ self.scroll_y = min(max_scroll_y,
1613
+ self.scroll_y + self.line_height *
1614
+ self.lines_to_scroll)
1615
+ self._trigger_update_graphics()
1616
+ else:
1617
+ max_scroll_x = self.get_max_scroll_x()
1618
+ if self.scroll_x < max_scroll_x:
1619
+ self.scroll_x = min(max_scroll_x, self.scroll_x +
1620
+ self.line_height)
1621
+ self._trigger_update_graphics()
1622
+ return True
1623
+
1624
+ touch.grab(self)
1625
+ self._touch_count += 1
1626
+ if touch.is_double_tap:
1627
+ self.dispatch('on_double_tap')
1628
+ if touch.is_triple_tap:
1629
+ self.dispatch('on_triple_tap')
1630
+ if self._touch_count == 4:
1631
+ self.dispatch('on_quad_touch')
1632
+
1633
+ # stores the touch for later use
1634
+ self._touch_down = touch
1635
+
1636
+ # Is a new touch_down, so previous scroll states needs to be reset
1637
+ self._enable_scroll = True
1638
+ self._have_scrolled = False
1639
+ self._scroll_distance_x = 0
1640
+ self._scroll_distance_y = 0
1641
+
1642
+ self._hide_cut_copy_paste(EventLoop.window)
1643
+ # schedule long touch for paste
1644
+ self._long_touch_ev = Clock.schedule_once(self.long_touch, .5)
1645
+
1646
+ self.cursor = self.get_cursor_from_xy(*touch_pos)
1647
+ if not self.scroll_from_swipe:
1648
+ self._cancel_update_selection(self._touch_down)
1649
+
1650
+ if CutBuffer and 'button' in touch.profile and \
1651
+ touch.button == 'middle':
1652
+ self.insert_text(CutBuffer.get_cutbuffer())
1653
+ return True
1654
+
1655
+ return True
1656
+
1657
+ # cancel/update existing selection after a single tap
1658
+ def _cancel_update_selection(self, touch):
1659
+ if not self._selection_touch:
1660
+ self.cancel_selection()
1661
+ self._selection_touch = touch
1662
+ self._selection_from = self._selection_to = self.cursor_index()
1663
+ self._update_selection()
1664
+
1665
+ def on_touch_move(self, touch):
1666
+ if touch.grab_current is not self:
1667
+ return
1668
+ if not self.focus:
1669
+ touch.ungrab(self)
1670
+ if self._selection_touch is touch:
1671
+ self._selection_touch = None
1672
+ return False
1673
+
1674
+ if self.scroll_from_swipe:
1675
+ self.scroll_text_from_swipe(touch)
1676
+
1677
+ if not self._have_scrolled and self._selection_touch is touch:
1678
+ self.cursor = self.get_cursor_from_xy(touch.x, touch.y)
1679
+ self._selection_to = self.cursor_index()
1680
+ self._update_selection()
1681
+ return True
1682
+
1683
+ def on_touch_up(self, touch):
1684
+ if touch.grab_current is not self:
1685
+ return
1686
+ touch.ungrab(self)
1687
+ self._touch_count -= 1
1688
+
1689
+ self.cancel_long_touch_event()
1690
+
1691
+ if not self.focus:
1692
+ return False
1693
+
1694
+ # types of touch that will have higher priority in being recognized,
1695
+ # compared to single tap
1696
+ prioritized_touch_types = (
1697
+ touch.is_double_tap
1698
+ or touch.is_triple_tap
1699
+ or self._touch_count == 4
1700
+ )
1701
+
1702
+ if not self._have_scrolled and not prioritized_touch_types:
1703
+ # Is a single tap and did not scrolled.
1704
+ # Selection needs to be canceled.
1705
+ self._cancel_update_selection(self._touch_down)
1706
+
1707
+ # show Bubble
1708
+ win = EventLoop.window
1709
+ if self._selection_to != self._selection_from:
1710
+ self._show_cut_copy_paste(touch.pos, win)
1711
+
1712
+ if self._selection_touch is touch:
1713
+ self._selection_to = self.cursor_index()
1714
+ self._update_selection(True)
1715
+ if self.use_handles and self._selection_to == self._selection_from:
1716
+ self._hide_handles()
1717
+ handle_middle = self._handle_middle
1718
+ if handle_middle is None:
1719
+ self._handle_middle = handle_middle = Selector(
1720
+ source=self.handle_image_middle,
1721
+ window=win,
1722
+ target=self,
1723
+ size_hint=(None, None),
1724
+ size=('45dp', '45dp'))
1725
+ handle_middle.bind(on_press=self._handle_pressed,
1726
+ on_touch_move=self._handle_move,
1727
+ on_release=self._handle_released)
1728
+ if not self._handle_middle.parent and self.text:
1729
+ EventLoop.window.add_widget(handle_middle, canvas='after')
1730
+ self._position_handles(mode='middle')
1731
+ return True
1732
+
1733
+ def scroll_text_from_swipe(self, touch):
1734
+ _scroll_timeout = (touch.time_update - touch.time_start) * 1000
1735
+ self._scroll_distance_x += abs(touch.dx)
1736
+ self._scroll_distance_y += abs(touch.dy)
1737
+ if not self._have_scrolled:
1738
+ # To be considered a scroll, touch should travel more than
1739
+ # scroll_distance in less than the scroll_timeout since touch_down
1740
+ if not (
1741
+ _scroll_timeout <= self.scroll_timeout
1742
+ and (
1743
+ (self._scroll_distance_x >= self.scroll_distance)
1744
+ or (self._scroll_distance_y >= self.scroll_distance)
1745
+ )
1746
+ ):
1747
+ # Distance isn't enough (yet) to consider it as a scroll
1748
+ if _scroll_timeout <= self.scroll_timeout:
1749
+ # Timeout is not reached, scroll is still enabled.
1750
+ return False
1751
+ else:
1752
+ self._enable_scroll = False
1753
+ self._cancel_update_selection(self._touch_down)
1754
+ return False
1755
+ # We have a scroll!
1756
+ self._have_scrolled = True
1757
+
1758
+ self.cancel_long_touch_event()
1759
+
1760
+ if self.multiline:
1761
+ max_scroll_y = max(0, self.minimum_height - self.height)
1762
+ self.scroll_y = min(
1763
+ max(0, self.scroll_y + touch.dy),
1764
+ max_scroll_y
1765
+ )
1766
+ else:
1767
+ max_scroll_x = self.get_max_scroll_x()
1768
+ self.scroll_x = min(
1769
+ max(0, self.scroll_x - touch.dx),
1770
+ max_scroll_x
1771
+ )
1772
+ self._trigger_update_graphics()
1773
+ self._position_handles()
1774
+ return True
1775
+
1776
+ def _handle_pressed(self, instance):
1777
+ self._hide_cut_copy_paste()
1778
+ from_, to_ = self._selection_from, self.selection_to
1779
+ if from_ > to_:
1780
+ self._selection_from, self._selection_to = to_, from_
1781
+
1782
+ def _handle_released(self, instance):
1783
+ if self._selection_from == self.selection_to:
1784
+ return
1785
+
1786
+ self._update_selection()
1787
+ self._show_cut_copy_paste(
1788
+ (
1789
+ self.x + instance.right
1790
+ if instance is self._handle_left
1791
+ else self.x + instance.x,
1792
+ self.y + instance.top + self.line_height
1793
+ ),
1794
+ EventLoop.window
1795
+ )
1796
+
1797
+ def _handle_move(self, instance, touch):
1798
+ if touch.grab_current != instance:
1799
+ return
1800
+ get_cursor = self.get_cursor_from_xy
1801
+ handle_right = self._handle_right
1802
+ handle_left = self._handle_left
1803
+ handle_middle = self._handle_middle
1804
+
1805
+ try:
1806
+ touch.push()
1807
+ touch.apply_transform_2d(self.to_widget)
1808
+ x, y = touch.pos
1809
+ finally:
1810
+ touch.pop()
1811
+
1812
+ cursor = get_cursor(
1813
+ x,
1814
+ y + instance._touch_diff + (self.line_height / 2)
1815
+ )
1816
+ self.cursor = cursor
1817
+
1818
+ if instance != touch.grab_current:
1819
+ return
1820
+
1821
+ if instance == handle_middle:
1822
+ self._position_handles(mode='middle')
1823
+ return
1824
+
1825
+ cindex = self.cursor_index()
1826
+
1827
+ if instance == handle_left:
1828
+ self._selection_from = cindex
1829
+ elif instance == handle_right:
1830
+ self._selection_to = cindex
1831
+ self._update_selection()
1832
+ self._trigger_update_graphics()
1833
+ self._trigger_position_handles()
1834
+
1835
+ def _position_handles(self, *args, **kwargs):
1836
+ if not self.text:
1837
+ return
1838
+ mode = kwargs.get('mode', 'both')
1839
+
1840
+ lh = self.line_height
1841
+
1842
+ handle_middle = self._handle_middle
1843
+ if handle_middle:
1844
+ hp_mid = self.cursor_pos
1845
+ pos = self.to_local(*hp_mid, relative=True)
1846
+ handle_middle.x = pos[0] - handle_middle.width / 2
1847
+ handle_middle.top = max(self.padding[3],
1848
+ min(self.height - self.padding[1],
1849
+ pos[1] - lh))
1850
+ if mode[0] == 'm':
1851
+ return
1852
+
1853
+ group = self.canvas.get_group('selection')
1854
+ if not group:
1855
+ return
1856
+
1857
+ EventLoop.window.remove_widget(self._handle_middle)
1858
+
1859
+ handle_left = self._handle_left
1860
+ if not handle_left:
1861
+ return
1862
+ hp_left = group[2].pos
1863
+ handle_left.pos = self.to_local(*hp_left, relative=True)
1864
+ handle_left.x -= handle_left.width
1865
+ handle_left.y -= handle_left.height
1866
+
1867
+ handle_right = self._handle_right
1868
+ last_rect = group[-1]
1869
+ hp_right = last_rect.pos[0], last_rect.pos[1]
1870
+ x, y = self.to_local(*hp_right, relative=True)
1871
+ handle_right.x = x + last_rect.size[0]
1872
+ handle_right.y = y - handle_right.height
1873
+
1874
+ def _hide_handles(self, win=None):
1875
+ win = win or EventLoop.window
1876
+ if win is None:
1877
+ return
1878
+ win.remove_widget(self._handle_right)
1879
+ win.remove_widget(self._handle_left)
1880
+ win.remove_widget(self._handle_middle)
1881
+
1882
+ def _show_handles(self, dt):
1883
+ if not self.use_handles or not self.text:
1884
+ return
1885
+
1886
+ win = EventLoop.window
1887
+
1888
+ handle_right = self._handle_right
1889
+ handle_left = self._handle_left
1890
+ if self._handle_left is None:
1891
+ self._handle_left = handle_left = Selector(
1892
+ source=self.handle_image_left,
1893
+ target=self,
1894
+ window=win,
1895
+ size_hint=(None, None),
1896
+ size=('45dp', '45dp'))
1897
+ handle_left.bind(on_press=self._handle_pressed,
1898
+ on_touch_move=self._handle_move,
1899
+ on_release=self._handle_released)
1900
+ self._handle_right = handle_right = Selector(
1901
+ source=self.handle_image_right,
1902
+ target=self,
1903
+ window=win,
1904
+ size_hint=(None, None),
1905
+ size=('45dp', '45dp'))
1906
+ handle_right.bind(on_press=self._handle_pressed,
1907
+ on_touch_move=self._handle_move,
1908
+ on_release=self._handle_released)
1909
+ else:
1910
+ if self._handle_left.parent:
1911
+ self._position_handles()
1912
+ return
1913
+ if not self.parent:
1914
+ return
1915
+
1916
+ self._trigger_position_handles()
1917
+ if self.selection_from != self.selection_to:
1918
+ self._handle_left.opacity = self._handle_right.opacity = 0
1919
+ win.add_widget(self._handle_left, canvas='after')
1920
+ win.add_widget(self._handle_right, canvas='after')
1921
+ anim = Animation(opacity=1, d=.4)
1922
+ anim.start(self._handle_right)
1923
+ anim.start(self._handle_left)
1924
+
1925
+ def _show_cut_copy_paste(
1926
+ self, pos, win, parent_changed=False, mode='', pos_in_window=False, *l
1927
+ ):
1928
+ """Show a bubble with cut copy and paste buttons"""
1929
+ if not self.use_bubble:
1930
+ return
1931
+
1932
+ bubble = self._bubble
1933
+ if bubble is None:
1934
+ self._bubble = bubble = TextInputCutCopyPaste(textinput=self)
1935
+ self.fbind('parent', self._show_cut_copy_paste, pos, win, True)
1936
+
1937
+ def hide_(*args):
1938
+ return self._hide_cut_copy_paste(win)
1939
+
1940
+ self.bind(
1941
+ focus=hide_,
1942
+ cursor_pos=hide_,
1943
+ )
1944
+ else:
1945
+ win.remove_widget(bubble)
1946
+ if not self.parent:
1947
+ return
1948
+ if parent_changed:
1949
+ return
1950
+
1951
+ # Search the position from the touch to the window
1952
+ lh, ls = self.line_height, self.line_spacing
1953
+
1954
+ x, y = pos
1955
+ t_pos = (x, y) if pos_in_window else self.to_window(x, y)
1956
+ bubble_size = bubble.size
1957
+ bubble_hw = bubble_size[0] / 2.
1958
+ win_size = win.size
1959
+ bubble_pos = (t_pos[0], t_pos[1] + inch(.25))
1960
+
1961
+ if (bubble_pos[0] - bubble_hw) < 0:
1962
+ # bubble beyond left of window
1963
+ if bubble_pos[1] > (win_size[1] - bubble_size[1]):
1964
+ # bubble above window height
1965
+ bubble_pos = (bubble_hw, (t_pos[1]) - (lh + ls + inch(.25)))
1966
+ bubble.arrow_pos = 'top_left'
1967
+ else:
1968
+ bubble_pos = (bubble_hw, bubble_pos[1])
1969
+ bubble.arrow_pos = 'bottom_left'
1970
+ elif (bubble_pos[0] + bubble_hw) > win_size[0]:
1971
+ # bubble beyond right of window
1972
+ if bubble_pos[1] > (win_size[1] - bubble_size[1]):
1973
+ # bubble above window height
1974
+ bubble_pos = (
1975
+ win_size[0] - bubble_hw,
1976
+ (t_pos[1]) - (lh + ls + inch(.25))
1977
+ )
1978
+ bubble.arrow_pos = 'top_right'
1979
+ else:
1980
+ bubble_pos = (win_size[0] - bubble_hw, bubble_pos[1])
1981
+ bubble.arrow_pos = 'bottom_right'
1982
+ else:
1983
+ if bubble_pos[1] > (win_size[1] - bubble_size[1]):
1984
+ # bubble above window height
1985
+ bubble_pos = (
1986
+ bubble_pos[0],
1987
+ (t_pos[1]) - (lh + ls + inch(.25))
1988
+ )
1989
+ bubble.arrow_pos = 'top_mid'
1990
+ else:
1991
+ bubble.arrow_pos = 'bottom_mid'
1992
+
1993
+ bubble_pos = self.to_widget(*bubble_pos, relative=True)
1994
+ bubble.center_x = bubble_pos[0]
1995
+ if bubble.arrow_pos[0] == 't':
1996
+ bubble.top = bubble_pos[1]
1997
+ else:
1998
+ bubble.y = bubble_pos[1]
1999
+ bubble.mode = mode
2000
+ Animation.cancel_all(bubble)
2001
+ bubble.opacity = 0
2002
+ win.add_widget(bubble, canvas='after')
2003
+ Animation(opacity=1, d=.225).start(bubble)
2004
+
2005
+ def _hide_cut_copy_paste(self, win=None):
2006
+ bubble = self._bubble
2007
+ if not bubble:
2008
+ return
2009
+
2010
+ bubble.hide()
2011
+
2012
+ #
2013
+ # Private
2014
+ #
2015
+
2016
+ @staticmethod
2017
+ def _reload_remove_observer(wr):
2018
+ """called when the textinput is deleted"""
2019
+ if wr in _textinput_list:
2020
+ _textinput_list.remove(wr)
2021
+
2022
+ def _on_textinput_focused(self, instance, value, *largs):
2023
+ win = EventLoop.window
2024
+ self.cancel_selection()
2025
+ self._hide_cut_copy_paste(win)
2026
+
2027
+ if value:
2028
+ if (
2029
+ not (self.readonly or self.disabled)
2030
+ or _is_desktop
2031
+ and self._keyboard_mode == 'system'
2032
+ ):
2033
+ self._trigger_cursor_reset()
2034
+ self._editable = True
2035
+ else:
2036
+ self._editable = False
2037
+ else:
2038
+ self._do_blink_cursor_ev.cancel()
2039
+ self._hide_handles(win)
2040
+
2041
+ def _ensure_clipboard(self):
2042
+ global Clipboard, CutBuffer
2043
+ if not Clipboard:
2044
+ from kivy.core.clipboard import Clipboard, CutBuffer
2045
+
2046
+ def cut(self):
2047
+ ''' Copy current selection to clipboard then delete it from TextInput.
2048
+
2049
+ .. versionadded:: 1.8.0
2050
+
2051
+ '''
2052
+ self._cut(self.selection_text)
2053
+
2054
+ def _cut(self, data):
2055
+ self._ensure_clipboard()
2056
+ Clipboard.copy(data)
2057
+ self.delete_selection()
2058
+
2059
+ def copy(self, data=''):
2060
+ ''' Copy the value provided in argument `data` into current clipboard.
2061
+ If data is not of type string it will be converted to string.
2062
+ If no data is provided then current selection if present is copied.
2063
+
2064
+ .. versionadded:: 1.8.0
2065
+
2066
+ '''
2067
+ self._ensure_clipboard()
2068
+ if data:
2069
+ return Clipboard.copy(data)
2070
+ if self.selection_text:
2071
+ return Clipboard.copy(self.selection_text)
2072
+
2073
+ def paste(self):
2074
+ ''' Insert text from system :class:`~kivy.core.clipboard.Clipboard`
2075
+ into the :class:`~kivy.uix.textinput.TextInput` at current cursor
2076
+ position.
2077
+
2078
+ .. versionadded:: 1.8.0
2079
+
2080
+ '''
2081
+ self._ensure_clipboard()
2082
+ data = Clipboard.paste()
2083
+ self.delete_selection()
2084
+ if not self.multiline:
2085
+ data = data.replace('\n', ' ')
2086
+ self.insert_text(data)
2087
+
2088
+ def _update_cutbuffer(self, *args):
2089
+ CutBuffer.set_cutbuffer(self.selection_text)
2090
+
2091
+ def _get_text_width(self, text, tab_width, _label_cached):
2092
+ """Return the width of a text, according to the current line options"""
2093
+ kw = self._get_line_options()
2094
+
2095
+ try:
2096
+ cid = u'{}\0{}\0{}'.format(text, self.password, kw)
2097
+ except UnicodeDecodeError:
2098
+ cid = '{}\0{}\0{}'.format(text, self.password, kw)
2099
+
2100
+ width = Cache_get('textinput.width', cid)
2101
+ if width:
2102
+ return width
2103
+ if not _label_cached:
2104
+ _label_cached = self._label_cached
2105
+ text = text.replace('\t', ' ' * tab_width)
2106
+ if not self.password:
2107
+ width = _label_cached.get_extents(text)[0]
2108
+ else:
2109
+ width = _label_cached.get_extents(
2110
+ self.password_mask * len(text))[0]
2111
+ Cache_append('textinput.width', cid, width)
2112
+ return width
2113
+
2114
+ def on_cursor_blink(self, instance, value):
2115
+ """trigger blink event reset to switch blinking while focused"""
2116
+ self._reset_cursor_blink()
2117
+
2118
+ def _do_blink_cursor(self, dt):
2119
+ if not self.cursor_blink:
2120
+ # ignore event if not triggered,
2121
+ # stop if cursor_blink value changed right now
2122
+ if self._do_blink_cursor_ev.is_triggered:
2123
+ self._do_blink_cursor_ev.cancel()
2124
+ # don't blink, make cursor visible
2125
+ self._cursor_blink = False
2126
+ return
2127
+
2128
+ # Callback for blinking the cursor.
2129
+ self._cursor_blink = not self._cursor_blink
2130
+
2131
+ def _reset_cursor_blink(self, *args):
2132
+ self._do_blink_cursor_ev.cancel()
2133
+ self._cursor_blink = False
2134
+ self._do_blink_cursor_ev()
2135
+
2136
+ def on_cursor(self, instance, value):
2137
+ """
2138
+ When the cursor is moved, reset cursor blinking to keep it showing,
2139
+ and update all the graphics.
2140
+ """
2141
+ if self.focus:
2142
+ self._trigger_cursor_reset()
2143
+ self._trigger_update_graphics()
2144
+
2145
+ def _delete_line(self, idx):
2146
+ """Delete current line, and fix cursor position"""
2147
+ assert idx < len(self._lines)
2148
+ self._lines_flags.pop(idx)
2149
+ self._lines_labels.pop(idx)
2150
+ self._lines.pop(idx)
2151
+ self.cursor = self.cursor
2152
+
2153
+ def _set_line_text(self, line_num, text):
2154
+ """Set current line with other text than the default one."""
2155
+ self._lines_labels[line_num] = self._create_line_label(text)
2156
+ self._lines[line_num] = text
2157
+
2158
+ def _trigger_refresh_line_options(self, *largs):
2159
+ if self._refresh_line_options_ev is not None:
2160
+ self._refresh_line_options_ev.cancel()
2161
+ else:
2162
+ self._refresh_line_options_ev = Clock.create_trigger(
2163
+ self._refresh_line_options, 0)
2164
+ self._refresh_line_options_ev()
2165
+
2166
+ def _refresh_line_options(self, *largs):
2167
+ self._line_options = None
2168
+ self._get_line_options()
2169
+ self._refresh_text_from_property()
2170
+ self._refresh_hint_text()
2171
+ self.cursor = self.get_cursor_from_index(len(self.text))
2172
+
2173
+ def _trigger_refresh_text(self, *largs):
2174
+ if len(largs) and largs[0] == self:
2175
+ largs = ()
2176
+ if self._refresh_text_from_property_ev is not None:
2177
+ self._refresh_text_from_property_ev.cancel()
2178
+ self._refresh_text_from_property_ev = Clock.schedule_once(
2179
+ lambda dt: self._refresh_text_from_property(*largs))
2180
+
2181
+ def _update_text_options(self, *largs):
2182
+ Cache_remove('textinput.width')
2183
+ self._trigger_refresh_text()
2184
+
2185
+ def _refresh_text_from_trigger(self, dt, *largs):
2186
+ self._refresh_text_from_property(*largs)
2187
+
2188
+ def _refresh_text_from_property(self, *largs):
2189
+ self._refresh_text(self.text, *largs)
2190
+
2191
+ def _refresh_text(self, text, *largs):
2192
+ """
2193
+ Refresh all the lines from a new text.
2194
+ By using cache in internal functions, this method should be fast.
2195
+ """
2196
+ mode = 'all'
2197
+ if len(largs) > 1:
2198
+ mode, start, finish, _lines, _lines_flags, len_lines = largs
2199
+ # start = max(0, start)
2200
+ cursor = None
2201
+ else:
2202
+ cursor = self.cursor_index()
2203
+ _lines, self._lines_flags = self._split_smart(text)
2204
+ _lines_labels = []
2205
+ _line_rects = []
2206
+ _create_label = self._create_line_label
2207
+
2208
+ for x in _lines:
2209
+ lbl = _create_label(x)
2210
+ _lines_labels.append(lbl)
2211
+ _line_rects.append(Rectangle(size=lbl.size))
2212
+
2213
+ if mode == 'all':
2214
+ self._lines_labels = _lines_labels
2215
+ self._lines_rects = _line_rects
2216
+ self._lines[:] = _lines
2217
+ elif mode == 'del':
2218
+ if finish > start:
2219
+ self._insert_lines(start, finish + 1, len_lines,
2220
+ _lines_flags, _lines, _lines_labels,
2221
+ _line_rects)
2222
+ elif mode == 'insert':
2223
+ self._insert_lines(start, finish + 1, len_lines, _lines_flags,
2224
+ _lines, _lines_labels, _line_rects)
2225
+
2226
+ min_line_ht = self._label_cached.get_extents('_')[1]
2227
+ # with markup texture can be of height `1`
2228
+ self.line_height = max(_lines_labels[0].height, min_line_ht)
2229
+ # self.line_spacing = 2
2230
+ # now, if the text change, maybe the cursor is not at the same place as
2231
+ # before. so, try to set the cursor on the good place
2232
+ row = self.cursor_row
2233
+ self.cursor = self.get_cursor_from_index(
2234
+ self.cursor_index() if cursor is None else cursor
2235
+ )
2236
+
2237
+ # if we back to a new line, reset the scroll, otherwise, the effect is
2238
+ # ugly
2239
+ if self.cursor_row != row:
2240
+ self.scroll_x = 0
2241
+ # with the new text don't forget to update graphics again
2242
+ self._trigger_update_graphics()
2243
+
2244
+ def _insert_lines(self, start, finish, len_lines, _lines_flags,
2245
+ _lines, _lines_labels, _line_rects):
2246
+ self_lines_flags = self._lines_flags
2247
+ _lins_flags = []
2248
+ _lins_flags.extend(self_lines_flags[:start])
2249
+ if len_lines:
2250
+ # if not inserting at first line then
2251
+ if start:
2252
+ # make sure line flags restored for first line
2253
+ # _split_smart assumes first line to be not a new line
2254
+ _lines_flags[0] = self_lines_flags[start]
2255
+ _lins_flags.extend(_lines_flags)
2256
+ _lins_flags.extend(self_lines_flags[finish:])
2257
+ self._lines_flags = _lins_flags
2258
+
2259
+ _lins_lbls = []
2260
+ _lins_lbls.extend(self._lines_labels[:start])
2261
+ if len_lines:
2262
+ _lins_lbls.extend(_lines_labels)
2263
+ _lins_lbls.extend(self._lines_labels[finish:])
2264
+ self._lines_labels = _lins_lbls
2265
+
2266
+ _lins_rcts = []
2267
+ _lins_rcts.extend(self._lines_rects[:start])
2268
+ if len_lines:
2269
+ _lins_rcts.extend(_line_rects)
2270
+ _lins_rcts.extend(self._lines_rects[finish:])
2271
+ self._lines_rects = _lins_rcts
2272
+
2273
+ _lins = []
2274
+ _lins.extend(self._lines[:start])
2275
+ if len_lines:
2276
+ _lins.extend(_lines)
2277
+ _lins.extend(self._lines[finish:])
2278
+ self._lines[:] = _lins
2279
+
2280
+ def _trigger_update_graphics(self, *largs):
2281
+ self._update_graphics_ev.cancel()
2282
+ self._update_graphics_ev()
2283
+
2284
+ def _update_graphics(self, *largs):
2285
+ """
2286
+ Update all the graphics according to the current internal values.
2287
+ """
2288
+
2289
+ # This is a little bit complex, because we have to :
2290
+ # - handle scroll_x
2291
+ # - handle padding
2292
+ # - create rectangle for the lines matching the viewport
2293
+ # - crop the texture coordinates to match the viewport
2294
+
2295
+ # This is the first step of graphics, the second is the selection.
2296
+
2297
+ self.canvas.clear()
2298
+
2299
+ line_height = self.line_height
2300
+ dy = line_height + self.line_spacing
2301
+
2302
+ # adjust view if the cursor is going outside the bounds
2303
+ scroll_x = self.scroll_x
2304
+ scroll_y = self.scroll_y
2305
+
2306
+ # draw labels
2307
+ if (
2308
+ not self._lines
2309
+ or (not self._lines[0] and len(self._lines) == 1)
2310
+ ):
2311
+ rects = self._hint_text_rects
2312
+ labels = self._hint_text_labels
2313
+ lines = self._hint_text_lines
2314
+ else:
2315
+ rects = self._lines_rects
2316
+ labels = self._lines_labels
2317
+ lines = self._lines
2318
+
2319
+ padding_left, padding_top, padding_right, padding_bottom = self.padding
2320
+ x = self.x + padding_left
2321
+ y = self.top - padding_top + scroll_y
2322
+ miny = self.y + padding_bottom
2323
+ maxy = self.top - padding_top
2324
+ halign = self.halign
2325
+ base_dir = self.base_direction
2326
+
2327
+ auto_halign_r = halign == 'auto' and base_dir and 'rtl' in base_dir
2328
+
2329
+ fst_visible_ln = None
2330
+ viewport_pos = scroll_x, 0
2331
+ for line_num, value in enumerate(lines):
2332
+ if miny < y < maxy + dy:
2333
+ if fst_visible_ln is None:
2334
+ fst_visible_ln = line_num
2335
+
2336
+ y = self._draw_line(
2337
+ value,
2338
+ line_num,
2339
+ labels[line_num],
2340
+ viewport_pos,
2341
+ line_height,
2342
+ miny,
2343
+ maxy,
2344
+ x,
2345
+ y,
2346
+ base_dir,
2347
+ halign,
2348
+ rects,
2349
+ auto_halign_r,
2350
+ )
2351
+ elif y <= miny:
2352
+ line_num -= 1
2353
+ break
2354
+
2355
+ y -= dy
2356
+
2357
+ if fst_visible_ln is not None:
2358
+ self._visible_lines_range = (fst_visible_ln, line_num + 1)
2359
+ else:
2360
+ self._visible_lines_range = 0, 0
2361
+
2362
+ self._update_graphics_selection()
2363
+
2364
+ def _draw_line(
2365
+ self,
2366
+ value,
2367
+ line_num,
2368
+ texture,
2369
+ viewport_pos,
2370
+ line_height,
2371
+ miny,
2372
+ maxy,
2373
+ x,
2374
+ y,
2375
+ base_dir,
2376
+ halign,
2377
+ rects,
2378
+ auto_halign_r,
2379
+ ):
2380
+ size = list(texture.size)
2381
+ texcoords = texture.tex_coords[:]
2382
+
2383
+ # compute coordinate
2384
+ padding_left, padding_top, padding_right, padding_bottom = self.padding
2385
+ viewport_width = self.width - padding_left - padding_right
2386
+ viewport_height = self.height - padding_top - padding_bottom
2387
+ texture_width, texture_height = size
2388
+ original_height, original_width = tch, tcw = texcoords[1:3]
2389
+
2390
+ # adjust size/texcoord according to viewport
2391
+ if viewport_pos:
2392
+ tcx, tcy = viewport_pos
2393
+ tcx = tcx / texture_width * original_width
2394
+ tcy = tcy / texture_height * original_height
2395
+
2396
+ else:
2397
+ tcx, tcy = 0, 0
2398
+
2399
+ if texture_width * (1 - tcx) < viewport_width:
2400
+ tcw = tcw - tcx
2401
+ texture_width = tcw * texture_width
2402
+ elif viewport_width < texture_width:
2403
+ tcw = (viewport_width / texture_width) * tcw
2404
+ texture_width = viewport_width
2405
+
2406
+ if viewport_height < texture_height:
2407
+ tch = (viewport_height / texture_height) * tch
2408
+ texture_height = viewport_height
2409
+
2410
+ # cropping
2411
+ if y > maxy:
2412
+ viewport_height = (maxy - y + line_height)
2413
+ tch = (viewport_height / line_height) * original_height
2414
+ tcy = original_height - tch
2415
+ texture_height = viewport_height
2416
+ if y - line_height < miny:
2417
+ diff = miny - (y - line_height)
2418
+ y += diff
2419
+ viewport_height = line_height - diff
2420
+ tch = (viewport_height / line_height) * original_height
2421
+ texture_height = viewport_height
2422
+
2423
+ if tcw < 0:
2424
+ # nothing to show
2425
+ return y
2426
+
2427
+ top_left_corner = tcx, tcy + tch
2428
+ top_right_corner = tcx + tcw, tcy + tch
2429
+ bottom_right_corner = tcx + tcw, tcy
2430
+ bottom_left_corner = tcx, tcy
2431
+
2432
+ texcoords = (
2433
+ top_left_corner
2434
+ + top_right_corner
2435
+ + bottom_right_corner
2436
+ + bottom_left_corner
2437
+ )
2438
+
2439
+ # Horizontal alignment
2440
+ xoffset = 0
2441
+ if not base_dir:
2442
+ base_dir = self._resolved_base_dir = Label.find_base_direction(value) # noqa
2443
+ if base_dir and halign == 'auto':
2444
+ auto_halign_r = 'rtl' in base_dir
2445
+ if halign == 'center':
2446
+ xoffset = int((viewport_width - texture_width) / 2.)
2447
+ elif halign == 'right' or auto_halign_r:
2448
+ xoffset = max(0, int(viewport_width - texture_width))
2449
+
2450
+ # add rectangle
2451
+ rect = rects[line_num]
2452
+ rect.pos = int(xoffset + x), int(y - line_height)
2453
+ rect.size = texture_width, texture_height
2454
+ rect.texture = texture
2455
+ rect.tex_coords = texcoords
2456
+ # useful to debug rectangle sizes
2457
+ # self.canvas.add(Color(0, .5, 0, .5, mode='rgba'))
2458
+ # self.canvas.add(Rectangle(pos=rect.pos, size=rect.size))
2459
+ # self.canvas.add(Color())
2460
+ self.canvas.add(rect)
2461
+
2462
+ return y
2463
+
2464
+ def _update_graphics_selection(self):
2465
+ if not self._selection:
2466
+ return
2467
+
2468
+ # local references to avoid dot lookups later
2469
+ padding_left, padding_top, padding_right, padding_bottom = self.padding
2470
+ rects = self._lines_rects
2471
+ label_cached = self._label_cached
2472
+ lines = self._lines
2473
+ tab_width = self.tab_width
2474
+ top = self.top
2475
+ get_text_width = self._get_text_width
2476
+ get_cursor_from_index = self.get_cursor_from_index
2477
+ draw_selection = self._draw_selection
2478
+ canvas_add = self.canvas.add
2479
+ selection_color = self.selection_color
2480
+
2481
+ # selection borders
2482
+ a, b = sorted((self._selection_from, self._selection_to))
2483
+ selection_start_col, selection_start_row = get_cursor_from_index(a)
2484
+ selection_end_col, selection_end_row = get_cursor_from_index(b)
2485
+
2486
+ dy = self.line_height + self.line_spacing
2487
+ x = self.x
2488
+ y = top - padding_top + self.scroll_y - selection_start_row * dy
2489
+ width = self.width
2490
+
2491
+ miny = self.y + padding_bottom
2492
+ maxy = top - padding_top + dy
2493
+
2494
+ self.canvas.remove_group('selection')
2495
+ first_visible_line = math.floor(self.scroll_y / dy)
2496
+ last_visible_line = math.ceil((self.scroll_y + maxy - miny) / dy)
2497
+ width_minus_padding = width - (padding_right + padding_left)
2498
+
2499
+ for line_num, rect in enumerate(
2500
+ islice(
2501
+ rects,
2502
+ max(selection_start_row, first_visible_line),
2503
+ min(selection_end_row + 1, last_visible_line - 1),
2504
+ ),
2505
+ start=max(selection_start_row, first_visible_line)
2506
+ ):
2507
+ draw_selection(
2508
+ rect.pos,
2509
+ rect.size,
2510
+ line_num,
2511
+ (selection_start_col, selection_start_row),
2512
+ (selection_end_col, selection_end_row),
2513
+ lines,
2514
+ get_text_width,
2515
+ tab_width,
2516
+ label_cached,
2517
+ width_minus_padding,
2518
+ padding_left,
2519
+ padding_right,
2520
+ x,
2521
+ canvas_add,
2522
+ selection_color
2523
+ )
2524
+ self._position_handles('both')
2525
+
2526
+ def _draw_selection(
2527
+ self,
2528
+ pos,
2529
+ size,
2530
+ line_num,
2531
+ selection_start,
2532
+ selection_end,
2533
+ lines,
2534
+ get_text_width,
2535
+ tab_width,
2536
+ label_cached,
2537
+ width_minus_padding,
2538
+ padding_left,
2539
+ padding_right,
2540
+ x,
2541
+ canvas_add,
2542
+ selection_color
2543
+ ):
2544
+ selection_start_col, selection_start_row = selection_start
2545
+ selection_end_col, selection_end_row = selection_end
2546
+
2547
+ # Draw the current selection on the widget.
2548
+ if not selection_start_row <= line_num <= selection_end_row:
2549
+ return
2550
+ x, y = pos
2551
+ w, h = size
2552
+ beg = x
2553
+ end = x + w
2554
+
2555
+ if line_num == selection_start_row:
2556
+ line = lines[line_num]
2557
+ beg -= self.scroll_x
2558
+ beg += get_text_width(
2559
+ line[:selection_start_col],
2560
+ tab_width,
2561
+ label_cached
2562
+ )
2563
+
2564
+ if line_num == selection_end_row:
2565
+ line = lines[line_num]
2566
+ end = (x - self.scroll_x) + get_text_width(
2567
+ line[:selection_end_col],
2568
+ tab_width,
2569
+ label_cached
2570
+ )
2571
+
2572
+ beg = boundary(beg, x, x + width_minus_padding)
2573
+ end = boundary(end, x, x + width_minus_padding)
2574
+ if beg == end:
2575
+ return
2576
+
2577
+ canvas_add(Color(*selection_color, group='selection'))
2578
+ canvas_add(
2579
+ Rectangle(
2580
+ pos=(beg, y),
2581
+ size=(end - beg, h),
2582
+ group='selection'
2583
+ )
2584
+ )
2585
+
2586
+ def on_size(self, instance, value):
2587
+ # if the size change, we might do invalid scrolling / text split
2588
+ # size the text maybe be put after size_hint have been resolved.
2589
+ self._trigger_refresh_text()
2590
+ self._refresh_hint_text()
2591
+ self.scroll_x = self.scroll_y = 0
2592
+
2593
+ def _get_row_width(self, row):
2594
+ # Get the pixel width of the given row.
2595
+ _labels = self._lines_labels
2596
+ if row < len(_labels):
2597
+ return _labels[row].width
2598
+ return 0
2599
+
2600
+ def _get_cursor_pos(self):
2601
+ # return the current cursor x/y from the row/col
2602
+ dy = self.line_height + self.line_spacing
2603
+ padding_left = self.padding[0]
2604
+ padding_top = self.padding[1]
2605
+ padding_right = self.padding[2]
2606
+ left = self.x + padding_left
2607
+ top = self.top - padding_top
2608
+ y = top + self.scroll_y
2609
+ y -= self.cursor_row * dy
2610
+
2611
+ # Horizontal alignment
2612
+ halign = self.halign
2613
+ viewport_width = self.width - padding_left - padding_right
2614
+ cursor_offset = self.cursor_offset()
2615
+ base_dir = self.base_direction or self._resolved_base_dir
2616
+ auto_halign_r = halign == 'auto' and base_dir and 'rtl' in base_dir
2617
+ if halign == 'center':
2618
+ row_width = self._get_row_width(self.cursor_row)
2619
+ x = (
2620
+ left
2621
+ + max(0, (viewport_width - row_width) // 2)
2622
+ + cursor_offset
2623
+ - self.scroll_x
2624
+ )
2625
+ elif halign == 'right' or auto_halign_r:
2626
+ row_width = self._get_row_width(self.cursor_row)
2627
+ x = (
2628
+ left
2629
+ + max(0, viewport_width - row_width)
2630
+ + cursor_offset
2631
+ - self.scroll_x
2632
+ )
2633
+ else:
2634
+ x = left + cursor_offset - self.scroll_x
2635
+
2636
+ return x, y
2637
+
2638
+ def _get_cursor_visual_height(self):
2639
+ # Return the height of the cursor's visible part
2640
+ _, cy = map(int, self.cursor_pos)
2641
+ max_y = self.top - self.padding[1]
2642
+ min_y = self.y + self.padding[3]
2643
+
2644
+ lh = self.line_height
2645
+ if cy > max_y:
2646
+ return lh - min(lh, cy - max_y)
2647
+ else:
2648
+ return min(lh, max(0, cy - min_y))
2649
+
2650
+ def _get_cursor_visual_pos(self):
2651
+ # Return the position of the cursor's top visible point
2652
+ cx, cy = map(int, self.cursor_pos)
2653
+ max_y = self.top - self.padding[3]
2654
+ return [cx, min(max_y, cy)]
2655
+
2656
+ def _get_line_options(self):
2657
+ # Get or create line options, to be used for Label creation
2658
+ if self._line_options is None:
2659
+ self._line_options = kw = {
2660
+ 'font_size': self.font_size,
2661
+ 'font_name': self.font_name,
2662
+ 'font_context': self.font_context,
2663
+ 'font_family': self.font_family,
2664
+ 'text_language': self.text_language,
2665
+ 'base_direction': self.base_direction,
2666
+ 'anchor_x': 'left',
2667
+ 'anchor_y': 'top',
2668
+ 'padding_x': 0,
2669
+ 'padding_y': 0,
2670
+ 'padding': (0, 0)
2671
+ }
2672
+ self._label_cached = Label(**kw)
2673
+ return self._line_options
2674
+
2675
+ def _create_line_label(self, text, hint=False):
2676
+ # Create a label from a text, using line options
2677
+ ntext = text.replace(u'\n', u'').replace(u'\t', u' ' * self.tab_width)
2678
+
2679
+ if self.password and not hint: # Don't replace hint_text with *
2680
+ ntext = self.password_mask * len(ntext)
2681
+
2682
+ kw = self._get_line_options()
2683
+ cid = '%s\0%s' % (ntext, str(kw))
2684
+ texture = Cache_get('textinput.label', cid)
2685
+
2686
+ if texture is None:
2687
+ # FIXME right now, we can't render very long line...
2688
+ # if we move on "VBO" version as fallback, we won't need to
2689
+ # do this. try to find the maximum text we can handle
2690
+ label = None
2691
+ label_len = len(ntext)
2692
+ ld = None
2693
+
2694
+ # check for blank line
2695
+ if not ntext:
2696
+ texture = Texture.create(size=(1, 1))
2697
+ Cache_append('textinput.label', cid, texture)
2698
+ return texture
2699
+
2700
+ while True:
2701
+ try:
2702
+ label = Label(text=ntext[:label_len], **kw)
2703
+ label.refresh()
2704
+ if ld is not None and ld > 2:
2705
+ ld //= 2
2706
+ label_len += ld
2707
+ else:
2708
+ break
2709
+
2710
+ except:
2711
+ # exception happen when we tried to render the text
2712
+ # reduce it...
2713
+ if ld is None:
2714
+ ld = len(ntext)
2715
+ ld //= 2
2716
+ if ld < 2 and label_len:
2717
+ label_len -= 1
2718
+ label_len -= ld
2719
+ continue
2720
+
2721
+ # ok, we found it.
2722
+ texture = label.texture
2723
+ Cache_append('textinput.label', cid, texture)
2724
+ return texture
2725
+
2726
+ _tokenize_delimiters = u' .,:;!?\r\t'
2727
+
2728
+ def _tokenize(self, text):
2729
+ # Tokenize a text string from some delimiters
2730
+ if text is None:
2731
+ return
2732
+ delimiters = self._tokenize_delimiters
2733
+ old_index = 0
2734
+ prev_char = ''
2735
+ for index, char in enumerate(text):
2736
+ if char not in delimiters:
2737
+ if char != u'\n':
2738
+ if index > 0 and (prev_char in delimiters):
2739
+ if old_index < index:
2740
+ yield text[old_index:index]
2741
+ old_index = index
2742
+ else:
2743
+ if old_index < index:
2744
+ yield text[old_index:index]
2745
+ yield text[index:index + 1]
2746
+ old_index = index + 1
2747
+ prev_char = char
2748
+ yield text[old_index:]
2749
+
2750
+ def _split_smart(self, text):
2751
+ """
2752
+ Do a "smart" split. If not multiline, or if wrap is set,
2753
+ we are not doing smart split, just a split on line break.
2754
+ Otherwise, we are trying to split as soon as possible, to prevent
2755
+ overflow on the widget.
2756
+ """
2757
+
2758
+ # depend of the options, split the text on line, or word
2759
+ if not self.multiline or not self.do_wrap:
2760
+ lines = text.split(u'\n')
2761
+ lines_flags = [0] + [FL_IS_LINEBREAK] * (len(lines) - 1)
2762
+ return lines, lines_flags
2763
+
2764
+ # no autosize, do wordwrap.
2765
+ x = flags = 0
2766
+ line = []
2767
+ lines = []
2768
+ lines_flags = []
2769
+ _join = u''.join
2770
+ lines_append, lines_flags_append = lines.append, lines_flags.append
2771
+ padding_left = self.padding[0]
2772
+ padding_right = self.padding[2]
2773
+ width = self.width - padding_left - padding_right
2774
+ text_width = self._get_text_width
2775
+ _tab_width, _label_cached = self.tab_width, self._label_cached
2776
+
2777
+ # try to add each word on current line.
2778
+ words_widths = {}
2779
+ for word in self._tokenize(text):
2780
+ is_newline = (word == u'\n')
2781
+ try:
2782
+ w = words_widths[word]
2783
+ except KeyError:
2784
+ w = text_width(word, _tab_width, _label_cached)
2785
+ words_widths[word] = w
2786
+ # if we have more than the width, or if it's a newline,
2787
+ # push the current line, and create a new one
2788
+ if (x + w > width and line) or is_newline:
2789
+ lines_append(_join(line))
2790
+ lines_flags_append(flags)
2791
+ flags = 0
2792
+ line = []
2793
+ x = 0
2794
+ if is_newline:
2795
+ flags |= FL_IS_LINEBREAK
2796
+ elif width >= 1 and w > width:
2797
+ while w > width:
2798
+ split_width = split_pos = 0
2799
+ # split the word
2800
+ for c in word:
2801
+ try:
2802
+ cw = words_widths[c]
2803
+ except KeyError:
2804
+ cw = text_width(c, _tab_width, _label_cached)
2805
+ words_widths[c] = cw
2806
+ if split_width + cw > width:
2807
+ break
2808
+ split_width += cw
2809
+ split_pos += 1
2810
+ if split_width == split_pos == 0:
2811
+ # can't fit the word in, give up
2812
+ break
2813
+ lines_append(word[:split_pos])
2814
+ lines_flags_append(flags)
2815
+ flags = FL_IS_WORDBREAK
2816
+ word = word[split_pos:]
2817
+ w -= split_width
2818
+ x = w
2819
+ line.append(word)
2820
+ else:
2821
+ x += w
2822
+ line.append(word)
2823
+ if line or flags & FL_IS_LINEBREAK:
2824
+ lines_append(_join(line))
2825
+ lines_flags_append(flags)
2826
+
2827
+ return lines, lines_flags
2828
+
2829
+ def _key_down(self, key, repeat=False):
2830
+ displayed_str, internal_str, internal_action, scale = key
2831
+
2832
+ # handle deletion
2833
+ if (
2834
+ self._selection
2835
+ and internal_action in (None, 'del', 'backspace', 'enter')
2836
+ and (internal_action != 'enter' or self.multiline)
2837
+ ):
2838
+ self.delete_selection()
2839
+
2840
+ elif internal_action == 'del':
2841
+ # Move cursor one char to the right. If that was successful,
2842
+ # do a backspace (effectively deleting char right of cursor)
2843
+ cursor = self.cursor
2844
+ self.do_cursor_movement('cursor_right')
2845
+ if cursor != self.cursor:
2846
+ self.do_backspace(mode='del')
2847
+
2848
+ elif internal_action == 'backspace':
2849
+ self.do_backspace()
2850
+
2851
+ # handle action keys and text insertion
2852
+ if internal_action is None:
2853
+ self.insert_text(displayed_str)
2854
+
2855
+ elif internal_action in ('shift', 'shift_L', 'shift_R'):
2856
+ if not self._selection:
2857
+ self._selection_from = self._selection_to = self.cursor_index()
2858
+ self._selection = True
2859
+ self._selection_finished = False
2860
+
2861
+ elif internal_action == 'ctrl_L':
2862
+ self._ctrl_l = True
2863
+
2864
+ elif internal_action == 'ctrl_R':
2865
+ self._ctrl_r = True
2866
+
2867
+ elif internal_action == 'alt_L':
2868
+ self._alt_l = True
2869
+
2870
+ elif internal_action == 'alt_R':
2871
+ self._alt_r = True
2872
+
2873
+ elif internal_action.startswith('cursor_'):
2874
+ cc, cr = self.cursor
2875
+ self.do_cursor_movement(
2876
+ internal_action,
2877
+ self._ctrl_l or self._ctrl_r,
2878
+ self._alt_l or self._alt_r
2879
+ )
2880
+ if self._selection and not self._selection_finished:
2881
+ self._selection_to = self.cursor_index()
2882
+ self._update_selection()
2883
+ else:
2884
+ self.cancel_selection()
2885
+
2886
+ elif internal_action == 'enter':
2887
+ if self.multiline:
2888
+ self.insert_text(u'\n')
2889
+ else:
2890
+ self.dispatch('on_text_validate')
2891
+ if self.text_validate_unfocus:
2892
+ self.focus = False
2893
+
2894
+ elif internal_action == 'escape':
2895
+ self.focus = False
2896
+
2897
+ def _key_up(self, key, repeat=False):
2898
+ displayed_str, internal_str, internal_action, scale = key
2899
+ if internal_action in ('shift', 'shift_L', 'shift_R'):
2900
+ if self._selection:
2901
+ self._update_selection(True)
2902
+ elif internal_action == 'ctrl_L':
2903
+ self._ctrl_l = False
2904
+ elif internal_action == 'ctrl_R':
2905
+ self._ctrl_r = False
2906
+ elif internal_action == 'alt_L':
2907
+ self._alt_l = False
2908
+ elif internal_action == 'alt_R':
2909
+ self._alt_r = False
2910
+
2911
+ def keyboard_on_key_down(self, window, keycode, text, modifiers):
2912
+ key, _ = keycode
2913
+ win = EventLoop.window
2914
+
2915
+ # This allows *either* ctrl *or* cmd, but not both.
2916
+ modifiers = set(modifiers) - {'capslock', 'numlock'}
2917
+ is_shortcut = (
2918
+ modifiers == {'ctrl'}
2919
+ or _is_osx and modifiers == {'meta'}
2920
+ )
2921
+ is_interesting_key = key in self.interesting_keys.keys()
2922
+
2923
+ if (
2924
+ not self.write_tab
2925
+ and super().keyboard_on_key_down(window, keycode, text, modifiers)
2926
+ ):
2927
+ return True
2928
+
2929
+ if text and is_shortcut and not is_interesting_key:
2930
+ self._handle_shortcut(key)
2931
+
2932
+ elif self._editable and text and not is_interesting_key:
2933
+ self._hide_handles(win)
2934
+ self._hide_cut_copy_paste(win)
2935
+ win.remove_widget(self._handle_middle)
2936
+
2937
+ # check for command modes
2938
+ # we use \x01INFO\x02 to get info from IME on mobiles
2939
+ # pygame seems to pass \x01 as the unicode for ctrl+a
2940
+ # checking for modifiers ensures conflict resolution.
2941
+
2942
+ first_char = ord(text[0])
2943
+ if not modifiers and first_char == 1:
2944
+ self._command_mode = True
2945
+ self._command = ''
2946
+ if not modifiers and first_char == 2:
2947
+ self._command_mode = False
2948
+ self._command = self._command[1:]
2949
+
2950
+ if self._command_mode:
2951
+ self._command += text
2952
+ return
2953
+
2954
+ _command = self._command
2955
+ if _command and first_char == 2:
2956
+ self._handle_command(_command)
2957
+ return
2958
+
2959
+ else:
2960
+ if EventLoop.window.managed_textinput:
2961
+ # we expect to get managed key input via on_textinput
2962
+ return
2963
+ if self._selection:
2964
+ self.delete_selection()
2965
+ self.insert_text(text)
2966
+ # self._recalc_size()
2967
+ return
2968
+
2969
+ if is_interesting_key:
2970
+ self._hide_cut_copy_paste(win)
2971
+ self._hide_handles(win)
2972
+
2973
+ if key == 27: # escape
2974
+ self.focus = False
2975
+ return True
2976
+ elif key == 9: # tab
2977
+ self.delete_selection()
2978
+ self.insert_text(u'\t')
2979
+ return True
2980
+
2981
+ k = self.interesting_keys.get(key)
2982
+ if k:
2983
+ key = (None, None, k, 1)
2984
+ self._key_down(key)
2985
+
2986
+ def _handle_command(self, command):
2987
+ from_undo = True
2988
+ command, data = command.split(':')
2989
+ self._command = ''
2990
+ if self._selection:
2991
+ self.delete_selection()
2992
+ if command == 'DEL':
2993
+ count = int(data)
2994
+ if not count:
2995
+ self.delete_selection(from_undo=True)
2996
+ end = self.cursor_index()
2997
+ self._selection_from = max(end - count, 0)
2998
+ self._selection_to = end
2999
+ self._selection = True
3000
+ self.delete_selection(from_undo=True)
3001
+ return
3002
+ elif command == 'INSERT':
3003
+ self.insert_text(data, from_undo)
3004
+ elif command == 'INSERTN':
3005
+ from_undo = False
3006
+ self.insert_text(data, from_undo)
3007
+ elif command == 'SELWORD':
3008
+ self.dispatch('on_double_tap')
3009
+ elif command == 'SEL':
3010
+ if data == '0':
3011
+ Clock.schedule_once(lambda dt: self.cancel_selection())
3012
+ elif command == 'CURCOL':
3013
+ self.cursor = int(data), self.cursor_row
3014
+
3015
+ def _handle_shortcut(self, key):
3016
+ # actions that can be done in readonly
3017
+ if key == ord('a'): # select all
3018
+ self.select_all()
3019
+ elif key == ord('c'): # copy selection
3020
+ self.copy()
3021
+
3022
+ if not self._editable:
3023
+ return
3024
+
3025
+ # actions that can be done only if editable
3026
+ if key == ord('x'): # cut selection
3027
+ self._cut(self.selection_text)
3028
+ elif key == ord('v'): # paste clipboard content
3029
+ self.paste()
3030
+ elif key == ord('z'): # undo
3031
+ self.do_undo()
3032
+ elif key == ord('r'): # redo
3033
+ self.do_redo()
3034
+
3035
+ def keyboard_on_key_up(self, window, keycode):
3036
+ key = keycode[0]
3037
+ k = self.interesting_keys.get(key)
3038
+ if k:
3039
+ key = (None, None, k, 1)
3040
+ self._key_up(key)
3041
+
3042
+ def keyboard_on_textinput(self, window, text):
3043
+ if self._selection:
3044
+ self.delete_selection()
3045
+ self.insert_text(text, False)
3046
+
3047
+ # current IME composition in progress by the IME system, or '' if nothing
3048
+ _ime_composition = StringProperty('')
3049
+ # cursor position of last IME event
3050
+ _ime_cursor = ListProperty(None, allownone=True)
3051
+
3052
+ def _bind_keyboard(self):
3053
+ super()._bind_keyboard()
3054
+ Window.bind(on_textedit=self.window_on_textedit)
3055
+
3056
+ def _unbind_keyboard(self):
3057
+ super()._unbind_keyboard()
3058
+ Window.unbind(on_textedit=self.window_on_textedit)
3059
+
3060
+ def window_on_textedit(self, window, ime_input):
3061
+ text_lines = self._lines or ['']
3062
+ if self._ime_composition:
3063
+ pcc, pcr = self._ime_cursor
3064
+ text = text_lines[pcr]
3065
+ len_ime = len(self._ime_composition)
3066
+ if text[pcc - len_ime:pcc] == self._ime_composition: # always?
3067
+ remove_old_ime_text = text[:pcc - len_ime] + text[pcc:]
3068
+ ci = self.cursor_index()
3069
+ self._refresh_text_from_property(
3070
+ "insert",
3071
+ *self._get_line_from_cursor(pcr, remove_old_ime_text)
3072
+ )
3073
+ self.cursor = self.get_cursor_from_index(ci - len_ime)
3074
+
3075
+ if ime_input:
3076
+ if self._selection:
3077
+ self.delete_selection()
3078
+ cc, cr = self.cursor
3079
+ text = text_lines[cr]
3080
+ new_text = text[:cc] + ime_input + text[cc:]
3081
+ self._refresh_text_from_property(
3082
+ "insert", *self._get_line_from_cursor(cr, new_text)
3083
+ )
3084
+ self.cursor = self.get_cursor_from_index(
3085
+ self.cursor_index() + len(ime_input)
3086
+ )
3087
+ self._ime_composition = ime_input
3088
+ self._ime_cursor = self.cursor
3089
+
3090
+ def on__hint_text(self, instance, value):
3091
+ self._refresh_hint_text()
3092
+
3093
+ def _refresh_hint_text(self):
3094
+ _lines, self._hint_text_flags = self._split_smart(self.hint_text)
3095
+ _hint_text_labels = []
3096
+ _hint_text_rects = []
3097
+ _create_label = self._create_line_label
3098
+
3099
+ for x in _lines:
3100
+ lbl = _create_label(x, hint=True)
3101
+ _hint_text_labels.append(lbl)
3102
+ _hint_text_rects.append(Rectangle(size=lbl.size))
3103
+
3104
+ self._hint_text_lines[:] = _lines
3105
+ self._hint_text_labels = _hint_text_labels
3106
+ self._hint_text_rects = _hint_text_rects
3107
+
3108
+ # Remember to update graphics
3109
+ self._trigger_update_graphics()
3110
+
3111
+ #
3112
+ # Properties
3113
+ #
3114
+
3115
+ _lines = ListProperty([])
3116
+ _hint_text_lines = ListProperty([])
3117
+ _editable = BooleanProperty(True)
3118
+ _insert_int_pat = re.compile(u'^-?[0-9]*$')
3119
+ _insert_float_pat = re.compile(u'^-?[0-9]*\\.?[0-9]*$')
3120
+ _cursor_blink = BooleanProperty(False)
3121
+ _cursor_visual_pos = AliasProperty(
3122
+ _get_cursor_visual_pos, None, bind=['cursor_pos']
3123
+ )
3124
+ _cursor_visual_height = AliasProperty(
3125
+ _get_cursor_visual_height, None, bind=['cursor_pos']
3126
+ )
3127
+
3128
+ readonly = BooleanProperty(False)
3129
+ '''If True, the user will not be able to change the content of a textinput.
3130
+
3131
+ .. versionadded:: 1.3.0
3132
+
3133
+ :attr:`readonly` is a :class:`~kivy.properties.BooleanProperty` and
3134
+ defaults to False.
3135
+ '''
3136
+
3137
+ text_validate_unfocus = BooleanProperty(True)
3138
+ '''If True, the :meth:`TextInput.on_text_validate` event will unfocus the
3139
+ widget, therefore make it stop listening to the keyboard. When disabled,
3140
+ the :meth:`TextInput.on_text_validate` event can be fired multiple times
3141
+ as the result of TextInput keeping the focus enabled.
3142
+
3143
+ .. versionadded:: 1.10.1
3144
+
3145
+ :attr:`text_validate_unfocus` is
3146
+ a :class:`~kivy.properties.BooleanProperty` and defaults to True.
3147
+ '''
3148
+
3149
+ multiline = BooleanProperty(True)
3150
+ '''If True, the widget will be able show multiple lines of text. If False,
3151
+ the "enter" keypress will defocus the textinput instead of adding a new
3152
+ line.
3153
+
3154
+ :attr:`multiline` is a :class:`~kivy.properties.BooleanProperty` and
3155
+ defaults to True.
3156
+ '''
3157
+
3158
+ do_wrap = BooleanProperty(True)
3159
+ '''If True, and the text is multiline, then lines larger than the width of
3160
+ the widget will wrap around to the next line, avoiding the need for
3161
+ horizontal scrolling. Disabling this option ensure one line is always
3162
+ displayed as one line.
3163
+
3164
+ :attr:`do_wrap` is a :class:`~kivy.properties.BooleanProperty` and defaults
3165
+ to True.
3166
+
3167
+ versionadded:: 2.1.0
3168
+ '''
3169
+
3170
+ password = BooleanProperty(False)
3171
+ '''If True, the widget will display its characters as the character
3172
+ set in :attr:`password_mask`.
3173
+
3174
+ .. versionadded:: 1.2.0
3175
+
3176
+ :attr:`password` is a :class:`~kivy.properties.BooleanProperty` and
3177
+ defaults to False.
3178
+ '''
3179
+
3180
+ password_mask = StringProperty('*')
3181
+ '''Sets the character used to mask the text when :attr:`password` is True.
3182
+
3183
+ .. versionadded:: 1.10.0
3184
+
3185
+ :attr:`password_mask` is a :class:`~kivy.properties.StringProperty` and
3186
+ defaults to `'*'`.
3187
+ '''
3188
+
3189
+ cursor_blink = BooleanProperty(True)
3190
+ '''This property is used to set whether the graphic cursor should blink
3191
+ or not.
3192
+
3193
+ .. versionchanged:: 1.10.1
3194
+ `cursor_blink` has been refactored to enable switching the blinking
3195
+ on/off and the previous behavior has been moved to a private
3196
+ `_cursor_blink` property. The previous default value `False` has been
3197
+ changed to `True`.
3198
+
3199
+ :attr:`cursor_blink` is a :class:`~kivy.properties.BooleanProperty` and
3200
+ defaults to True.
3201
+ '''
3202
+
3203
+ def _get_cursor(self):
3204
+ return self._cursor
3205
+
3206
+ def _set_cursor(self, pos):
3207
+ if not self._lines:
3208
+ self._trigger_refresh_text()
3209
+ return
3210
+ l = self._lines
3211
+ cr = boundary(pos[1], 0, len(l) - 1)
3212
+ cc = boundary(pos[0], 0, len(l[cr]))
3213
+ cursor = cc, cr
3214
+
3215
+ # adjust scrollview to ensure that the cursor will be always inside our
3216
+ # viewport.
3217
+ self._adjust_viewport(cc, cr)
3218
+
3219
+ if self._cursor == cursor:
3220
+ return
3221
+
3222
+ self._cursor = cursor
3223
+ return True
3224
+
3225
+ @triggered(timeout=-1)
3226
+ def _adjust_viewport(self, cc, cr):
3227
+ padding_left = self.padding[0]
3228
+ padding_right = self.padding[2]
3229
+ viewport_width = self.width - padding_left - padding_right
3230
+ sx = self.scroll_x
3231
+ base_dir = self.base_direction or self._resolved_base_dir
3232
+ auto_halign_r = (
3233
+ self.halign == 'auto'
3234
+ and base_dir
3235
+ and 'rtl' in base_dir
3236
+ )
3237
+
3238
+ offset = self.cursor_offset()
3239
+ row_width = self._get_row_width(self.cursor_row)
3240
+
3241
+ # if offset is outside the current bounds, readjust
3242
+ if offset - sx >= viewport_width:
3243
+ self.scroll_x = offset - viewport_width
3244
+ elif offset < sx + 1:
3245
+ self.scroll_x = offset
3246
+
3247
+ # Avoid right/center horizontal alignment issues if the viewport is at
3248
+ # the end of the line, if not multiline.
3249
+ viewport_scroll_x = row_width - viewport_width
3250
+ if (
3251
+ not self.multiline
3252
+ and offset >= viewport_scroll_x
3253
+ and self.scroll_x >= viewport_scroll_x
3254
+ and (
3255
+ self.halign == "center"
3256
+ or self.halign == "right"
3257
+ or auto_halign_r
3258
+ )
3259
+ ):
3260
+ self.scroll_x = max(0, viewport_scroll_x)
3261
+
3262
+ # do the same for Y
3263
+ # this algo try to center the cursor as much as possible
3264
+ dy = self.line_height + self.line_spacing
3265
+ offsety = cr * dy
3266
+
3267
+ padding_top = self.padding[1]
3268
+ padding_bottom = self.padding[3]
3269
+ viewport_height = self.height - padding_top - padding_bottom - dy
3270
+
3271
+ sy = self.scroll_y
3272
+ if offsety > viewport_height + sy:
3273
+ self.scroll_y = offsety - viewport_height
3274
+ elif offsety < sy:
3275
+ self.scroll_y = offsety
3276
+
3277
+ cursor = AliasProperty(_get_cursor, _set_cursor)
3278
+ '''Tuple of (col, row) values indicating the current cursor position.
3279
+ You can set a new (col, row) if you want to move the cursor. The scrolling
3280
+ area will be automatically updated to ensure that the cursor is
3281
+ visible inside the viewport.
3282
+
3283
+ :attr:`cursor` is an :class:`~kivy.properties.AliasProperty`.
3284
+ '''
3285
+
3286
+ def _get_cursor_col(self):
3287
+ return self._cursor[0]
3288
+
3289
+ cursor_col = AliasProperty(_get_cursor_col, None, bind=('cursor', ))
3290
+ '''Current column of the cursor.
3291
+
3292
+ :attr:`cursor_col` is an :class:`~kivy.properties.AliasProperty` to
3293
+ cursor[0], read-only.
3294
+ '''
3295
+
3296
+ def _get_cursor_row(self):
3297
+ return self._cursor[1]
3298
+
3299
+ cursor_row = AliasProperty(_get_cursor_row, None, bind=('cursor', ))
3300
+ '''Current row of the cursor.
3301
+
3302
+ :attr:`cursor_row` is an :class:`~kivy.properties.AliasProperty` to
3303
+ cursor[1], read-only.
3304
+ '''
3305
+
3306
+ cursor_pos = AliasProperty(_get_cursor_pos, None,
3307
+ bind=('cursor', 'padding', 'pos', 'size',
3308
+ 'focus', 'scroll_x', 'scroll_y',
3309
+ 'line_height', 'line_spacing'),
3310
+ cache=False)
3311
+ '''Current position of the cursor, in (x, y).
3312
+
3313
+ :attr:`cursor_pos` is an :class:`~kivy.properties.AliasProperty`,
3314
+ read-only.
3315
+ '''
3316
+
3317
+ cursor_color = ColorProperty([1, 0, 0, 1])
3318
+ '''Current color of the cursor, in (r, g, b, a) format.
3319
+
3320
+ .. versionadded:: 1.9.0
3321
+
3322
+ :attr:`cursor_color` is a :class:`~kivy.properties.ColorProperty` and
3323
+ defaults to [1, 0, 0, 1].
3324
+
3325
+ .. versionchanged:: 2.0.0
3326
+ Changed from :class:`~kivy.properties.ListProperty` to
3327
+ :class:`~kivy.properties.ColorProperty`.
3328
+ '''
3329
+
3330
+ cursor_width = NumericProperty('1sp')
3331
+ '''Current width of the cursor.
3332
+
3333
+ .. versionadded:: 1.10.0
3334
+
3335
+ :attr:`cursor_width` is a :class:`~kivy.properties.NumericProperty` and
3336
+ defaults to '1sp'.
3337
+ '''
3338
+
3339
+ line_height = NumericProperty(1)
3340
+ '''Height of a line. This property is automatically computed from the
3341
+ :attr:`font_name`, :attr:`font_size`. Changing the line_height will have
3342
+ no impact.
3343
+
3344
+ .. note::
3345
+
3346
+ :attr:`line_height` is the height of a single line of text.
3347
+ Use :attr:`minimum_height`, which also includes padding, to
3348
+ get the height required to display the text properly.
3349
+
3350
+ :attr:`line_height` is a :class:`~kivy.properties.NumericProperty`,
3351
+ read-only.
3352
+ '''
3353
+
3354
+ tab_width = NumericProperty(4)
3355
+ '''By default, each tab will be replaced by four spaces on the text
3356
+ input widget. You can set a lower or higher value.
3357
+
3358
+ :attr:`tab_width` is a :class:`~kivy.properties.NumericProperty` and
3359
+ defaults to 4.
3360
+ '''
3361
+
3362
+ padding_x = VariableListProperty([0, 0], length=2, deprecated=True)
3363
+ '''Horizontal padding of the text: [padding_left, padding_right].
3364
+
3365
+ padding_x also accepts a one argument form [padding_horizontal].
3366
+
3367
+ :attr:`padding_x` is a :class:`~kivy.properties.VariableListProperty` and
3368
+ defaults to [0, 0]. This might be changed by the current theme.
3369
+
3370
+ .. deprecated:: 1.7.0
3371
+ Use :attr:`padding` instead.
3372
+ '''
3373
+
3374
+ def on_padding_x(self, instance, value):
3375
+ self.padding[0] = value[0]
3376
+ self.padding[2] = value[1]
3377
+
3378
+ padding_y = VariableListProperty([0, 0], length=2, deprecated=True)
3379
+ '''Vertical padding of the text: [padding_top, padding_bottom].
3380
+
3381
+ padding_y also accepts a one argument form [padding_vertical].
3382
+
3383
+ :attr:`padding_y` is a :class:`~kivy.properties.VariableListProperty` and
3384
+ defaults to [0, 0]. This might be changed by the current theme.
3385
+
3386
+ .. deprecated:: 1.7.0
3387
+ Use :attr:`padding` instead.
3388
+ '''
3389
+
3390
+ def on_padding_y(self, instance, value):
3391
+ self.padding[1] = value[0]
3392
+ self.padding[3] = value[1]
3393
+
3394
+ padding = VariableListProperty([6, 6, 6, 6])
3395
+ '''Padding of the text: [padding_left, padding_top, padding_right,
3396
+ padding_bottom].
3397
+
3398
+ padding also accepts a two argument form [padding_horizontal,
3399
+ padding_vertical] and a one argument form [padding].
3400
+
3401
+ .. versionchanged:: 1.7.0
3402
+ Replaced AliasProperty with VariableListProperty.
3403
+
3404
+ :attr:`padding` is a :class:`~kivy.properties.VariableListProperty` and
3405
+ defaults to [6, 6, 6, 6].
3406
+ '''
3407
+
3408
+ halign = OptionProperty('auto', options=['left', 'center', 'right',
3409
+ 'auto'])
3410
+ '''Horizontal alignment of the text.
3411
+
3412
+ :attr:`halign` is an :class:`~kivy.properties.OptionProperty` and
3413
+ defaults to 'auto'. Available options are : auto, left, center and right.
3414
+ Auto will attempt to autodetect horizontal alignment for RTL text (Pango
3415
+ only), otherwise it behaves like `left`.
3416
+
3417
+ .. versionadded:: 1.10.1
3418
+ '''
3419
+
3420
+ scroll_x = NumericProperty(0)
3421
+ '''X scrolling value of the viewport. The scrolling is automatically
3422
+ updated when the cursor is moved or text changed. If there is no
3423
+ user input, the scroll_x and scroll_y properties may be changed.
3424
+
3425
+ :attr:`scroll_x` is a :class:`~kivy.properties.NumericProperty` and
3426
+ defaults to 0.
3427
+ '''
3428
+
3429
+ scroll_y = NumericProperty(0)
3430
+ '''Y scrolling value of the viewport. See :attr:`scroll_x` for more
3431
+ information.
3432
+
3433
+ :attr:`scroll_y` is a :class:`~kivy.properties.NumericProperty` and
3434
+ defaults to 0.
3435
+ '''
3436
+
3437
+ selection_color = ColorProperty([0.1843, 0.6549, 0.8313, .5])
3438
+ '''Current color of the selection, in (r, g, b, a) format.
3439
+
3440
+ .. warning::
3441
+
3442
+ The color should always have an "alpha" component less than 1
3443
+ since the selection is drawn after the text.
3444
+
3445
+ :attr:`selection_color` is a :class:`~kivy.properties.ColorProperty` and
3446
+ defaults to [0.1843, 0.6549, 0.8313, .5].
3447
+
3448
+ .. versionchanged:: 2.0.0
3449
+ Changed from :class:`~kivy.properties.ListProperty` to
3450
+ :class:`~kivy.properties.ColorProperty`.
3451
+ '''
3452
+
3453
+ border = ListProperty([4, 4, 4, 4])
3454
+ '''Border used for :class:`~kivy.graphics.vertex_instructions.BorderImage`
3455
+ graphics instruction. Used with :attr:`background_normal` and
3456
+ :attr:`background_active`. Can be used for a custom background.
3457
+
3458
+ .. versionadded:: 1.4.1
3459
+
3460
+ It must be a list of four values: (bottom, right, top, left). Read the
3461
+ BorderImage instruction for more information about how to use it.
3462
+
3463
+ :attr:`border` is a :class:`~kivy.properties.ListProperty` and defaults
3464
+ to (4, 4, 4, 4).
3465
+ '''
3466
+
3467
+ background_normal = StringProperty(
3468
+ 'atlas://data/images/defaulttheme/textinput')
3469
+ '''Background image of the TextInput when it's not in focus.
3470
+
3471
+ .. versionadded:: 1.4.1
3472
+
3473
+ :attr:`background_normal` is a :class:`~kivy.properties.StringProperty` and
3474
+ defaults to 'atlas://data/images/defaulttheme/textinput'.
3475
+ '''
3476
+
3477
+ background_disabled_normal = StringProperty(
3478
+ 'atlas://data/images/defaulttheme/textinput_disabled')
3479
+ '''Background image of the TextInput when disabled.
3480
+
3481
+ .. versionadded:: 1.8.0
3482
+
3483
+ :attr:`background_disabled_normal` is a
3484
+ :class:`~kivy.properties.StringProperty` and
3485
+ defaults to 'atlas://data/images/defaulttheme/textinput_disabled'.
3486
+ '''
3487
+
3488
+ background_active = StringProperty(
3489
+ 'atlas://data/images/defaulttheme/textinput_active')
3490
+ '''Background image of the TextInput when it's in focus.
3491
+
3492
+ .. versionadded:: 1.4.1
3493
+
3494
+ :attr:`background_active` is a
3495
+ :class:`~kivy.properties.StringProperty` and
3496
+ defaults to 'atlas://data/images/defaulttheme/textinput_active'.
3497
+ '''
3498
+
3499
+ background_color = ColorProperty([1, 1, 1, 1])
3500
+ '''Current color of the background, in (r, g, b, a) format.
3501
+
3502
+ .. versionadded:: 1.2.0
3503
+
3504
+ :attr:`background_color` is a :class:`~kivy.properties.ColorProperty`
3505
+ and defaults to [1, 1, 1, 1] (white).
3506
+
3507
+ .. versionchanged:: 2.0.0
3508
+ Changed from :class:`~kivy.properties.ListProperty` to
3509
+ :class:`~kivy.properties.ColorProperty`.
3510
+ '''
3511
+
3512
+ foreground_color = ColorProperty([0, 0, 0, 1])
3513
+ '''Current color of the foreground, in (r, g, b, a) format.
3514
+
3515
+ .. versionadded:: 1.2.0
3516
+
3517
+ :attr:`foreground_color` is a :class:`~kivy.properties.ColorProperty`
3518
+ and defaults to [0, 0, 0, 1] (black).
3519
+
3520
+ .. versionchanged:: 2.0.0
3521
+ Changed from :class:`~kivy.properties.ListProperty` to
3522
+ :class:`~kivy.properties.ColorProperty`.
3523
+ '''
3524
+
3525
+ disabled_foreground_color = ColorProperty([0, 0, 0, .5])
3526
+ '''Current color of the foreground when disabled, in (r, g, b, a) format.
3527
+
3528
+ .. versionadded:: 1.8.0
3529
+
3530
+ :attr:`disabled_foreground_color` is a
3531
+ :class:`~kivy.properties.ColorProperty` and
3532
+ defaults to [0, 0, 0, 5] (50% transparent black).
3533
+
3534
+ .. versionchanged:: 2.0.0
3535
+ Changed from :class:`~kivy.properties.ListProperty` to
3536
+ :class:`~kivy.properties.ColorProperty`.
3537
+ '''
3538
+
3539
+ use_bubble = BooleanProperty(not _is_desktop)
3540
+ '''Indicates whether the cut/copy/paste bubble is used.
3541
+
3542
+ .. versionadded:: 1.7.0
3543
+
3544
+ :attr:`use_bubble` is a :class:`~kivy.properties.BooleanProperty`
3545
+ and defaults to True on mobile OS's, False on desktop OS's.
3546
+ '''
3547
+
3548
+ use_handles = BooleanProperty(not _is_desktop)
3549
+ '''Indicates whether the selection handles are displayed.
3550
+
3551
+ .. versionadded:: 1.8.0
3552
+
3553
+ :attr:`use_handles` is a :class:`~kivy.properties.BooleanProperty`
3554
+ and defaults to True on mobile OS's, False on desktop OS's.
3555
+ '''
3556
+
3557
+ scroll_from_swipe = BooleanProperty(not _is_desktop)
3558
+ '''Allow to scroll the text using swipe gesture according to
3559
+ :attr:`scroll_timeout` and :attr:`scroll_distance`.
3560
+
3561
+ .. versionadded:: 2.1.0
3562
+
3563
+ :attr:`scroll_from_swipe` is a BooleanProperty and defaults to True on
3564
+ mobile OS’s, False on desktop OS’s.
3565
+ '''
3566
+
3567
+ scroll_distance = NumericProperty(_scroll_distance)
3568
+ '''Minimum distance to move before change from scroll to selection mode, in
3569
+ pixels.
3570
+ It is advisable that you base this value on the dpi of your target device's
3571
+ screen.
3572
+
3573
+ .. versionadded:: 2.1.0
3574
+
3575
+ :attr:`scroll_distance` is a NumericProperty and defaults to 20 pixels.
3576
+ '''
3577
+
3578
+ scroll_timeout = NumericProperty(_scroll_timeout)
3579
+ '''Timeout allowed to trigger the :attr:`scroll_distance`, in milliseconds.
3580
+ If the user has not moved :attr:`scroll_distance` within the timeout, the
3581
+ scrolling will be disabled, and the selection mode will start.
3582
+
3583
+ .. versionadded:: 2.1.0
3584
+
3585
+ :attr:`scroll_timeout` is a NumericProperty and defaults to 250
3586
+ milliseconds.
3587
+ '''
3588
+
3589
+ def get_sel_from(self):
3590
+ return self._selection_from
3591
+
3592
+ selection_from = AliasProperty(get_sel_from, None)
3593
+ '''If a selection is in progress or complete, this property will represent
3594
+ the cursor index where the selection started.
3595
+
3596
+ .. versionchanged:: 1.4.0
3597
+ :attr:`selection_from` is an :class:`~kivy.properties.AliasProperty`
3598
+ and defaults to None, readonly.
3599
+ '''
3600
+
3601
+ def get_sel_to(self):
3602
+ return self._selection_to
3603
+
3604
+ selection_to = AliasProperty(get_sel_to, None)
3605
+ '''If a selection is in progress or complete, this property will represent
3606
+ the cursor index where the selection started.
3607
+
3608
+ .. versionchanged:: 1.4.0
3609
+ :attr:`selection_to` is an :class:`~kivy.properties.AliasProperty` and
3610
+ defaults to None, readonly.
3611
+ '''
3612
+
3613
+ selection_text = StringProperty(u'')
3614
+ '''Current content selection.
3615
+
3616
+ :attr:`selection_text` is a :class:`~kivy.properties.StringProperty`
3617
+ and defaults to '', readonly.
3618
+ '''
3619
+
3620
+ def on_selection_text(self, instance, value):
3621
+ if value:
3622
+ if self.use_handles:
3623
+ self._trigger_show_handles()
3624
+ if CutBuffer and not self.password:
3625
+ self._trigger_update_cutbuffer()
3626
+
3627
+ def _get_text(self):
3628
+ flags = self._lines_flags
3629
+ lines = self._lines
3630
+ len_lines = len(lines)
3631
+ less_flags = len(flags) < len_lines
3632
+ if less_flags:
3633
+ flags.append(1)
3634
+ text = ''.join(
3635
+ ('\n' if (flags[i] & FL_IS_LINEBREAK) else '') + lines[i]
3636
+ for i in range(len_lines)
3637
+ )
3638
+ if less_flags:
3639
+ flags.pop()
3640
+ return text
3641
+
3642
+ def _set_text(self, text):
3643
+ if isinstance(text, bytes):
3644
+ text = text.decode('utf8')
3645
+ if self.replace_crlf:
3646
+ text = text.replace(u'\r\n', u'\n')
3647
+ if self.text != text:
3648
+ self._refresh_text(text)
3649
+ self.cursor = self.get_cursor_from_index(len(text))
3650
+
3651
+ text = AliasProperty(_get_text, _set_text, bind=('_lines',), cache=True)
3652
+ '''Text of the widget.
3653
+
3654
+ Creation of a simple hello world::
3655
+
3656
+ widget = TextInput(text='Hello world')
3657
+
3658
+ If you want to create the widget with an unicode string, use::
3659
+
3660
+ widget = TextInput(text=u'My unicode string')
3661
+
3662
+ :attr:`text` is an :class:`~kivy.properties.AliasProperty`.
3663
+ '''
3664
+
3665
+ font_name = StringProperty(DEFAULT_FONT)
3666
+ '''Filename of the font to use. The path can be absolute or relative.
3667
+ Relative paths are resolved by the :func:`~kivy.resources.resource_find`
3668
+ function.
3669
+
3670
+ .. warning::
3671
+
3672
+ Depending on your text provider, the font file may be ignored. However,
3673
+ you can mostly use this without problems.
3674
+
3675
+ If the font used lacks the glyphs for the particular language/symbols
3676
+ you are using, you will see '[]' blank box characters instead of the
3677
+ actual glyphs. The solution is to use a font that has the glyphs you
3678
+ need to display. For example, to display |unicodechar|, use a font like
3679
+ freesans.ttf that has the glyph.
3680
+
3681
+ .. |unicodechar| image:: images/unicode-char.png
3682
+
3683
+ :attr:`font_name` is a :class:`~kivy.properties.StringProperty` and
3684
+ defaults to 'Roboto'. This value is taken
3685
+ from :class:`~kivy.config.Config`.
3686
+ '''
3687
+
3688
+ font_size = NumericProperty('15sp')
3689
+ '''Font size of the text in pixels.
3690
+
3691
+ :attr:`font_size` is a :class:`~kivy.properties.NumericProperty` and
3692
+ defaults to 15 :attr:`~kivy.metrics.sp`.
3693
+ '''
3694
+
3695
+ font_context = StringProperty(None, allownone=True)
3696
+ '''Font context. `None` means the font is used in isolation, so you are
3697
+ guaranteed to be drawing with the TTF file resolved by :attr:`font_name`.
3698
+ Specifying a value here will load the font file into a named context,
3699
+ enabling fallback between all fonts in the same context. If a font
3700
+ context is set, you are not guaranteed that rendering will actually use
3701
+ the specified TTF file for all glyphs (Pango will pick the one it
3702
+ thinks is best).
3703
+
3704
+ If Kivy is linked against a system-wide installation of FontConfig,
3705
+ you can load the system fonts by specifying a font context starting
3706
+ with the special string `system://`. This will load the system
3707
+ fontconfig configuration, and add your application-specific fonts on
3708
+ top of it (this imposes a significant risk of family name collision,
3709
+ Pango may not use your custom font file, but pick one from the system)
3710
+
3711
+ .. note::
3712
+ This feature requires the Pango text provider.
3713
+
3714
+ .. versionadded:: 1.10.1
3715
+
3716
+ :attr:`font_context` is a :class:`~kivy.properties.StringProperty` and
3717
+ defaults to None.
3718
+ '''
3719
+
3720
+ font_family = StringProperty(None, allownone=True)
3721
+ '''Font family, this is only applicable when using :attr:`font_context`
3722
+ option. The specified font family will be requested, but note that it may
3723
+ not be available, or there could be multiple fonts registered with the
3724
+ same family. The value can be a family name (string) available in the
3725
+ font context (for example a system font in a `system://` context, or a
3726
+ custom font file added using :class:`kivy.core.text.FontContextManager`).
3727
+ If set to `None`, font selection is controlled by the :attr:`font_name`
3728
+ setting.
3729
+
3730
+ .. note::
3731
+ If using :attr:`font_name` to reference a custom font file, you
3732
+ should leave this as `None`. The family name is managed automatically
3733
+ in this case.
3734
+
3735
+ .. note::
3736
+ This feature requires the Pango text provider.
3737
+
3738
+ .. versionadded:: 1.10.1
3739
+
3740
+ :attr:`font_family` is a :class:`~kivy.properties.StringProperty` and
3741
+ defaults to None.
3742
+ '''
3743
+
3744
+ base_direction = OptionProperty(
3745
+ None,
3746
+ options=['ltr', 'rtl', 'weak_rtl', 'weak_ltr', None],
3747
+ allownone=True
3748
+ )
3749
+ '''Base direction of text, this impacts horizontal alignment when
3750
+ :attr:`halign` is `auto` (the default). Available options are: None,
3751
+ "ltr" (left to right), "rtl" (right to left) plus "weak_ltr" and
3752
+ "weak_rtl".
3753
+
3754
+ .. note::
3755
+ This feature requires the Pango text provider.
3756
+
3757
+ .. note::
3758
+ Weak modes are currently not implemented in Kivy text layout, and
3759
+ have the same effect as setting strong mode.
3760
+
3761
+ .. versionadded:: 1.10.1
3762
+
3763
+ :attr:`base_direction` is an :class:`~kivy.properties.OptionProperty` and
3764
+ defaults to None (autodetect RTL if possible, otherwise LTR).
3765
+ '''
3766
+
3767
+ text_language = StringProperty(None, allownone=True)
3768
+ '''Language of the text, if None Pango will determine it from locale.
3769
+ This is an RFC-3066 format language tag (as a string), for example
3770
+ "en_US", "zh_CN", "fr" or "ja". This can impact font selection, metrics
3771
+ and rendering. For example, the same bytes of text can look different
3772
+ for `ur` and `ar` languages, though both use Arabic script.
3773
+
3774
+ .. note::
3775
+ This feature requires the Pango text provider.
3776
+
3777
+ .. versionadded:: 1.10.1
3778
+
3779
+ :attr:`text_language` is a :class:`~kivy.properties.StringProperty` and
3780
+ defaults to None.
3781
+ '''
3782
+
3783
+ _hint_text = StringProperty('')
3784
+
3785
+ def _set_hint_text(self, value):
3786
+ if isinstance(value, bytes):
3787
+ value = value.decode('utf8')
3788
+ self._hint_text = value
3789
+
3790
+ def _get_hint_text(self):
3791
+ return self._hint_text
3792
+
3793
+ hint_text = AliasProperty(
3794
+ _get_hint_text, _set_hint_text, bind=('_hint_text', ))
3795
+ '''Hint text of the widget, shown if text is ''.
3796
+
3797
+ .. versionadded:: 1.6.0
3798
+
3799
+ .. versionchanged:: 1.10.0
3800
+ The property is now an AliasProperty and byte values are decoded to
3801
+ strings. The hint text will stay visible when the widget is focused.
3802
+
3803
+ :attr:`hint_text` a :class:`~kivy.properties.AliasProperty` and defaults
3804
+ to ''.
3805
+ '''
3806
+
3807
+ hint_text_color = ColorProperty([0.5, 0.5, 0.5, 1.0])
3808
+ '''Current color of the hint_text text, in (r, g, b, a) format.
3809
+
3810
+ .. versionadded:: 1.6.0
3811
+
3812
+ :attr:`hint_text_color` is a :class:`~kivy.properties.ColorProperty` and
3813
+ defaults to [0.5, 0.5, 0.5, 1.0] (grey).
3814
+
3815
+ .. versionchanged:: 2.0.0
3816
+ Changed from :class:`~kivy.properties.ListProperty` to
3817
+ :class:`~kivy.properties.ColorProperty`.
3818
+ '''
3819
+
3820
+ auto_indent = BooleanProperty(False)
3821
+ '''Automatically indent multiline text.
3822
+
3823
+ .. versionadded:: 1.7.0
3824
+
3825
+ :attr:`auto_indent` is a :class:`~kivy.properties.BooleanProperty` and
3826
+ defaults to False.
3827
+ '''
3828
+
3829
+ replace_crlf = BooleanProperty(True)
3830
+ '''Automatically replace CRLF with LF.
3831
+
3832
+ .. versionadded:: 1.9.1
3833
+
3834
+ :attr:`replace_crlf` is a :class:`~kivy.properties.BooleanProperty` and
3835
+ defaults to True.
3836
+ '''
3837
+
3838
+ allow_copy = BooleanProperty(True)
3839
+ '''Decides whether to allow copying the text.
3840
+
3841
+ .. versionadded:: 1.8.0
3842
+
3843
+ :attr:`allow_copy` is a :class:`~kivy.properties.BooleanProperty` and
3844
+ defaults to True.
3845
+ '''
3846
+
3847
+ def _get_min_height(self):
3848
+ return (
3849
+ len(self._lines) * (self.line_height + self.line_spacing)
3850
+ + self.padding[1]
3851
+ + self.padding[3]
3852
+ )
3853
+
3854
+ minimum_height = AliasProperty(
3855
+ _get_min_height,
3856
+ bind=(
3857
+ '_lines', 'line_spacing', 'padding', 'font_size', 'font_name',
3858
+ 'password', 'font_context', 'hint_text', 'line_height'
3859
+ ),
3860
+ cache=True
3861
+ )
3862
+ '''Minimum height of the content inside the TextInput.
3863
+
3864
+ .. versionadded:: 1.8.0
3865
+
3866
+ :attr:`minimum_height` is a readonly
3867
+ :class:`~kivy.properties.AliasProperty`.
3868
+
3869
+ .. warning::
3870
+ :attr:`minimum_width` is calculated based on :attr:`width` therefore
3871
+ code like this will lead to an infinite loop::
3872
+
3873
+ <FancyTextInput>:
3874
+ height: self.minimum_height
3875
+ width: self.height
3876
+ '''
3877
+
3878
+ line_spacing = NumericProperty(0)
3879
+ '''Space taken up between the lines.
3880
+
3881
+ .. versionadded:: 1.8.0
3882
+
3883
+ :attr:`line_spacing` is a :class:`~kivy.properties.NumericProperty` and
3884
+ defaults to 0.
3885
+ '''
3886
+
3887
+ lines_to_scroll = BoundedNumericProperty(3, min=1)
3888
+ '''Set how many lines will be scrolled at once when using the mouse scroll
3889
+ wheel.
3890
+
3891
+ .. versionadded:: 2.2.0
3892
+
3893
+ :attr:`lines_to_scroll is a
3894
+ :class:`~kivy.properties.BoundedNumericProperty` and defaults to 3, the
3895
+ minimum is 1.
3896
+ '''
3897
+
3898
+ input_filter = ObjectProperty(None, allownone=True)
3899
+ ''' Filters the input according to the specified mode, if not None. If
3900
+ None, no filtering is applied.
3901
+
3902
+ .. versionadded:: 1.9.0
3903
+
3904
+ :attr:`input_filter` is an :class:`~kivy.properties.ObjectProperty` and
3905
+ defaults to `None`. Can be one of `None`, `'int'` (string), or `'float'`
3906
+ (string), or a callable. If it is `'int'`, it will only accept numbers.
3907
+ If it is `'float'` it will also accept a single period. Finally, if it is
3908
+ a callable it will be called with two parameters; the string to be added
3909
+ and a bool indicating whether the string is a result of undo (True). The
3910
+ callable should return a new substring that will be used instead.
3911
+ '''
3912
+
3913
+ handle_image_middle = StringProperty(
3914
+ 'atlas://data/images/defaulttheme/selector_middle')
3915
+ '''Image used to display the middle handle on the TextInput for cursor
3916
+ positioning.
3917
+
3918
+ .. versionadded:: 1.8.0
3919
+
3920
+ :attr:`handle_image_middle` is a :class:`~kivy.properties.StringProperty`
3921
+ and defaults to 'atlas://data/images/defaulttheme/selector_middle'.
3922
+ '''
3923
+
3924
+ def on_handle_image_middle(self, instance, value):
3925
+ if self._handle_middle:
3926
+ self._handle_middle.source = value
3927
+
3928
+ handle_image_left = StringProperty(
3929
+ 'atlas://data/images/defaulttheme/selector_left')
3930
+ '''Image used to display the Left handle on the TextInput for selection.
3931
+
3932
+ .. versionadded:: 1.8.0
3933
+
3934
+ :attr:`handle_image_left` is a :class:`~kivy.properties.StringProperty` and
3935
+ defaults to 'atlas://data/images/defaulttheme/selector_left'.
3936
+ '''
3937
+
3938
+ def on_handle_image_left(self, instance, value):
3939
+ if self._handle_left:
3940
+ self._handle_left.source = value
3941
+
3942
+ handle_image_right = StringProperty(
3943
+ 'atlas://data/images/defaulttheme/selector_right')
3944
+ '''Image used to display the Right handle on the TextInput for selection.
3945
+
3946
+ .. versionadded:: 1.8.0
3947
+
3948
+ :attr:`handle_image_right` is a
3949
+ :class:`~kivy.properties.StringProperty` and defaults to
3950
+ 'atlas://data/images/defaulttheme/selector_right'.
3951
+ '''
3952
+
3953
+ def on_handle_image_right(self, instance, value):
3954
+ if self._handle_right:
3955
+ self._handle_right.source = value
3956
+
3957
+ write_tab = BooleanProperty(True)
3958
+ '''Whether the tab key should move focus to the next widget or if it should
3959
+ enter a tab in the :class:`TextInput`. If `True` a tab will be written,
3960
+ otherwise, focus will move to the next widget.
3961
+
3962
+ .. versionadded:: 1.9.0
3963
+
3964
+ :attr:`write_tab` is a :class:`~kivy.properties.BooleanProperty` and
3965
+ defaults to `True`.
3966
+ '''
3967
+
3968
+
3969
+ if __name__ == '__main__':
3970
+ from textwrap import dedent
3971
+ from kivy.app import App
3972
+ from kivy.uix.boxlayout import BoxLayout
3973
+ from kivy.lang import Builder
3974
+
3975
+ KV = dedent(r'''
3976
+ #:set font_size '20dp'
3977
+
3978
+ BoxLayout:
3979
+ orientation: 'vertical'
3980
+ padding: '20dp'
3981
+ spacing: '10dp'
3982
+ TextInput:
3983
+ font_size: font_size
3984
+ size_hint_y: None
3985
+ height: self.minimum_height
3986
+ multiline: False
3987
+ text: 'monoline'
3988
+
3989
+ TextInput:
3990
+ size_hint_y: None
3991
+ font_size: font_size
3992
+ height: self.minimum_height
3993
+ multiline: False
3994
+ password: True
3995
+ password_mask: '•'
3996
+ text: 'password'
3997
+
3998
+ TextInput:
3999
+ font_size: font_size
4000
+ size_hint_y: None
4001
+ height: self.minimum_height
4002
+ multiline: False
4003
+ readonly: True
4004
+ text: 'readonly'
4005
+
4006
+ TextInput:
4007
+ font_size: font_size
4008
+ size_hint_y: None
4009
+ height: self.minimum_height
4010
+ multiline: False
4011
+ disabled: True
4012
+ text: 'disabled'
4013
+
4014
+ TextInput:
4015
+ font_size: font_size
4016
+ hint_text: 'normal with hint text'
4017
+
4018
+ TextInput:
4019
+ font_size: font_size
4020
+ text: 'default'
4021
+
4022
+ TextInput:
4023
+ font_size: font_size
4024
+ text: 'bubble & handles'
4025
+ use_bubble: True
4026
+ use_handles: True
4027
+
4028
+ TextInput:
4029
+ font_size: font_size
4030
+ text: 'no wrap'
4031
+ do_wrap: False
4032
+
4033
+ TextInput:
4034
+ font_size: font_size
4035
+ text: 'multiline\nreadonly'
4036
+ disabled: app.time % 5 < 2.5
4037
+ ''')
4038
+
4039
+ class TextInputApp(App):
4040
+ time = NumericProperty()
4041
+
4042
+ def build(self):
4043
+ Clock.schedule_interval(self.update_time, 0)
4044
+ return Builder.load_string(KV)
4045
+
4046
+ def update_time(self, dt):
4047
+ self.time += dt
4048
+
4049
+ TextInputApp().run()